@bruc3van/dsh-doctor 0.5.5 → 0.5.6

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 CHANGED
@@ -12,7 +12,7 @@ DSH 0.1.1 → DSH 0.1.2
12
12
 
13
13
  The project also diagnoses DSH profiles and installed plugins, checks for compatible versions, and performs safety checks before quarantine or removal.
14
14
 
15
- > This is a community-maintained third-party project, not an official DeepSeek project. The current catalog uses `dsh-v0.1.1-rc.2` and `dsh-v0.1.2-alpha.2` as the reference points for the 0.1.1-to-0.1.2 changes. An agent should still check actual differences when a plugin uses another patch or prerelease.
15
+ > This is a community-maintained third-party project, not an official DeepSeek project. The migration scope remains DSH 0.1.1 → 0.1.2; the current known-latest catalog exactly covers `dsh-v0.1.1-rc.2` through `dsh-v0.1.2-alpha.3`. The older alpha.2 catalog remains as historical rules. When a newer 0.1.2 prerelease appears, inspect its delta before deliberately updating the catalog and skill.
16
16
 
17
17
  ## Upgrade a plugin with the skill
18
18
 
@@ -115,7 +115,7 @@ Node.js `^22.19.0` or `>=24.0.0` is required.
115
115
  First confirm that the CLI contains the required migration:
116
116
 
117
117
  ```sh
118
- npx --yes --package=@bruc3van/dsh-doctor@0.5.5 \
118
+ npm exec --yes --package=@bruc3van/dsh-doctor@<selected-version> -- \
119
119
  dsh-doctor migrations list
120
120
  ```
121
121
 
@@ -124,12 +124,12 @@ npx --yes --package=@bruc3van/dsh-doctor@0.5.5 \
124
124
  ```sh
125
125
  dsh-doctor migrate analyze /path/to/plugin \
126
126
  --from dsh-v0.1.1-rc.2 \
127
- --to dsh-v0.1.2-alpha.2 \
127
+ --to dsh-v0.1.2-alpha.3 \
128
128
  --harness-root /path/to/deepseek-harness \
129
129
  --json
130
130
  ```
131
131
 
132
- Analysis checks source, dependencies, manifest, client graph, patch targets, and existing build output without executing plugin code.
132
+ Analysis checks source, dependencies, manifest, client graph, patch targets, and existing build output without executing plugin code. `--from`/`--to` select the exact catalog that supplies API rules. Alpha.3 is the current default, so it needs no separate `--target-version`. If a newer 0.1.2 build is explicitly targeted before the catalog is updated, inspect the source delta from alpha.3 first, then use `--target-version` to control dependency ranges, safe-plan pins, and the runtime version check. The report keeps the catalog and actual targets separate.
133
133
 
134
134
  ### 2. Apply
135
135
 
@@ -155,7 +155,7 @@ dsh-doctor migrate verify /path/to/plugin --level static \
155
155
  dsh-doctor migrate verify /path/to/plugin --level build --yes --install \
156
156
  --harness-root /path/to/deepseek-harness --json
157
157
  dsh-doctor migrate verify /path/to/plugin --level runtime --yes --install \
158
- --harness-root /path/to/deepseek-harness --json
158
+ --dsh-command /path/to/dsh --json
159
159
  ```
160
160
 
161
161
  | Level | What it checks |
package/README.md CHANGED
@@ -12,7 +12,7 @@ DSH 0.1.1 → DSH 0.1.2
12
12
 
13
13
  项目同时提供 DSH profile 和插件的诊断、兼容版本检查、隔离与删除前检查。
14
14
 
15
- > 这是社区维护的第三方工具,不属于 DeepSeek 官方项目。当前 catalog `dsh-v0.1.1-rc.2` `dsh-v0.1.2-alpha.2` 为基准记录 0.1.1 0.1.2 的变化;插件使用其他 patch 或预发布版本时,Agent 仍需核对实际差异。
15
+ > 这是社区维护的第三方工具,不属于 DeepSeek 官方项目。工具的迁移范围保持为 DSH 0.1.1 → 0.1.2;当前已知最新版 catalog 精确覆盖 `dsh-v0.1.1-rc.2` `dsh-v0.1.2-alpha.3`。旧 alpha.2 catalog 作为历史规则保留;出现更新的 0.1.2 预发布版本时,需要先核对增量,再明确更新 catalog Skill。
16
16
 
17
17
  ## 使用 Skill 升级插件
18
18
 
@@ -115,7 +115,7 @@ Migration catalog 保存 source/target tag 和 Git commit,并记录 package、
115
115
  先确认 CLI 包含需要的迁移:
116
116
 
117
117
  ```sh
118
- npx --yes --package=@bruc3van/dsh-doctor@0.5.5 \
118
+ npm exec --yes --package=@bruc3van/dsh-doctor@<selected-version> -- \
119
119
  dsh-doctor migrations list
120
120
  ```
121
121
 
@@ -124,12 +124,12 @@ npx --yes --package=@bruc3van/dsh-doctor@0.5.5 \
124
124
  ```sh
125
125
  dsh-doctor migrate analyze /path/to/plugin \
126
126
  --from dsh-v0.1.1-rc.2 \
127
- --to dsh-v0.1.2-alpha.2 \
127
+ --to dsh-v0.1.2-alpha.3 \
128
128
  --harness-root /path/to/deepseek-harness \
129
129
  --json
130
130
  ```
131
131
 
132
- 分析会检查源码、依赖、manifest、client graph、patch target 和已有构建产物,不执行插件代码。
132
+ 分析会检查源码、依赖、manifest、client graph、patch target 和已有构建产物,不执行插件代码。`--from`/`--to` 选择提供 API 规则的精确 catalog。当前默认目标就是 alpha.3,无需额外指定 `--target-version`;如果在 catalog 更新前明确迁移到更新的 0.1.2 版本,必须先调查 alpha.3 到该版本的源码差异,再用 `--target-version` 单独控制依赖范围、safe plan pin 和 runtime 版本核验。报告会保留 catalog 与实际目标两个值。
133
133
 
134
134
  ### 2. 修改
135
135
 
@@ -155,7 +155,7 @@ dsh-doctor migrate verify /path/to/plugin --level static \
155
155
  dsh-doctor migrate verify /path/to/plugin --level build --yes --install \
156
156
  --harness-root /path/to/deepseek-harness --json
157
157
  dsh-doctor migrate verify /path/to/plugin --level runtime --yes --install \
158
- --harness-root /path/to/deepseek-harness --json
158
+ --dsh-command /path/to/dsh --json
159
159
  ```
160
160
 
161
161
  | 级别 | 验证内容 |
@@ -38,9 +38,7 @@
38
38
  "@deepseek-ai/dsh-win32-process"
39
39
  ],
40
40
  "targetVersions": {
41
- "@deepseek-ai/cordis": "4.0.2",
42
- "@deepseek-ai/dsh-client-store": "0.1.2-alpha.2",
43
- "@deepseek-ai/dsh-session": "0.1.2-alpha.2"
41
+ "@deepseek-ai/cordis": "4.0.2"
44
42
  },
45
43
  "dependencyPolicies": {
46
44
  "@deepseek-ai/cordis": {
@@ -1,5 +1,13 @@
1
1
  {
2
2
  "modules": {
3
+ "@deepseek-ai/dsh-settings": {
4
+ "settingsNamespace": {
5
+ "toModule": "@deepseek-ai/dsh-settings",
6
+ "toSymbol": "SettingsProvider.register",
7
+ "confidence": "semantic",
8
+ "reason": "The settingsNamespace factory was removed. Inject the settings Service and register the namespace through ctx.settings.register(name, schema)."
9
+ }
10
+ },
3
11
  "@deepseek-ai/dsh-client-runtime/client": {
4
12
  "ClientContext": {
5
13
  "toModule": "@deepseek-ai/cordis",
@@ -0,0 +1,14 @@
1
+ # DSH 0.1.1-rc.2 to 0.1.2-alpha.3 behavior migration
2
+
3
+ The old Client Runtime was split by ownership. There is no aggregate replacement package.
4
+
5
+ - Session control, list state, commands, projections, queue and event windows belong to `api-session-controller/client`.
6
+ - Workspace state and commands belong to `api-workspace-controller/client`; navigation policy belongs to `ui-workspace`.
7
+ - Conversation assembly belongs to `ui-conversation`; Chat and Trajectory own their respective projections.
8
+ - Approval and Question own their pending objects; `ui-session` only aggregates domain publications.
9
+ - The store engine belongs to `client-store`; React hook synthesis belongs to the renderer.
10
+ - The Host API Proxy was removed. Unary browser operations live on their natural Remote service owners.
11
+ - The demo-only `dsh-agent-spine-demo` package and the SQLite session persistence package were removed by alpha.3; neither has a catalog-confirmed drop-in replacement.
12
+ - Alpha.3 adds the `dsh-session-turn-outline` package and extends session submission/load-through APIs. These additions do not justify mechanical rewrites of existing plugin behavior.
13
+
14
+ An import move is safe only when the catalog marks it `exact`. A `semantic` mapping identifies the new owner but still requires the plugin developer to rewrite behavior and verify the real lifecycle.
@@ -0,0 +1,36 @@
1
+ {
2
+ "platformModules": [
3
+ "react",
4
+ "react/jsx-runtime",
5
+ "react-dom",
6
+ "react-dom/client",
7
+ "@deepseek-ai/cordis",
8
+ "@deepseek-ai/dsh-client-store",
9
+ "@deepseek-ai/dsh-client-ui-slots",
10
+ "@deepseek-ai/dsh-client-ui-primitives"
11
+ ],
12
+ "profilePatchPaths": {
13
+ "web": [
14
+ "packages/bundle/base/cordis.patch.yml",
15
+ "packages/bundle/web-app/cordis.patch.yml"
16
+ ]
17
+ },
18
+ "rules": [
19
+ {
20
+ "id": "client-inject-is-informational",
21
+ "note": "dsh.client.inject lists package dependency edges; Cordis service inject controls activation."
22
+ },
23
+ {
24
+ "id": "baseline-external-is-implicit",
25
+ "note": "Platform modules are implicit and must not be repeated in dsh.client.external."
26
+ },
27
+ {
28
+ "id": "feature-values-use-services-or-slots",
29
+ "note": "Feature plugins must not use dsh.client.external as a feature-to-feature value dependency mechanism."
30
+ },
31
+ {
32
+ "id": "client-export-required",
33
+ "note": "A web dsh.client declaration requires a published exports[\"./client\"] artifact."
34
+ }
35
+ ]
36
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "id": "dsh-v0.1.1-rc.2__dsh-v0.1.2-alpha.3",
4
+ "from": {
5
+ "ref": "dsh-v0.1.1-rc.2",
6
+ "commit": "b150a551b8d465e31e418e1b2eaf5e79bbb7d28e"
7
+ },
8
+ "to": {
9
+ "ref": "dsh-v0.1.2-alpha.3",
10
+ "commit": "dd6322d604e00eec1ba5e0c8541159906a21094a",
11
+ "version": "0.1.2-alpha.3"
12
+ },
13
+ "description": "DeepSeek Harness 0.1.1-rc.2 to 0.1.2-alpha.3 plugin migration",
14
+ "references": {
15
+ "ownership": ".agents/notes/implemented/architecture/2026-08-20-client-session-conversation-ownership.md",
16
+ "remoteMigration": ".agents/notes/implemented/architecture/2026-08-10-unary-apiproxy-remote-migration.md",
17
+ "clientRules": "packages/client/AGENTS.md"
18
+ }
19
+ }
@@ -0,0 +1,74 @@
1
+ {
2
+ "removed": [
3
+ "@deepseek-ai/dsh-acp-demo",
4
+ "@deepseek-ai/dsh-acp-snapshot",
5
+ "@deepseek-ai/dsh-agent-spine-demo",
6
+ "@deepseek-ai/dsh-client-runtime",
7
+ "@deepseek-ai/dsh-host-apiproxy",
8
+ "@deepseek-ai/dsh-sdk-jsonrpc-demo",
9
+ "@deepseek-ai/dsh-session-persistence-sqlite"
10
+ ],
11
+ "added": [
12
+ "@deepseek-ai/dsh-acp-app",
13
+ "@deepseek-ai/dsh-api-session-controller",
14
+ "@deepseek-ai/dsh-api-settings-controller",
15
+ "@deepseek-ai/dsh-api-workspace-controller",
16
+ "@deepseek-ai/dsh-client-store",
17
+ "@deepseek-ai/dsh-client-ui-approval",
18
+ "@deepseek-ai/dsh-client-ui-chat",
19
+ "@deepseek-ai/dsh-client-ui-schedule",
20
+ "@deepseek-ai/dsh-client-ui-session",
21
+ "@deepseek-ai/dsh-deepseek-llm-api-extensions",
22
+ "@deepseek-ai/dsh-deque",
23
+ "@deepseek-ai/dsh-experimental-agent-team-profile",
24
+ "@deepseek-ai/dsh-experimental-agent-team-web-profile",
25
+ "@deepseek-ai/dsh-experimental-client-ui-agent-team",
26
+ "@deepseek-ai/dsh-experimental-inspector",
27
+ "@deepseek-ai/dsh-experimental-webworker-packer",
28
+ "@deepseek-ai/dsh-experimental-webworker-runtime",
29
+ "@deepseek-ai/dsh-plugin-package-inventory-deepseek",
30
+ "@deepseek-ai/dsh-sdk-app",
31
+ "@deepseek-ai/dsh-sdk-minimal",
32
+ "@deepseek-ai/dsh-session-log-deepseek",
33
+ "@deepseek-ai/dsh-session-snapshot",
34
+ "@deepseek-ai/dsh-session-turn-outline",
35
+ "@deepseek-ai/dsh-util-crypto",
36
+ "@deepseek-ai/dsh-util-time",
37
+ "@deepseek-ai/dsh-util-values",
38
+ "@deepseek-ai/dsh-util-workspace-path",
39
+ "@deepseek-ai/dsh-webhook",
40
+ "@deepseek-ai/dsh-webhook-github",
41
+ "@deepseek-ai/dsh-win32-process"
42
+ ],
43
+ "targetVersions": {
44
+ "@deepseek-ai/cordis": "4.0.2"
45
+ },
46
+ "dependencyPolicies": {
47
+ "@deepseek-ai/cordis": {
48
+ "client": ["peerDependencies", "devDependencies"]
49
+ },
50
+ "@deepseek-ai/dsh-client-store": {
51
+ "client": ["devDependencies"]
52
+ },
53
+ "@deepseek-ai/dsh-session": {
54
+ "client": ["devDependencies"]
55
+ }
56
+ },
57
+ "semanticReplacements": {
58
+ "@deepseek-ai/dsh-client-runtime": [
59
+ "@deepseek-ai/dsh-api-session-controller",
60
+ "@deepseek-ai/dsh-api-workspace-controller",
61
+ "@deepseek-ai/dsh-client-store",
62
+ "@deepseek-ai/dsh-client-ui-session",
63
+ "@deepseek-ai/dsh-client-ui-conversation",
64
+ "@deepseek-ai/dsh-client-ui-chat",
65
+ "@deepseek-ai/dsh-client-ui-trajectory",
66
+ "@deepseek-ai/dsh-client-ui-approval",
67
+ "@deepseek-ai/dsh-client-ui-user-questions"
68
+ ],
69
+ "@deepseek-ai/dsh-host-apiproxy": [
70
+ "@deepseek-ai/dsh-api-gateway",
71
+ "@deepseek-ai/dsh-api-remotes"
72
+ ]
73
+ }
74
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "services": {
3
+ "sessions": {
4
+ "owner": "@deepseek-ai/dsh-api-session-controller/client",
5
+ "confidence": "semantic",
6
+ "note": "The service name survives, but Session snapshots no longer contain Conversation views or pending interactions."
7
+ },
8
+ "workspaces": {
9
+ "owner": "@deepseek-ai/dsh-api-workspace-controller/client",
10
+ "confidence": "semantic",
11
+ "note": "The service name survives, while cross-domain navigation belongs to ui-workspace."
12
+ },
13
+ "conversation": {
14
+ "owner": "@deepseek-ai/dsh-client-ui-conversation/client",
15
+ "confidence": "semantic",
16
+ "note": "Conversation assembly and input are independent of Session lifecycle snapshots."
17
+ },
18
+ "slots": {
19
+ "owner": "@deepseek-ai/dsh-client-ui-renderer/client",
20
+ "confidence": "semantic",
21
+ "note": "Slot declarations stay in ui-slots; runtime registration and binding belong to ui-renderer."
22
+ }
23
+ },
24
+ "removedHostFacade": {
25
+ "package": "@deepseek-ai/dsh-host-apiproxy",
26
+ "replacementPolicy": "Use the natural business Remote owner through generated api-remotes contributions; there is no replacement facade."
27
+ }
28
+ }
@@ -0,0 +1,134 @@
1
+ {
2
+ "modules": {
3
+ "@deepseek-ai/dsh-settings": {
4
+ "settingsNamespace": {
5
+ "toModule": "@deepseek-ai/dsh-settings",
6
+ "toSymbol": "SettingsProvider.register",
7
+ "confidence": "semantic",
8
+ "reason": "The settingsNamespace factory was removed. Inject the settings Service and register the namespace through ctx.settings.register(name, schema)."
9
+ }
10
+ },
11
+ "@deepseek-ai/dsh-client-runtime/client": {
12
+ "ClientContext": {
13
+ "toModule": "@deepseek-ai/cordis",
14
+ "toSymbol": "Context",
15
+ "confidence": "exact",
16
+ "reason": "ClientContext was a type alias of Cordis Context."
17
+ },
18
+ "createSnapshotStore": {
19
+ "toModule": "@deepseek-ai/dsh-client-store",
20
+ "toSymbol": "createSnapshotStore",
21
+ "confidence": "exact",
22
+ "reason": "The React-free store engine moved to client-store."
23
+ },
24
+ "defineStore": {
25
+ "toModule": "@deepseek-ai/dsh-client-store",
26
+ "toSymbol": "defineStore",
27
+ "confidence": "exact",
28
+ "reason": "The declarative store factory moved to client-store."
29
+ },
30
+ "ObservableSnapshot": {
31
+ "toModule": "@deepseek-ai/dsh-client-store",
32
+ "toSymbol": "ObservableSnapshot",
33
+ "confidence": "exact",
34
+ "reason": "The observable store contract moved to client-store."
35
+ },
36
+ "SnapshotStore": {
37
+ "toModule": "@deepseek-ai/dsh-client-store",
38
+ "toSymbol": "SnapshotStore",
39
+ "confidence": "exact",
40
+ "reason": "The writable store contract moved to client-store."
41
+ },
42
+ "shallowEqual": {
43
+ "toModule": "@deepseek-ai/dsh-client-store",
44
+ "toSymbol": "shallowEqual",
45
+ "confidence": "exact",
46
+ "reason": "The store equality helper moved to client-store."
47
+ },
48
+ "SessionId": {
49
+ "toModule": "@deepseek-ai/dsh-session/types",
50
+ "toSymbol": "SessionId",
51
+ "confidence": "exact",
52
+ "reason": "Session identity is owned by the Session domain."
53
+ },
54
+ "ISessions": {
55
+ "toModule": "@deepseek-ai/dsh-api-session-controller/client",
56
+ "toSymbol": "ISessions",
57
+ "confidence": "semantic",
58
+ "reason": "Session ownership moved to the Session Controller and its snapshots changed."
59
+ },
60
+ "Session": {
61
+ "toModule": "@deepseek-ai/dsh-api-session-controller/client",
62
+ "toSymbol": "Session",
63
+ "confidence": "semantic",
64
+ "reason": "The Client Session object and lifecycle moved to the Session Controller."
65
+ },
66
+ "SessionBinding": {
67
+ "toModule": "@deepseek-ai/dsh-api-session-controller/client",
68
+ "toSymbol": "SessionBinding",
69
+ "confidence": "semantic",
70
+ "reason": "Session bindings are now Controller-owned lifetimes."
71
+ },
72
+ "SessionListState": {
73
+ "toModule": "@deepseek-ai/dsh-api-session-controller/client",
74
+ "toSymbol": "SessionListState",
75
+ "confidence": "semantic",
76
+ "reason": "The list state moved and no longer owns Conversation or pending-interaction facts."
77
+ },
78
+ "SessionSummary": {
79
+ "toModule": "@deepseek-ai/dsh-api-session-controller/client",
80
+ "toSymbol": "SessionSummary",
81
+ "confidence": "semantic",
82
+ "reason": "The Session summary is now a Controller-owned control snapshot."
83
+ },
84
+ "UseProjection": {
85
+ "toModule": "@deepseek-ai/dsh-api-session-controller/client",
86
+ "toSymbol": "UseProjection",
87
+ "confidence": "semantic",
88
+ "reason": "Projection sources are now exposed by Session Controller bindings and adapted by ui-session."
89
+ },
90
+ "IWorkspaces": {
91
+ "toModule": "@deepseek-ai/dsh-api-workspace-controller/client",
92
+ "toSymbol": "IWorkspaces",
93
+ "confidence": "semantic",
94
+ "reason": "Workspace ownership moved to the Workspace Controller and navigation policy moved to ui-workspace."
95
+ },
96
+ "WorkspaceId": {
97
+ "toModule": "@deepseek-ai/dsh-api-workspace-controller/client",
98
+ "toSymbol": "WorkspaceId",
99
+ "confidence": "semantic",
100
+ "reason": "Workspace identity is re-exported by the Workspace Controller."
101
+ },
102
+ "WorkspaceView": {
103
+ "toModule": "@deepseek-ai/dsh-api-workspace-controller/client",
104
+ "toSymbol": "WorkspaceView",
105
+ "confidence": "semantic",
106
+ "reason": "Workspace snapshots are Controller-owned and their shape changed."
107
+ },
108
+ "ConversationNode": {
109
+ "toModule": "@deepseek-ai/dsh-client-ui-conversation/client",
110
+ "toSymbol": "ConversationNode",
111
+ "confidence": "semantic",
112
+ "reason": "Conversation assembly moved out of Session snapshots into ui-conversation."
113
+ },
114
+ "ConversationSnapshot": {
115
+ "toModule": "@deepseek-ai/dsh-client-ui-conversation/client",
116
+ "toSymbol": "ConversationSnapshot",
117
+ "confidence": "semantic",
118
+ "reason": "Conversation is now an independent target-neutral source."
119
+ },
120
+ "PendingInteraction": {
121
+ "toModule": "@deepseek-ai/dsh-client-ui-session/client",
122
+ "toSymbol": "SessionPendingInteraction",
123
+ "confidence": "semantic",
124
+ "reason": "Approval and Question now publish domain-owned pending interactions through ui-session."
125
+ },
126
+ "PendingWait": {
127
+ "toModule": "@deepseek-ai/dsh-client-ui-session/client",
128
+ "toSymbol": "SessionPendingInteraction",
129
+ "confidence": "semantic",
130
+ "reason": "The Runtime PendingWait aggregate was removed; pending domains own their payloads."
131
+ }
132
+ }
133
+ }
134
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bruc3van/dsh-doctor",
3
- "version": "0.5.5",
3
+ "version": "0.5.6",
4
4
  "description": "Help agents diagnose and upgrade DeepSeek Harness plugins from DSH 0.1.1 to 0.1.2",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,13 +1,13 @@
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, 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.
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 known target reference is dsh-v0.1.2-alpha.3.
4
4
  ---
5
5
 
6
6
  # Upgrade a DSH 0.1.1 plugin to 0.1.2
7
7
 
8
8
  Help the developer diagnose and modify one plugin at a time. Treat source migration, artifact verification, runtime activation, and business behavior as separate gates.
9
9
 
10
- The current migration knowledge covers the DSH 0.1.1 to 0.1.2 transition. Its CLI catalog records `dsh-v0.1.1-rc.2` and `dsh-v0.1.2-alpha.2` as the exact reference points used to derive and verify known changes. Use those refs in dsh-doctor commands, but first record the plugin's actual DSH ranges and requested target. When another patch or prerelease is involved, use the catalog for known changes only and report that the additional version difference still needs review. Do not claim that the catalog proves an unlisted version combination.
10
+ The migration knowledge covers the DSH 0.1.1 to 0.1.2 transition. The current known catalog records `dsh-v0.1.1-rc.2` and `dsh-v0.1.2-alpha.3` as the exact reference points used to derive and verify known changes. Keep the skill's product scope at the release-line level (`0.1.1` to `0.1.2`), while keeping the catalog target exact. First record the plugin's actual DSH ranges and requested target. When a newer patch or prerelease appears, use the catalog for known changes only, inspect the additional interval, and report that it is not catalog-confirmed until the catalog and this skill are deliberately updated. Do not claim that the catalog proves an unlisted version combination.
11
11
 
12
12
  ## Inputs
13
13
 
@@ -24,13 +24,15 @@ Determine:
24
24
  Use these catalog reference points in the current CLI commands:
25
25
 
26
26
  - source: `dsh-v0.1.1-rc.2`
27
- - target: `dsh-v0.1.2-alpha.2`
27
+ - target: `dsh-v0.1.2-alpha.3`
28
+
29
+ The standard commands target the current known alpha.3 catalog and do not need `--target-version`. If the developer explicitly targets a newer 0.1.2 build before the catalog is updated, inspect the interval first, then pass its exact version to every migration phase as `--target-version <actual-0.1.2-version>`. This changes dependency pins and runtime expectations only; it does not extend the catalog's API claims.
28
30
 
29
31
  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.
30
32
 
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.
33
+ 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 the exact-version `npm exec` fallback over changing the developer's global installation. Never globally install or update the CLI without explicit authorization.
32
34
 
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.
35
+ The examples below use `dsh-doctor` for readability. When bootstrap selected a package-runner invocation, substitute the complete pinned prefix, `npm exec --yes --package=@bruc3van/dsh-doctor@<selected-version> -- dsh-doctor`, in every phase.
34
36
 
35
37
  ## Compatibility decision gate
36
38
 
@@ -53,7 +55,7 @@ Run from any directory:
53
55
  ```sh
54
56
  dsh-doctor migrate analyze <plugin-root> \
55
57
  --from dsh-v0.1.1-rc.2 \
56
- --to dsh-v0.1.2-alpha.2 \
58
+ --to dsh-v0.1.2-alpha.3 \
57
59
  --json
58
60
  ```
59
61
 
@@ -61,6 +63,10 @@ Append `--harness-root <deepseek-harness-root>` when the checkout is available.
61
63
 
62
64
  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
65
 
66
+ The catalog reports the retained-package removal of `@deepseek-ai/dsh-settings.settingsNamespace` as semantic work. Remove only that named import, inject the `settings` Service, register through `ctx.settings.register(name, schema)`, and migrate any reads to the provider's current API. Do not remove still-exported symbols such as `SettingsConflictError` merely because they share the same import declaration.
67
+
68
+ The static analyzer recognizes named imports and re-exports for retained-package symbol removals, but it does not resolve property access through namespace imports such as `import * as settings from '@deepseek-ai/dsh-settings'`. Search for and inspect those namespace bindings manually before declaring the semantic migration complete.
69
+
64
70
  Group the result by:
65
71
 
66
72
  1. errors blocking compatibility;
@@ -102,7 +108,7 @@ Run the gates in order:
102
108
  ```sh
103
109
  dsh-doctor migrate verify <plugin-root> --level static --harness-root <deepseek-harness-root> --json
104
110
  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
111
+ dsh-doctor migrate verify <plugin-root> --level runtime --yes --install --dsh-command <actual-dsh-command> --json
106
112
  ```
107
113
 
108
114
  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`.
@@ -119,6 +125,8 @@ For a dual-version result, verify the produced release against both the actual 0
119
125
 
120
126
  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.
121
127
 
128
+ For a `0.1.2-only` release, update the README or compatibility documentation to state the minimum actual 0.1.2 version, that the new release does not support 0.1.1, and which prior plugin release 0.1.1 users should retain when known. After changing the release version or any other packed metadata, rebuild, inspect the final-version tarball, and repeat the isolated runtime gate against that artifact before committing or tagging; evidence from a tarball carrying the previous plugin version is not final release evidence.
129
+
122
130
  ## Report the outcome
123
131
 
124
132
  State the highest achieved gate exactly:
@@ -11,7 +11,7 @@ dsh-doctor --version
11
11
  dsh-doctor migrations list --json
12
12
  ```
13
13
 
14
- Record the local version and whether the exact `dsh-v0.1.1-rc.2` to `dsh-v0.1.2-alpha.2` catalog is present. A command that exists but lacks this catalog is not usable for this skill.
14
+ Record the local version and whether the exact current `dsh-v0.1.1-rc.2` to `dsh-v0.1.2-alpha.3` catalog is present. A command that exists but lacks this catalog is not usable for the current known target.
15
15
 
16
16
  ## 2. Check the registry without changing the machine
17
17
 
@@ -26,12 +26,14 @@ This is a read-only update check. If it fails because the registry is unavailabl
26
26
  ## 3. Select and pin one invocation
27
27
 
28
28
  - When the local version equals the registry version and exposes the catalog, use `dsh-doctor` directly.
29
- - When the local CLI is missing, differs from the registry version, or lacks the catalog, prefer the registry version through exact-version npx. First verify that version exposes the required catalog:
29
+ - When the local CLI is missing, differs from the registry version, or lacks the catalog, prefer the registry version through an exact-version package runner. First verify that version exposes the required catalog:
30
30
 
31
31
  ```sh
32
- npx --yes --package=@bruc3van/dsh-doctor@<registry-version> dsh-doctor migrations list --json
32
+ npm exec --yes --package=@bruc3van/dsh-doctor@<registry-version> -- dsh-doctor migrations list --json
33
33
  ```
34
34
 
35
+ This `npm exec ... -- dsh-doctor` form is the preferred cross-platform invocation. On Windows, `npx --package=... dsh-doctor` can fail to resolve the temporary binary when launched from an unrelated project root. A direct `npx --yes @bruc3van/dsh-doctor@<version> ...` smoke from a fresh temporary directory is also acceptable, but keep one verified invocation for the whole migration.
36
+
35
37
  - When the registry is unavailable, use the local CLI only if its catalog check passed.
36
38
  - When neither candidate exposes the catalog, stop and report the missing prerequisite instead of guessing a compatible CLI.
37
39
  - Respect an explicit developer request to use a particular local or development CLI, but still record its version and verify the catalog.
@@ -54,6 +56,6 @@ Include these fields in the handoff:
54
56
 
55
57
  - local CLI version or `missing`;
56
58
  - registry version or `unknown`;
57
- - selected exact version and invocation source (`local`, `npx`, or authorized `global-install`);
59
+ - selected exact version and invocation source (`local`, `npm-exec`, or authorized `global-install`);
58
60
  - required catalog present or absent;
59
61
  - update status: `current`, `outdated`, `missing`, or `unknown`.
@@ -19,6 +19,10 @@ Session snapshots no longer own Conversation views or all pending-interaction fa
19
19
 
20
20
  `@deepseek-ai/dsh-host-apiproxy` was removed. Browser operations use their natural generated Remote owners through API Remotes/API Gateway contributions. There is no facade-compatible package substitution.
21
21
 
22
+ ## Settings registration
23
+
24
+ `@deepseek-ai/dsh-settings` remains, but its `settingsNamespace` factory export was removed in 0.1.2-alpha.2. This is a semantic Service migration, not an import move: inject the `settings` Service and register the plugin namespace through `ctx.settings.register(name, schema)`. Keep `SettingsConflictError` or other exports that still exist, and verify every namespace read path against the provider API (for example `describe`) instead of assuming the former factory object's methods still exist.
25
+
22
26
  ## Client graph
23
27
 
24
28
  - `dsh.client.inject` declares package dependency edges; Cordis service injection still controls activation.
@@ -39,4 +43,4 @@ Do not copy a replacement package into every dependency section that contained `
39
43
 
40
44
  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.
41
45
 
42
- The machine-readable source of truth ships with DSH Doctor. Confirm the installed CLI exposes this exact version pair with `dsh-doctor migrations list`; the canonical catalog source is also available in the [DSH Doctor repository](https://github.com/bruc3van/dsh-doctor/tree/master/migrations/dsh-v0.1.1-rc.2__dsh-v0.1.2-alpha.2).
46
+ The machine-readable source of truth ships with DSH Doctor. Confirm the installed CLI exposes the current exact version pair with `dsh-doctor migrations list`; the canonical catalog source is also available in the [DSH Doctor repository](https://github.com/bruc3van/dsh-doctor/tree/master/migrations/dsh-v0.1.1-rc.2__dsh-v0.1.2-alpha.3). The older alpha.2 catalog remains historical evidence, not the current default.
@@ -70,6 +70,8 @@ Classify every additional difference as:
70
70
 
71
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
72
 
73
+ After the additional interval is reviewed, pass that exact version as `--target-version` in analyze, apply preview/apply, and verify. This is an explicit fallback for a newer 0.1.2 build that the current alpha.3 catalog does not yet cover. It changes DSH dependency-range validation, deterministic DSH development pins, and the runtime version expectation; it does not turn the unlisted interval into catalog-confirmed API knowledge. Use `--dsh-command` to name the executable that actually reports and runs that version, and update the catalog and skill deliberately when that build becomes the new known target.
74
+
73
75
  ## 4. Investigate each semantic task in the plugin
74
76
 
75
77
  Start from the finding's `file`, `symbol`, `targetModule`, `targetSymbol`, and `reason`. Then:
@@ -4,12 +4,16 @@
4
4
 
5
5
  Static analysis covers source/type imports, dependency ranges, client graph declarations, and generated artifacts. A pass means no known blocking finding; it does not execute project code.
6
6
 
7
+ The current known static target is alpha.3. When the actual target is later than that catalog ref on the same 0.1.2 release line, first inspect the additional interval, then use `--target-version <version>` at every phase. The report keeps `migration.to` as the catalog ref and records `migration.actualTarget` separately, so a successful dependency or runtime check cannot be mistaken for catalog coverage of the additional API delta.
8
+
7
9
  ## Build
8
10
 
9
11
  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
12
 
11
13
  If dependency installation or resolution verification fails, stop before running project scripts. Review and retain package manifest and lockfile changes with the source migration.
12
14
 
15
+ If pnpm's `minimumReleaseAge` blocks a newly published target prerelease, do not disable or weaken the repository-wide supply-chain policy. Confirm the selected target and registry provenance, then add only the exact `package@version` rows required under `minimumReleaseAgeExclude`, including transitive DSH packages reported by pnpm. Review this workspace-policy change alongside the lockfile and report it explicitly.
16
+
13
17
  ## Runtime
14
18
 
15
19
  Runtime verification:
@@ -24,6 +28,8 @@ The gate also checks the DSH CLI's exact target version, the created profile man
24
28
 
25
29
  Use a built target Harness CLI through `--harness-root`, or pass `--dsh-command` explicitly. The temporary home is deleted only after a successful run unless `--keep-temp` is set.
26
30
 
31
+ The runtime executable must report the exact actual target selected by `--target-version` (or the catalog target when the option is omitted). A later prerelease is not accepted implicitly because that would disconnect the runtime evidence from the reviewed dependency and source target.
32
+
27
33
  ## Behavior
28
34
 
29
35
  Choose focused checks from the plugin contract: render the real UI, invoke each command, exercise settings, verify service availability, repeat activation/deactivation, check listener cleanup, and cover failure recovery. Record exact commands, screenshots, logs, or tests. This gate is deliberately not inferred by dsh-doctor.
package/src/cli.mjs CHANGED
@@ -43,7 +43,8 @@ Options:
43
43
  --fix, --repair legacy confirmed repairs (never removes a plugin)
44
44
  --yes confirm an explicit write or command
45
45
  --from <ref> source DSH ref (default: dsh-v0.1.1-rc.2)
46
- --to <ref> target DSH ref (default: dsh-v0.1.2-alpha.2)
46
+ --to <ref> target DSH ref (default: dsh-v0.1.2-alpha.3)
47
+ --target-version <ver> actual DSH 0.1.2 version for dependency/runtime checks
47
48
  --safe restrict migrate apply to catalog-confirmed exact rewrites
48
49
  --plan-file <path> persist or consume the reviewed migrate apply plan
49
50
  --level <level> static, build, or runtime verification
@@ -85,7 +86,8 @@ const HELP_ZH = `用法:
85
86
  --fix, --repair 旧式确认修复(绝不移除插件)
86
87
  --yes 确认一个明确的写入或命令动作
87
88
  --from <ref> 源 DSH ref(默认:dsh-v0.1.1-rc.2)
88
- --to <ref> 目标 DSH ref(默认:dsh-v0.1.2-alpha.2
89
+ --to <ref> 目标 DSH ref(默认:dsh-v0.1.2-alpha.3
90
+ --target-version <版本> 依赖与 runtime 验证使用的实际 DSH 0.1.2 版本
89
91
  --safe migrate apply 仅执行 catalog 确认的精确改写
90
92
  --plan-file <路径> 保存或读取已审阅的 migrate apply 计划
91
93
  --level <级别> static、build 或 runtime
@@ -146,6 +148,8 @@ function parse(args) {
146
148
  else if (arg.startsWith('--from=')) options.from = optionValue(arg, '--from')
147
149
  else if (arg === '--to') options.to = valueAfter(args, index++, arg)
148
150
  else if (arg.startsWith('--to=')) options.to = optionValue(arg, '--to')
151
+ else if (arg === '--target-version') options.targetVersion = valueAfter(args, index++, arg)
152
+ else if (arg.startsWith('--target-version=')) options.targetVersion = optionValue(arg, '--target-version')
149
153
  else if (arg === '--safe') options.safe = true
150
154
  else if (arg === '--plan-file') options.planFile = valueAfter(args, index++, arg)
151
155
  else if (arg.startsWith('--plan-file=')) options.planFile = optionValue(arg, '--plan-file')
@@ -300,7 +304,7 @@ async function main() {
300
304
  if (!['analyze', 'apply', 'verify'].includes(options.migrateAction)) throw new Error('migrate needs analyze, apply, or verify')
301
305
  if (options.migrateAction === 'apply' && options.safe !== true) throw new Error('migrate apply requires --safe')
302
306
  if (options.migrateAction === 'apply' && options.pluginRoot === undefined) throw new Error('migrate apply requires an explicit plugin root')
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 }
307
+ const migrationOptions = { from: options.from, to: options.to, targetVersion: options.targetVersion, harnessRoot: options.harnessRoot, dshCommand: options.dshCommand, level: options.level, yes: options.yes, safe: options.safe, keepTemp: options.keepTemp, planFile: options.planFile, install: options.install }
304
308
  if (options.migrateAction === 'verify') {
305
309
  const { formatVerification, verifyMigration } = await import('./migrate-verify.mjs')
306
310
  const result = verifyMigration(options.pluginRoot, migrationOptions)
@@ -214,7 +214,8 @@ export function verifyMigration(pluginRoot = process.cwd(), options = {}) {
214
214
  const [versionCommand, versionArgs] = dshInvocation(dsh, ['--version'])
215
215
  const versionRun = commandResult(versionCommand, versionArgs, { cwd: root, env })
216
216
  const actualDshVersion = versionRun.stdout.trim()
217
- const versionPassed = versionRun.passed && actualDshVersion === analysis.migration.to.version
217
+ const expectedDshVersion = analysis.migration.actualTarget.version
218
+ const versionPassed = versionRun.passed && actualDshVersion === expectedDshVersion
218
219
  const pack = commandResult(manager.command, manager.pack(packDir), { cwd: root })
219
220
  const tarball = tarballFromPack(pack, packDir)
220
221
  const runtimeCommands = []
@@ -236,7 +237,7 @@ export function verifyMigration(pluginRoot = process.cwd(), options = {}) {
236
237
  result.stages.push({
237
238
  name: 'runtime',
238
239
  passed: runtimePassed,
239
- dshVersion: { expected: analysis.migration.to.version, actual: actualDshVersion, passed: versionPassed, command: commandEvidence(versionRun) },
240
+ dshVersion: { expected: expectedDshVersion, catalog: analysis.migration.to.version, actual: actualDshVersion, passed: versionPassed, command: commandEvidence(versionRun) },
240
241
  pack: commandEvidence(pack),
241
242
  tarball,
242
243
  commands: runtimeCommands.map(commandEvidence),
package/src/migrate.mjs CHANGED
@@ -80,12 +80,17 @@ function moduleLiteral(node) {
80
80
 
81
81
  function namedImports(node) {
82
82
  const bindings = node.importClause?.namedBindings
83
- if (!bindings || !ts.isNamedImports(bindings)) return []
84
- return bindings.elements.map(item => ({
83
+ if (bindings && ts.isNamedImports(bindings)) return bindings.elements.map(item => ({
85
84
  imported: item.propertyName?.text ?? item.name.text,
86
85
  local: item.name.text,
87
86
  typeOnly: node.importClause.isTypeOnly || item.isTypeOnly,
88
87
  }))
88
+ if (ts.isExportDeclaration(node) && node.exportClause && ts.isNamedExports(node.exportClause)) return node.exportClause.elements.map(item => ({
89
+ imported: item.propertyName?.text ?? item.name.text,
90
+ local: item.name.text,
91
+ typeOnly: node.isTypeOnly || item.isTypeOnly,
92
+ }))
93
+ return []
89
94
  }
90
95
 
91
96
  function quoteModule(text) {
@@ -117,10 +122,11 @@ function analyzeFile(file, pluginRoot, catalog, origin) {
117
122
  handledLiterals.add(literal)
118
123
  const moduleName = literal.text
119
124
  const rootName = packageRoot(moduleName)
120
- if (removed.has(rootName)) {
125
+ const moduleRemoved = removed.has(rootName)
126
+ const symbolRules = catalog.symbols.modules[moduleName]
127
+ if (moduleRemoved || symbolRules !== undefined) {
121
128
  const where = location(source, literal, pluginRoot)
122
- const symbolRules = catalog.symbols.modules[moduleName]
123
- const specs = ts.isImportDeclaration(node) ? namedImports(node) : []
129
+ const specs = namedImports(node)
124
130
  const exact = []
125
131
  const remaining = []
126
132
  for (const spec of specs) {
@@ -128,7 +134,7 @@ function analyzeFile(file, pluginRoot, catalog, origin) {
128
134
  if (rule?.confidence === 'exact') {
129
135
  exact.push({ ...spec, imported: rule.toSymbol, toModule: rule.toModule, fromSymbol: spec.imported, reason: rule.reason })
130
136
  findings.push({ code: 'MIG_MOVED_SYMBOL', severity: 'error', message: `${spec.imported} moved to ${rule.toModule}`, location: where, evidence: { module: moduleName, symbol: spec.imported, targetModule: rule.toModule, replacement: rule.toSymbol }, autoFix: 'safe' })
131
- } else {
137
+ } else if (rule?.confidence === 'semantic' || moduleRemoved) {
132
138
  remaining.push(spec)
133
139
  const semantic = rule?.confidence === 'semantic'
134
140
  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' })
@@ -140,7 +146,7 @@ function analyzeFile(file, pluginRoot, catalog, origin) {
140
146
  ...(rule?.toSymbol ? { targetSymbol: rule.toSymbol } : {}),
141
147
  reason: rule?.reason ?? 'No exact replacement is known.',
142
148
  })
143
- }
149
+ } else remaining.push(spec)
144
150
  }
145
151
  const declarationText = ts.isImportDeclaration(node) ? text.slice(node.getStart(source), node.getEnd()) : ''
146
152
  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)
@@ -160,7 +166,7 @@ function analyzeFile(file, pluginRoot, catalog, origin) {
160
166
  const generated = [...groups].map(([target, items]) => importText(target, items, node.importClause.isTypeOnly))
161
167
  if (remaining.length > 0) generated.unshift(importText(moduleName, remaining, node.importClause.isTypeOnly))
162
168
  replacements.push({ start: node.getStart(source), end: node.getEnd(), next: generated.join('\n'), exact })
163
- } else if (specs.length === 0 || exact.length > 0) {
169
+ } else if (moduleRemoved && (specs.length === 0 || exact.length > 0)) {
164
170
  for (const finding of findings) if (finding.code === 'MIG_MOVED_SYMBOL' && finding.location.line === where.line && exact.some(item => item.fromSymbol === finding.evidence.symbol)) finding.autoFix = 'none'
165
171
  findings.push({ code: 'MIG_REMOVED_PACKAGE_REFERENCE', severity: 'error', message: `${moduleName} was removed and this reference cannot be rewritten safely`, location: where, evidence: { module: moduleName, kind: ts.SyntaxKind[node.kind] }, autoFix: 'none' })
166
172
  unresolved.push({ file: where.file, package: rootName, module: moduleName, reason: 'Default, namespace, side-effect, export, require, dynamic import, or module augmentation reference.' })
@@ -189,12 +195,28 @@ function readManifest(pluginRoot) {
189
195
  return { file, value, text }
190
196
  }
191
197
 
192
- function targetVersion(name, catalog) {
198
+ function actualTargetVersion(catalog, requested) {
199
+ if (requested === undefined) return catalog.manifest.to.version
200
+ const version = semver.valid(requested)
201
+ if (version === null) throw new Error(`--target-version must be an exact semantic version, received ${requested}`)
202
+ const catalogVersion = semver.parse(catalog.manifest.to.version)
203
+ const target = semver.parse(version)
204
+ if (catalogVersion === null || target === null || target.major !== catalogVersion.major || target.minor !== catalogVersion.minor || target.patch !== catalogVersion.patch) {
205
+ throw new Error(`--target-version ${requested} is outside the catalog's ${catalogVersion?.major}.${catalogVersion?.minor}.${catalogVersion?.patch} release line`)
206
+ }
207
+ if (semver.lt(target, catalogVersion)) {
208
+ throw new Error(`--target-version ${requested} predates the catalog target ${catalog.manifest.to.version}`)
209
+ }
210
+ return version
211
+ }
212
+
213
+ function targetVersion(name, catalog, actualTarget) {
214
+ if (name.startsWith('@deepseek-ai/dsh-')) return actualTarget
193
215
  if (catalog.packages.targetVersions?.[name] !== undefined) return catalog.packages.targetVersions[name]
194
- return name.startsWith('@deepseek-ai/dsh-') ? catalog.manifest.to.version : undefined
216
+ return undefined
195
217
  }
196
218
 
197
- function manifestFindings(manifest, catalog) {
219
+ function manifestFindings(manifest, catalog, actualTarget) {
198
220
  const findings = []
199
221
  for (const field of DEPENDENCY_FIELDS) {
200
222
  const dependencyMap = manifest.value[field]
@@ -207,7 +229,7 @@ function manifestFindings(manifest, catalog) {
207
229
  findings.push({ code: 'MIG_REMOVED_PACKAGE_REFERENCE', severity: 'error', message: `${field}.${name} targets a removed package`, location: { file: 'package.json' }, evidence: { field, package: name, range }, autoFix: 'conditional' })
208
230
  continue
209
231
  }
210
- const target = targetVersion(name, catalog)
232
+ const target = targetVersion(name, catalog, actualTarget)
211
233
  if (target === undefined) continue
212
234
  const validRange = typeof range === 'string' ? semver.validRange(range) : null
213
235
  if (validRange === null) findings.push({ code: 'MIG_INVALID_DEPENDENCY_RANGE', severity: 'error', message: `${field}.${name} has a non-registry range that cannot prove target compatibility`, location: { file: 'package.json' }, evidence: { field, package: name, range, target }, autoFix: field === 'devDependencies' ? 'safe' : 'none' })
@@ -307,7 +329,7 @@ function patchTargetFindings(pluginRoot, manifest, harness) {
307
329
  return findings
308
330
  }
309
331
 
310
- function planManifest(manifest, sourceResults, artifactResults, opaqueReferences, catalog) {
332
+ function planManifest(manifest, sourceResults, artifactResults, opaqueReferences, catalog, actualTarget) {
311
333
  const next = structuredClone(manifest.value)
312
334
  const exactTargets = sourceResults.flatMap(result => result.exactTargets)
313
335
  const sourceReferences = new Set(sourceResults.flatMap(result => result.remainingPackages))
@@ -323,14 +345,14 @@ function planManifest(manifest, sourceResults, artifactResults, opaqueReferences
323
345
  }
324
346
  if (field === 'devDependencies') {
325
347
  for (const [name, range] of Object.entries(deps)) {
326
- const target = targetVersion(name, catalog)
348
+ const target = targetVersion(name, catalog, actualTarget)
327
349
  if (target !== undefined && !catalog.packages.removed.includes(name) && (semver.validRange(range) === null || !semver.satisfies(target, range))) deps[name] = target
328
350
  }
329
351
  }
330
352
  }
331
353
  for (const move of new Map(exactTargets.map(item => [`${item.relationship}\0${item.toPackage}`, item])).values()) {
332
354
  const fields = catalog.packages.dependencyPolicies?.[move.toPackage]?.[move.relationship]
333
- const version = targetVersion(move.toPackage, catalog)
355
+ const version = targetVersion(move.toPackage, catalog, actualTarget)
334
356
  if (!Array.isArray(fields) || version === undefined) continue
335
357
  for (const field of fields) {
336
358
  if (!DEPENDENCY_FIELDS.includes(field)) throw new Error(`migration catalog has an invalid dependency field ${field}`)
@@ -369,7 +391,8 @@ function analysisInputs(root) {
369
391
  export function analyzeMigration(pluginRoot = process.cwd(), options = {}) {
370
392
  const root = resolve(pluginRoot)
371
393
  if (!statSync(root).isDirectory()) throw new Error(`plugin root is not a directory: ${root}`)
372
- const catalog = loadMigration(options.from ?? 'dsh-v0.1.1-rc.2', options.to ?? 'dsh-v0.1.2-alpha.2')
394
+ const catalog = loadMigration(options.from ?? 'dsh-v0.1.1-rc.2', options.to ?? 'dsh-v0.1.2-alpha.3')
395
+ const actualTarget = actualTargetVersion(catalog, options.targetVersion)
373
396
  const harness = verifyHarnessCheckout(catalog, options.harnessRoot)
374
397
  const manifest = readManifest(root)
375
398
  const sourceFiles = walk(root, SOURCE_DIRS_TO_SKIP).filter(file => !isTopLevelArtifact(root, file))
@@ -378,10 +401,10 @@ export function analyzeMigration(pluginRoot = process.cwd(), options = {}) {
378
401
  const opaqueReferences = opaqueReferenceFindings(root, sourceFiles, manifest, catalog)
379
402
  const sourceRemoved = sources.some(item => item.findings.some(finding => ['MIG_REMOVED_PACKAGE_REFERENCE', 'MIG_SEMANTIC_API_CHANGE', 'MIG_MOVED_SYMBOL'].includes(finding.code)))
380
403
  const artifactRemoved = artifacts.some(item => item.findings.some(finding => ['MIG_REMOVED_PACKAGE_REFERENCE', 'MIG_SEMANTIC_API_CHANGE', 'MIG_MOVED_SYMBOL'].includes(finding.code)))
381
- const findings = [...manifestFindings(manifest, catalog), ...patchTargetFindings(root, manifest, harness), ...sources.flatMap(item => item.findings), ...opaqueReferences.findings, ...artifacts.flatMap(item => item.findings)]
404
+ const findings = [...manifestFindings(manifest, catalog, actualTarget), ...patchTargetFindings(root, manifest, harness), ...sources.flatMap(item => item.findings), ...opaqueReferences.findings, ...artifacts.flatMap(item => item.findings)]
382
405
  if (!sourceRemoved && artifactRemoved) findings.push({ code: 'MIG_SOURCE_ARTIFACT_DRIFT', severity: 'error', message: 'built artifacts still reference removed APIs although source files do not', location: { file: '.' }, evidence: { artifactDirectories: ARTIFACT_DIRS }, autoFix: 'none' })
383
406
  findings.push({ code: 'MIG_RUNTIME_VERIFICATION_REQUIRED', severity: 'info', message: 'static analysis cannot prove activation and lifecycle behavior; run migrate verify --level runtime', location: { file: '.' }, evidence: {}, autoFix: 'none' })
384
- const manifestPlan = planManifest(manifest, sources, artifacts, opaqueReferences.packages, catalog)
407
+ const manifestPlan = planManifest(manifest, sources, artifacts, opaqueReferences.packages, catalog, actualTarget)
385
408
  const changed = [...sources.filter(item => item.changed), ...(manifestPlan.changed ? [manifestPlan] : [])]
386
409
  const safeEdits = changed.map(item => ({
387
410
  file: relativePath(root, item.file),
@@ -393,14 +416,21 @@ export function analyzeMigration(pluginRoot = process.cwd(), options = {}) {
393
416
  return {
394
417
  schemaVersion: 1,
395
418
  command: 'migrate analyze',
396
- migration: { id: catalog.manifest.id, from: catalog.manifest.from, to: catalog.manifest.to, references: catalog.manifest.references ?? {}, harness },
419
+ migration: {
420
+ id: catalog.manifest.id,
421
+ from: catalog.manifest.from,
422
+ to: catalog.manifest.to,
423
+ actualTarget: { version: actualTarget, catalogVersion: catalog.manifest.to.version, catalogExact: actualTarget === catalog.manifest.to.version },
424
+ references: catalog.manifest.references ?? {},
425
+ harness,
426
+ },
397
427
  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' },
398
428
  summary: summarize(findings, safeEdits, unresolved),
399
429
  findings,
400
430
  safeEdits,
401
431
  semanticTasks: unresolved,
402
432
  sourceInvestigation: {
403
- required: harness.exact !== true || unresolved.length > 0,
433
+ required: harness.exact !== true || unresolved.length > 0 || actualTarget !== catalog.manifest.to.version,
404
434
  targetRef: catalog.manifest.to.ref,
405
435
  catalogReferences: catalog.manifest.references ?? {},
406
436
  semanticTargets: [...new Set(unresolved.map(item => item.targetModule).filter(Boolean))],
@@ -429,6 +459,7 @@ export function createMigrationPlan(report) {
429
459
  id: report.migration.id,
430
460
  from: report.migration.from.ref,
431
461
  to: report.migration.to.ref,
462
+ targetVersion: report.migration.actualTarget.version,
432
463
  harness: {
433
464
  status: report.migration.harness.status,
434
465
  fromCommit: report.migration.harness.fromCommit,
@@ -453,7 +484,7 @@ function readMigrationPlan(file) {
453
484
  function assertMigrationPlan(report, plan) {
454
485
  const current = createMigrationPlan(report)
455
486
  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')
487
+ if (plan.migration.id !== current.migration.id || plan.migration.from !== current.migration.from || plan.migration.to !== current.migration.to || plan.migration.targetVersion !== current.migration.targetVersion) throw new Error('migration plan targets a different catalog or actual target version')
457
488
  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
489
  return current
459
490
  }
@@ -478,7 +509,7 @@ export function applyMigration(report, options = {}) {
478
509
  if (currentExists !== item.snapshot.exists || sha256(current) !== item.snapshot.hash) throw new Error(`${item.snapshot.file} changed after the preview; diagnose again before applying`)
479
510
  }
480
511
  const writes = report._plan.map(item => ({ ...atomicWrite(item.snapshot, item.nextText), beforeHash: item.snapshot.hash, afterHash: sha256(item.nextText) }))
481
- const verification = analyzeMigration(report.plugin.root, { from: report.migration.from.ref, to: report.migration.to.ref, harnessRoot: report.migration.harness.root })
512
+ const verification = analyzeMigration(report.plugin.root, { from: report.migration.from.ref, to: report.migration.to.ref, targetVersion: report.migration.actualTarget.version, harnessRoot: report.migration.harness.root })
482
513
  return { mode: 'applied', plan: { file: planFile, id: verifiedPlan.planId }, writes, report: publicMigrationReport(verification) }
483
514
  }
484
515