dsh-composition-doctor 0.3.0 → 0.4.0
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/CHANGELOG.md +22 -9
- package/README.md +125 -26
- package/README.zh.md +125 -26
- package/dist/cli/main.js +131 -4
- package/dist/client/report-view.js +43 -0
- package/dist/client.js +80 -1
- package/dist/core/bundle-inventory.js +9 -6
- package/dist/core/composition-provenance.js +6 -1
- package/dist/core/dsh-dump-parser.js +1 -1
- package/dist/core/failure-explainer.js +205 -0
- package/dist/core/failure-profile.js +110 -0
- package/dist/core/package-provenance.js +4 -0
- package/dist/core/preflight/npm-artifact.js +18 -2
- package/dist/core/rules.js +9 -2
- package/dist/core/snapshot.js +1 -1
- package/dist/reports/failure.js +23 -0
- package/dist/reports/markdown.js +3 -1
- package/dist/reports/schema.js +29 -0
- package/package.json +67 -66
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,25 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 0.
|
|
4
|
-
|
|
5
|
-
- Add
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.4.0 — Failure Explainer (unreleased RC)
|
|
4
|
+
|
|
5
|
+
- Add `diagnose` and the small `check` alias: human-first explanations connect supported symptoms to entities, independent source paths, evidence boundaries and manual next steps.
|
|
6
|
+
- Add a pure failure matcher/explainer shared with additive report/Web data. Default diagnosis inspects selected static metadata without running DSH, importing plugins or accessing the network. Public composed evidence remains opt-in.
|
|
7
|
+
- Support duplicate loader declarations, unavailable bundles, missing/invalid patches, unmatched targets, DSH-family peer/version mismatches and config replacement/override explanations. Normal updates and patch provenance chains are never treated as duplicate insertions.
|
|
8
|
+
- Accept bounded, untrusted file/stdin error logs; never store raw lines or treat log-reported versions as trusted runtime facts. Refuse secret/workspace/session/chat files and profile-mutating report destinations.
|
|
9
|
+
- Add minimized public failure regressions, synchronized bilingual onboarding, two demos, release/discussion drafts and a 25-second terminal recording script.
|
|
10
|
+
- Recheck the current npm DSH release (`0.2.0-rc.2`) with the public composition harness. Extend CI to Windows/Ubuntu and Node 20/22/24, including packed fresh-install smoke. Runtime execution remains not-run.
|
|
11
|
+
- Correct snapshot producer version metadata and preserve graph identities for same-layer duplicate declarations.
|
|
12
|
+
- Fix the installed Unix CLI launcher and an identical-artifact concurrent cache publication race exposed by Windows RC verification.
|
|
13
|
+
|
|
14
|
+
No npm publish, tag, GitHub Release or Discussion has been created.
|
|
15
|
+
|
|
16
|
+
## 0.3.0
|
|
17
|
+
|
|
18
|
+
- Add read-only `why row` and bounded `impact bundle` explanations using shared, versioned composition facts.
|
|
19
|
+
- Extend reports and Web Settings with a redacted bundle/source/layer/row provenance graph; older reports remain readable without the graph.
|
|
20
|
+
- Keep field ownership, removed config keys, route/slot ownership, runtime hook ownership, arbitrary dependencies, and possible dependents unknown unless directly observed.
|
|
21
|
+
|
|
22
|
+
## 0.2.2
|
|
10
23
|
|
|
11
24
|
- Fixed the packaged `dsh-doctor` CLI entrypoint when pnpm's Windows shim supplies a non-canonical path.
|
|
12
25
|
- Added a built-entrypoint regression test covering `--version` and `--help`.
|
package/README.md
CHANGED
|
@@ -6,24 +6,131 @@
|
|
|
6
6
|
|
|
7
7
|
English | [中文](README.zh.md)
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
> **DSH failed after installing or upgrading a plugin?**
|
|
10
|
+
>
|
|
11
|
+
> Find which bundle, profile layer, or override is involved—and what the evidence actually proves.
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
```sh
|
|
14
|
+
dsh-doctor diagnose --profile ./web --log dsh-error.log
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
Duplicate loader entry id: session-cleaner
|
|
19
|
+
reported-by-log; evidence: static
|
|
20
|
+
|
|
21
|
+
Cause: session-cleaner is introduced by 2 separate loader declarations.
|
|
22
|
+
|
|
23
|
+
Path 1: bundle session-cleaner → <PROFILE>/node_modules/session-cleaner/cordis.patch.yml
|
|
24
|
+
layer: dsh.profile.bundles[0]: session-cleaner → row: session-cleaner
|
|
25
|
+
Path 2: cordis.patch.yml
|
|
26
|
+
layer: cordis.patch.yml → row: session-cleaner
|
|
27
|
+
|
|
28
|
+
Unknown: runtime not observed / not-run; final composition unknown.
|
|
29
|
+
Doctor did not modify your profile.
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Excerpt from a minimized [public duplicate-loading case](https://github.com/deepseek-ai/deepseek-harness/discussions/2889). Static declarations expose the conflicting paths; they do not prove a runtime crash. [Full demos and sources](docs/failure-cases.md) · [25-second terminal demo](scripts/failure-demo.mjs)
|
|
12
33
|
|
|
13
|
-
|
|
34
|
+
**Explain failures · Trace composition provenance · Preview upgrade risks**
|
|
35
|
+
|
|
36
|
+
**Read-only · Offline by default · No automatic fixes or installs**
|
|
37
|
+
|
|
38
|
+
For [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (`dsh`). One command connects a supported failure to its entity, observable source paths and a manual next step. Missing evidence stays unknown.
|
|
39
|
+
|
|
40
|
+
## Quick start
|
|
41
|
+
|
|
42
|
+
**0.4.0 is a local release candidate, not yet published.** The public 0.3.0 package does not have `diagnose`. Build this checkout and install the reviewed tarball locally:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
pnpm install --frozen-lockfile
|
|
46
|
+
pnpm build
|
|
47
|
+
pnpm pack
|
|
48
|
+
pnpm add -g ./dsh-composition-doctor-0.4.0.tgz --ignore-scripts
|
|
49
|
+
dsh-doctor check --profile ./web
|
|
50
|
+
dsh-doctor diagnose --profile ./web --log dsh-error.log
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`--profile` is an explicit directory, not a DSH profile nickname. For the standard Web profile, use `~/.dsh/profiles/web` (PowerShell: `$HOME/.dsh/profiles/web`). CLI use needs no running Web UI or plugin installation. To use the Web Explorer, add the reviewed local tarball through DSH's plugin command, then opt in to publishing reports to its configured Doctor report directory.
|
|
54
|
+
|
|
55
|
+
Pipes and structured output work too:
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
dsh web 2>&1 | dsh-doctor diagnose --profile ./web
|
|
59
|
+
dsh-doctor diagnose --profile ./web --log - --format json
|
|
60
|
+
dsh-doctor diagnose --profile ./web --format markdown
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Default `diagnose`/`check` inspect static metadata without starting DSH, importing plugin code, running lifecycle scripts or accessing the network. `check` is a small alias for the same reader and requires the same explicit profile. JSON returns the versioned explanation result; `--output <dir>` or `--report-dir <dir>` writes a compatible analysis report with `failureExplanation` and `compositionFacts` plus Markdown. Output directories must be outside the profile. No report is written by default.
|
|
64
|
+
|
|
65
|
+
Then inspect a specific source:
|
|
66
|
+
|
|
67
|
+
```powershell
|
|
68
|
+
dsh-doctor why row tool-bash --profile C:\path\to\profile
|
|
69
|
+
dsh-doctor impact bundle @example/dsh-bundle --profile C:\path\to\profile
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`why` explains one observed row and its source chain. `impact bundle` lists rows and diagnostics directly associated with that bundle source.
|
|
73
|
+
|
|
74
|
+
```mermaid
|
|
75
|
+
flowchart LR
|
|
76
|
+
B["Bundle<br/>direct association"] --> S["Source"]
|
|
77
|
+
S -->|"introduced / patched-by"| R["Row"]
|
|
78
|
+
L["Layer"] -->|"contains"| R
|
|
79
|
+
R -->|"diagnosed-by"| D["Diagnostic"]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
These links appear only when the report has supporting facts; missing ownership is left unknown.
|
|
83
|
+
|
|
84
|
+
## Real failure examples
|
|
85
|
+
|
|
86
|
+
1. **A bundle plus a hand-written insertion.** [DSH Discussion #2889](https://github.com/deepseek-ai/deepseek-harness/discussions/2889) reports a plugin-management change that promotes dependencies into the bundle list while manual rows remain. Doctor's minimized regression connects `session-cleaner` to both insertion paths. Review the bundle declaration and manual patch together; a patch update is never counted as a second insertion.
|
|
87
|
+
2. **A package without its declared patch file.** [DSH Discussion #6539](https://github.com/deepseek-ai/deepseek-harness/discussions/6539) includes a report of the published `dsh-cad` package lacking its YAML overlay. The minimized fixture produces:
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
Bundle patch is unavailable: dsh-cad
|
|
91
|
+
reported-by-log; evidence: static
|
|
92
|
+
|
|
93
|
+
Path 1: bundle dsh-cad → <PROFILE>/node_modules/dsh-cad/package.json
|
|
94
|
+
|
|
95
|
+
Next: Review the bundle manifest → dsh.bundle.patch and packaged YAML file.
|
|
96
|
+
Doctor did not modify your profile.
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
These are sanitized reconstructions of public failure patterns, not copies of users' profiles or a replay of their runtime. The suite also verifies that the same logs with no matching metadata remain `unknown`.
|
|
100
|
+
|
|
101
|
+
## Supported explanations
|
|
102
|
+
|
|
103
|
+
| Pattern | Observable basis | Boundary |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| Duplicate loader id | Separate `insert`/base-row declarations, or duplicate composed rows | A normal update or patched source chain is not another insertion; no runtime rejection is inferred. |
|
|
106
|
+
| Bundle unavailable | Declared bundle plus bounded local resolution finding | Built-in installation bundles and external links may resolve elsewhere. |
|
|
107
|
+
| Missing/invalid bundle patch | Manifest plus missing, invalid or disallowed declared YAML metadata | No file or command suggested by a log is followed. |
|
|
108
|
+
| Patch target missing | Target declaration with no local insertion, or a public dump finding | Home/CLI/built-in layers may still supply it. |
|
|
109
|
+
| Peer/version mismatch | Installed DSH-family peer range and independently selected `--dsh-version <exact>`; Node engine uses the actual Doctor process | Log-reported versions are not runtime facts; no compatible release is guessed. |
|
|
110
|
+
| Config replacement / override | Config patch declaration or composed provenance/replacement fact | Removed effective keys and field ownership stay unknown unless observed. |
|
|
111
|
+
|
|
112
|
+
Upgrade ownership changes use existing `snapshot`/`diff` and `preflight`; arbitrary route/slot/hook root causes are outside this version's failure patterns. `diagnose --composed` opts in to the existing isolated public dump adapter; it never installs packages, starts plugin runtime, or upgrades static declarations to composed evidence. Copied-profile/provider coverage can be incomplete, and runtime remains `not-observed`.
|
|
113
|
+
|
|
114
|
+
Logs are limited to 1 MiB; explicit files must be `.log`, `.txt` or `.out`. Secret, workspace, session and chat paths are refused. At most 32 log signatures and 50 metadata explanations are retained; truncation is indicated in JSON. Human output shows the first three explanations. Exit 0 means a report was produced (including `unknown`/`no-match`), not that DSH is healthy; operational failures return 1 and invalid arguments return 2.
|
|
115
|
+
|
|
116
|
+
[Commands](#what-it-explains) · [Evidence boundaries](#reports-and-evidence-boundaries) · [Safety & privacy](#safety-and-privacy) · [Compatibility](#support-and-limitations) · [Development](#development)
|
|
117
|
+
|
|
118
|
+
## What it explains
|
|
14
119
|
|
|
15
120
|
| Command | Purpose |
|
|
16
121
|
|---|---|
|
|
122
|
+
| `dsh-doctor diagnose` | Explain supported failures from selected metadata, optionally focused by an untrusted error log. Human-readable by default; JSON/Markdown are available. |
|
|
123
|
+
| `dsh-doctor check` | Inspect the explicitly selected profile with the same bounded reader and show how to diagnose a symptom. |
|
|
17
124
|
| `dsh-doctor scan` | Detect duplicate Cordis rows, hook-order risks, UI slot/route ownership conflicts, bundle overrides, peer/platform mismatches, and profile drift. |
|
|
18
125
|
| `dsh-doctor snapshot` | Create a redacted, comparable profile snapshot with lockfile hashes. |
|
|
19
126
|
| `dsh-doctor diff` | Summarize added/removed/upgraded plugins, rows, hooks, UI claims, peers, and platforms. |
|
|
20
|
-
| `dsh-doctor preflight` | Rehearse a target DSH upgrade in an isolated temporary profile. |
|
|
21
|
-
| `dsh-doctor why row <id>` | Explain one row from an explicitly selected profile using observed source/layer facts. |
|
|
22
|
-
| `dsh-doctor impact bundle <name>` | List rows and diagnostics directly associated with an observed bundle source. |
|
|
23
|
-
|
|
24
|
-
The `why` and `impact` commands require `--profile <dir>`. Reports include an optional, versioned, redacted `compositionFacts` section shared by these commands and the Web Composition Explorer. The Web Settings page remains display/export only: it reads the latest local report, shows diagnostic and composition graphs, and exports JSON/Markdown. It has no repair, install, or uninstall action.
|
|
25
|
-
|
|
26
|
-
Composition facts describe the structure returned by the public `--dump-config` command or static declarations when that is unavailable. `introduced` and `patched-by` edges describe the source chain recorded by DSH; they do not prove per-field ownership. If earlier config keys are not available, removed keys and field owners remain unknown. Route/slot ownership, runtime hook ownership, arbitrary dependencies, and possible dependents are reported as not observed/not modelled.
|
|
127
|
+
| `dsh-doctor preflight` | Rehearse a target DSH upgrade in an isolated temporary profile. |
|
|
128
|
+
| `dsh-doctor why row <id>` | Explain one row from an explicitly selected profile using observed source/layer facts. |
|
|
129
|
+
| `dsh-doctor impact bundle <name>` | List rows and diagnostics directly associated with an observed bundle source. |
|
|
130
|
+
|
|
131
|
+
The `why` and `impact` commands require `--profile <dir>`. Reports include an optional, versioned, redacted `compositionFacts` section shared by these commands and the Web Composition Explorer. The Web Settings page remains display/export only: it reads the latest local report, shows diagnostic and composition graphs, and exports JSON/Markdown. It has no repair, install, or uninstall action.
|
|
132
|
+
|
|
133
|
+
Composition facts describe the structure returned by the public `--dump-config` command or static declarations when that is unavailable. `introduced` and `patched-by` edges describe the source chain recorded by DSH; they do not prove per-field ownership. If earlier config keys are not available, removed keys and field owners remain unknown. Route/slot ownership, runtime hook ownership, arbitrary dependencies, and possible dependents are reported as not observed/not modelled.
|
|
27
134
|
|
|
28
135
|
## Reports and evidence boundaries
|
|
29
136
|
|
|
@@ -35,29 +142,19 @@ Reports declare `evidenceMode`: `static` means allow-listed manifest/patch/packa
|
|
|
35
142
|
|
|
36
143
|
`scan --fail-on never|info|warning|error` controls the scan exit code. The default is `never`: warnings and errors remain in the report without changing the exit code. `info` fails on any diagnostic, `warning` fails on warnings or errors, and `error` fails only on errors. Malformed arguments return exit code 2; operational failures return 1.
|
|
37
144
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
```powershell
|
|
41
|
-
npm install -g dsh-composition-doctor
|
|
42
|
-
npx @deepseek-ai/dsh plugin --profile web add dsh-composition-doctor
|
|
43
|
-
dsh-doctor --version
|
|
44
|
-
```
|
|
145
|
+
After changing a profile, restart the Web UI (`npx @deepseek-ai/dsh web`) before scanning it again.
|
|
45
146
|
|
|
46
|
-
|
|
147
|
+
Each finding is `info`, `warning`, or `error` and includes evidence, explanation, and the smallest remediation. `runtimeSmoke.status=not-run` is not a runtime PASS; `artifact-unavailable` is not `incompatible`; and a warning is not a confirmed failure.
|
|
47
148
|
|
|
48
|
-
##
|
|
149
|
+
## More commands
|
|
49
150
|
|
|
50
151
|
```powershell
|
|
51
152
|
dsh-doctor scan --profile C:\path\to\profile --format both --output .\reports\profile --publish
|
|
52
153
|
dsh-doctor scan --profile C:\path\to\profile --format both --output .\reports\archive --report-dir C:\safe\doctor-reports
|
|
53
154
|
dsh-doctor snapshot --profile C:\path\to\profile --output .\reports\before.json
|
|
54
155
|
dsh-doctor diff --before .\reports\before.json --after .\reports\after.json --format both
|
|
55
|
-
dsh-doctor preflight --profile C:\path\to\profile --target-dsh 0.1.5-rc.2
|
|
56
|
-
|
|
57
|
-
dsh-doctor impact bundle @example/dsh-bundle --profile C:\path\to\profile
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
Each finding is `info`, `warning`, or `error` and includes evidence, explanation, and the smallest remediation. `runtimeSmoke.status=not-run` is not a runtime PASS; `artifact-unavailable` is not `incompatible`; and a warning is not a confirmed failure.
|
|
156
|
+
dsh-doctor preflight --profile C:\path\to\profile --target-dsh 0.1.5-rc.2
|
|
157
|
+
```
|
|
61
158
|
|
|
62
159
|
## Safety and privacy
|
|
63
160
|
|
|
@@ -65,7 +162,7 @@ Default operations are read-only or isolated under the OS temporary directory. T
|
|
|
65
162
|
|
|
66
163
|
## Support and limitations
|
|
67
164
|
|
|
68
|
-
|
|
165
|
+
On 2026-10-03 the npm `latest` release was checked as `@deepseek-ai/dsh@0.2.0-rc.2`; its public `--version`/`--dump-config` harness was rerun on Windows and Ubuntu 24.04 (WSL) + Node.js 24.19.0. Doctor's full RC gates and packed fresh-install smoke passed locally on Windows/Node 24 and Ubuntu/Node 20.19.5 and 24.19.0. On 2026-10-04, [Hosted CI passed all six Windows/Ubuntu × Node 20/22/24 jobs](https://github.com/lemonxiny55/dsh-composition-doctor/actions/runs/37187050083), including packed fresh-install smoke; the current DSH public harness passed on both Node 24 jobs. Older `0.1.5-rc.1`/`rc.2` goldens are historical evidence; their real-artifact tests skip when artifacts are unavailable. `0.2.1-alpha.1` is expected-compatible/experimental, not verified. Doctor supports Node.js `>=20`. This does not claim that every third-party plugin works, or that plugin runtime/UI registration was observed. [Compatibility detail](docs/compatibility.md) · [RC verification record](docs/release-evidence/0.4.0.md).
|
|
69
166
|
|
|
70
167
|
## Development
|
|
71
168
|
|
|
@@ -73,6 +170,8 @@ The real public CLI harness verifies `@deepseek-ai/dsh@0.1.5-rc.1` and `@deepsee
|
|
|
73
170
|
pnpm test
|
|
74
171
|
pnpm typecheck
|
|
75
172
|
pnpm build
|
|
173
|
+
pnpm pack
|
|
174
|
+
pnpm smoke:pack ./dsh-composition-doctor-0.4.0.tgz
|
|
76
175
|
```
|
|
77
176
|
|
|
78
177
|
For checkout-only development, run the CLI with `node dist/cli/main.js` after building.
|
package/README.zh.md
CHANGED
|
@@ -6,24 +6,131 @@
|
|
|
6
6
|
|
|
7
7
|
[English](README.md) | 中文
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
> **安装或升级插件后,DSH 启动失败了?**
|
|
10
|
+
>
|
|
11
|
+
> 找出涉及哪个 bundle、profile layer 或 override,并看清现有证据究竟能证明什么。
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
```sh
|
|
14
|
+
dsh-doctor diagnose --profile ./web --log dsh-error.log
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
Duplicate loader entry id: session-cleaner
|
|
19
|
+
reported-by-log; evidence: static
|
|
20
|
+
|
|
21
|
+
Cause: session-cleaner is introduced by 2 separate loader declarations.
|
|
22
|
+
|
|
23
|
+
Path 1: bundle session-cleaner → <PROFILE>/node_modules/session-cleaner/cordis.patch.yml
|
|
24
|
+
layer: dsh.profile.bundles[0]: session-cleaner → row: session-cleaner
|
|
25
|
+
Path 2: cordis.patch.yml
|
|
26
|
+
layer: cordis.patch.yml → row: session-cleaner
|
|
27
|
+
|
|
28
|
+
Unknown: runtime not observed / not-run; final composition unknown.
|
|
29
|
+
Doctor did not modify your profile.
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
来自最小化的[公开重复加载案例](https://github.com/deepseek-ai/deepseek-harness/discussions/2889)的输出摘录。静态声明能指出冲突路径,不能证明 runtime 已崩溃。[完整 Demo 与来源](docs/failure-cases.md) · [25 秒终端 Demo](scripts/failure-demo.mjs)
|
|
12
33
|
|
|
13
|
-
|
|
34
|
+
**解释故障 · 追踪组合来源 · 预检升级风险**
|
|
35
|
+
|
|
36
|
+
**只读 · 默认离线 · 不自动修复或安装**
|
|
37
|
+
|
|
38
|
+
面向 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(`dsh`)。一条命令把已支持的故障关联到实体、可观察的来源路径和人工下一步;没有证据的部分保留 unknown。
|
|
39
|
+
|
|
40
|
+
## 快速开始
|
|
41
|
+
|
|
42
|
+
**0.4.0 目前为本地 RC,尚未发布。** 公开的 0.3.0 没有 `diagnose`。请从本 checkout 构建,并安装审查后的本地 tarball:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
pnpm install --frozen-lockfile
|
|
46
|
+
pnpm build
|
|
47
|
+
pnpm pack
|
|
48
|
+
pnpm add -g ./dsh-composition-doctor-0.4.0.tgz --ignore-scripts
|
|
49
|
+
dsh-doctor check --profile ./web
|
|
50
|
+
dsh-doctor diagnose --profile ./web --log dsh-error.log
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`--profile` 接受明确的目录,而不是 DSH profile 昵称。标准 Web profile 可使用 `~/.dsh/profiles/web`,PowerShell 中使用 `$HOME/.dsh/profiles/web`。CLI 不需要 Web UI 正常启动,也不需要安装 host 插件。需要 Web Explorer 时,通过 DSH plugin 命令添加审查后的本地 tarball,再显式导出到插件配置的 Doctor 报告目录。
|
|
54
|
+
|
|
55
|
+
支持管道和结构化输出:
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
dsh web 2>&1 | dsh-doctor diagnose --profile ./web
|
|
59
|
+
dsh-doctor diagnose --profile ./web --log - --format json
|
|
60
|
+
dsh-doctor diagnose --profile ./web --format markdown
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
默认 `diagnose`/`check` 只读取静态元数据,不启动 DSH、不导入插件代码、不执行 lifecycle script、不访问网络。`check` 是复用相同 reader 的极简别名,同样需要明确指定 profile。JSON 返回版本化的 explanation;`--output <目录>` 或 `--report-dir <目录>` 写入含 `failureExplanation` 和 `compositionFacts` 的兼容分析报告及 Markdown。输出目录必须位于 profile 外;默认不写报告。
|
|
64
|
+
|
|
65
|
+
继续追踪具体来源:
|
|
66
|
+
|
|
67
|
+
```powershell
|
|
68
|
+
dsh-doctor why row tool-bash --profile C:\path\to\profile
|
|
69
|
+
dsh-doctor impact bundle @example/dsh-bundle --profile C:\path\to\profile
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`why` 解释一个可观察 row 的来源链;`impact bundle` 列出有直接 source 关联证据的 rows 和 diagnostics。
|
|
73
|
+
|
|
74
|
+
```mermaid
|
|
75
|
+
flowchart LR
|
|
76
|
+
B["Bundle<br/>直接关联"] --> S["Source"]
|
|
77
|
+
S -->|"introduced / patched-by"| R["Row"]
|
|
78
|
+
L["Layer"] -->|"contains"| R
|
|
79
|
+
R -->|"diagnosed-by"| D["Diagnostic"]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
只有报告中存在支持 facts 时才展示这些关系;没有 ownership 证据的部分仍标为 unknown。
|
|
83
|
+
|
|
84
|
+
## 真实故障案例
|
|
85
|
+
|
|
86
|
+
1. **bundle 与手写 insert 重复加载。** [DSH Discussion #2889](https://github.com/deepseek-ai/deepseek-harness/discussions/2889) 报告了插件管理把依赖加入 bundle 列表,而旧手写 row 仍然存在的情况。最小化回归把 `session-cleaner` 关联到两条 insert 路径。应一起检查 bundle 声明和手写 patch;普通 patch update 不会被算成第二次引入。
|
|
87
|
+
2. **已安装包缺少它声明的 patch。** [DSH Discussion #6539](https://github.com/deepseek-ai/deepseek-harness/discussions/6539) 的评论报告了 npm `dsh-cad` 缺少 YAML overlay。最小化 fixture 的输出摘录:
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
Bundle patch is unavailable: dsh-cad
|
|
91
|
+
reported-by-log; evidence: static
|
|
92
|
+
|
|
93
|
+
Path 1: bundle dsh-cad → <PROFILE>/node_modules/dsh-cad/package.json
|
|
94
|
+
|
|
95
|
+
Next: Review the bundle manifest → dsh.bundle.patch and packaged YAML file.
|
|
96
|
+
Doctor did not modify your profile.
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
这些是公开故障模式的脱敏结构重建,不是用户 profile 原件,也不声称重放了现场 runtime。同一日志缺少匹配元数据时,测试要求返回 `unknown`。
|
|
100
|
+
|
|
101
|
+
## 支持的解释
|
|
102
|
+
|
|
103
|
+
| 模式 | 可观察依据 | 边界 |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| duplicate loader id | 独立 insert/base-row 声明,或 composed tree 中重复 row | 普通 update、patched source chain 不算第二次插入,不推断 runtime 已拒绝。 |
|
|
106
|
+
| bundle 不可解析 | profile 声明及有边界的本地解析 finding | 安装级内置 bundle、外部 link 可能在其他位置可解析。 |
|
|
107
|
+
| bundle patch 缺失/无效 | manifest 与缺失、无效或不允许读取的 YAML | 不跟随日志建议的文件或命令。 |
|
|
108
|
+
| patch target 缺失 | patch target 没有本地 insert,或公开 dump finding | home/CLI/内置 layer 可能仍提供该 row。 |
|
|
109
|
+
| peer/version 不兼容 | 已安装 DSH-family peer range 与独立指定的 `--dsh-version <精确版本>`;Node engine 使用 Doctor 当前进程版本 | 日志中的版本不是 runtime fact,不猜测可用插件版本。 |
|
|
110
|
+
| config replacement/override | config patch 声明、composed provenance/replacement fact | 未观察到的有效键丢失、字段 owner 保持 unknown。 |
|
|
111
|
+
|
|
112
|
+
升级后的 ownership 变化继续通过 `snapshot`/`diff`、`preflight` 检查;任意 route/slot/hook 根因不进入本版 pattern。`diagnose --composed` 显式启用既有的隔离公开 dump adapter,不安装包、不启动插件 runtime、不把静态声明升级为 composed evidence。复制 profile/provider 的覆盖可能不完整,runtime 仍是 `not-observed`。
|
|
113
|
+
|
|
114
|
+
日志限制 1 MiB;显式文件只接受 `.log`、`.txt`、`.out`,拒绝 secret、workspace、session、chat 路径。最多保留 32 个日志 signature 和 50 条元数据解释;JSON 标明截断,人类输出默认显示前三条。退出 0 表示生成了报告,包括 `unknown`/`no-match`,不表示 DSH 健康;执行失败返回 1,参数错误返回 2。
|
|
115
|
+
|
|
116
|
+
[命令能力](#模型可解释什么) · [证据边界](#报告与证据边界) · [安全与隐私](#安全与隐私) · [兼容范围](#支持范围与限制) · [开发](#开发)
|
|
117
|
+
|
|
118
|
+
## 模型可解释什么
|
|
14
119
|
|
|
15
120
|
| 命令 | 作用 |
|
|
16
121
|
|---|---|
|
|
122
|
+
| `dsh-doctor diagnose` | 从明确选择的元数据解释支持的故障,可用不可信日志聚焦。默认人类可读,支持 JSON/Markdown。 |
|
|
123
|
+
| `dsh-doctor check` | 使用同一有边界的 reader 检查明确指定的 profile,并提示如何进一步 diagnose。 |
|
|
17
124
|
| `dsh-doctor scan` | 检测重复 Cordis row、hook 顺序风险、UI slot/route 所有权冲突、bundle 覆盖、peer/platform 不匹配和 profile 漂移。 |
|
|
18
125
|
| `dsh-doctor snapshot` | 生成脱敏、可比较的 profile 快照及 lockfile 哈希。 |
|
|
19
126
|
| `dsh-doctor diff` | 汇总新增、删除或升级的插件,以及 rows、hooks、UI 声明、peer 和平台变化。 |
|
|
20
|
-
| `dsh-doctor preflight` | 在独立临时 profile 中演练目标 DSH 升级。 |
|
|
21
|
-
| `dsh-doctor why row <id>` | 基于明确指定的 profile 中可观察的来源和 layer 信息解释一个 row。 |
|
|
22
|
-
| `dsh-doctor impact bundle <name>` | 列出有直接来源关联证据的 bundle rows 和 diagnostics。 |
|
|
23
|
-
|
|
24
|
-
`why` 和 `impact` 都需要 `--profile <目录>`。报告新增可选、版本化且脱敏的 `compositionFacts`,由这两个命令和 Web Composition Explorer 共用。Web Settings 仍然只读:它读取最新本地报告,展示诊断图和 composition 图,并导出 JSON/Markdown;不提供修复、安装或卸载操作。
|
|
25
|
-
|
|
26
|
-
Composition facts 描述公开 `--dump-config` 返回的结构;无法取得时仅描述静态声明。`introduced` 和 `patched-by` 表示 DSH 输出中记录的来源链,不证明逐字段 owner。若未观察到前层 config key,移除的键和字段 owner 必须是 unknown。route/slot ownership、runtime hook ownership、任意依赖和可能的 dependents 会标记为未观察/未建模。
|
|
127
|
+
| `dsh-doctor preflight` | 在独立临时 profile 中演练目标 DSH 升级。 |
|
|
128
|
+
| `dsh-doctor why row <id>` | 基于明确指定的 profile 中可观察的来源和 layer 信息解释一个 row。 |
|
|
129
|
+
| `dsh-doctor impact bundle <name>` | 列出有直接来源关联证据的 bundle rows 和 diagnostics。 |
|
|
130
|
+
|
|
131
|
+
`why` 和 `impact` 都需要 `--profile <目录>`。报告新增可选、版本化且脱敏的 `compositionFacts`,由这两个命令和 Web Composition Explorer 共用。Web Settings 仍然只读:它读取最新本地报告,展示诊断图和 composition 图,并导出 JSON/Markdown;不提供修复、安装或卸载操作。
|
|
132
|
+
|
|
133
|
+
Composition facts 描述公开 `--dump-config` 返回的结构;无法取得时仅描述静态声明。`introduced` 和 `patched-by` 表示 DSH 输出中记录的来源链,不证明逐字段 owner。若未观察到前层 config key,移除的键和字段 owner 必须是 unknown。route/slot ownership、runtime hook ownership、任意依赖和可能的 dependents 会标记为未观察/未建模。
|
|
27
134
|
|
|
28
135
|
## 报告与证据边界
|
|
29
136
|
|
|
@@ -35,29 +142,19 @@ Composition facts 描述公开 `--dump-config` 返回的结构;无法取得时
|
|
|
35
142
|
|
|
36
143
|
`scan --fail-on never|info|warning|error` 控制 scan 的退出码。默认是 `never`:warning 和 error 会写入报告,但不改变退出码;`info` 遇到任意诊断时退出 1,`warning` 遇到 warning 或 error 时退出 1,`error` 只在 error 时退出 1。参数错误退出 2,执行失败退出 1。
|
|
37
144
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
```powershell
|
|
41
|
-
npm install -g dsh-composition-doctor
|
|
42
|
-
npx @deepseek-ai/dsh plugin --profile web add dsh-composition-doctor
|
|
43
|
-
dsh-doctor --version
|
|
44
|
-
```
|
|
145
|
+
更改 profile 后重启 Web UI(`npx @deepseek-ai/dsh web`),再重新扫描。
|
|
45
146
|
|
|
46
|
-
|
|
147
|
+
每条诊断均为 `info`、`warning` 或 `error`,并附带 evidence、explanation 和最小 remediation。`runtimeSmoke.status=not-run` 不等于 runtime PASS;`artifact-unavailable` 不等于 `incompatible`;warning 也不等于已确认失败。
|
|
47
148
|
|
|
48
|
-
##
|
|
149
|
+
## 更多命令
|
|
49
150
|
|
|
50
151
|
```powershell
|
|
51
152
|
dsh-doctor scan --profile C:\path\to\profile --format both --output .\reports\profile --publish
|
|
52
153
|
dsh-doctor scan --profile C:\path\to\profile --format both --output .\reports\archive --report-dir C:\safe\doctor-reports
|
|
53
154
|
dsh-doctor snapshot --profile C:\path\to\profile --output .\reports\before.json
|
|
54
155
|
dsh-doctor diff --before .\reports\before.json --after .\reports\after.json --format both
|
|
55
|
-
dsh-doctor preflight --profile C:\path\to\profile --target-dsh 0.1.5-rc.2
|
|
56
|
-
|
|
57
|
-
dsh-doctor impact bundle @example/dsh-bundle --profile C:\path\to\profile
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
每条诊断均为 `info`、`warning` 或 `error`,并附带 evidence、explanation 和最小 remediation。`runtimeSmoke.status=not-run` 不等于 runtime PASS;`artifact-unavailable` 不等于 `incompatible`;warning 也不等于已确认失败。
|
|
156
|
+
dsh-doctor preflight --profile C:\path\to\profile --target-dsh 0.1.5-rc.2
|
|
157
|
+
```
|
|
61
158
|
|
|
62
159
|
## 安全与隐私
|
|
63
160
|
|
|
@@ -65,7 +162,7 @@ dsh-doctor impact bundle @example/dsh-bundle --profile C:\path\to\profile
|
|
|
65
162
|
|
|
66
163
|
## 支持范围与限制
|
|
67
164
|
|
|
68
|
-
|
|
165
|
+
2026-10-03 核对的 npm `latest` 为 `@deepseek-ai/dsh@0.2.0-rc.2`,已在 Windows 和 Ubuntu 24.04(WSL)+ Node.js 24.19.0 重新运行公开 `--version`/`--dump-config` harness。Doctor 的完整 RC 门槛与 packed fresh-install smoke 已在本地 Windows/Node 24、Ubuntu/Node 20.19.5 和 24.19.0 通过。2026-10-04,[Hosted CI 的 Windows/Ubuntu × Node 20/22/24 共六个作业全部通过](https://github.com/lemonxiny55/dsh-composition-doctor/actions/runs/37187050083),包含 packed fresh-install smoke;两个 Node 24 作业均通过当前 DSH 公开 harness。`0.1.5-rc.1`/`rc.2` golden 仅作历史证据,没有 artifact 的测试会 skip。`0.2.1-alpha.1` 属于 expected-compatible/experimental,未验证。Doctor 支持 Node.js `>=20`。此声明不代表任意第三方插件可用,也不代表观察过插件 runtime/UI 注册。[兼容性细节](docs/compatibility.md) · [RC 验证记录](docs/release-evidence/0.4.0.md)。
|
|
69
166
|
|
|
70
167
|
## 开发
|
|
71
168
|
|
|
@@ -73,6 +170,8 @@ real-release harness 已使用真实公开 CLI 验证 `@deepseek-ai/dsh@0.1.5-rc
|
|
|
73
170
|
pnpm test
|
|
74
171
|
pnpm typecheck
|
|
75
172
|
pnpm build
|
|
173
|
+
pnpm pack
|
|
174
|
+
pnpm smoke:pack ./dsh-composition-doctor-0.4.0.tgz
|
|
76
175
|
```
|
|
77
176
|
|
|
78
177
|
仅在 checkout 开发时,构建完成后使用 `node dist/cli/main.js` 运行 CLI。
|
package/dist/cli/main.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
import { fileURLToPath } from 'node:url';
|
|
2
3
|
import { readFileSync, realpathSync } from 'node:fs';
|
|
3
|
-
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { lstat, mkdir, readFile, realpath, writeFile } from 'node:fs/promises';
|
|
4
5
|
import { dirname, resolve } from 'node:path';
|
|
5
6
|
import { resolveComposition } from '../core/composition-adapter.js';
|
|
6
7
|
import { diffSnapshots, renderSnapshotDiffMarkdown } from '../core/diff.js';
|
|
@@ -12,8 +13,128 @@ import { renderJson } from '../reports/json.js';
|
|
|
12
13
|
import { renderMarkdown } from '../reports/markdown.js';
|
|
13
14
|
import { resolveReportDirectory } from '../reports/location.js';
|
|
14
15
|
import { runPreflight } from '../core/preflight.js';
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
import { explainFailures, maxFailureLogBytes } from '../core/failure-explainer.js';
|
|
17
|
+
import { readFailureComposition } from '../core/failure-profile.js';
|
|
18
|
+
import { renderFailure } from '../reports/failure.js';
|
|
19
|
+
const usage = 'Usage: dsh-doctor <diagnose | check | scan | snapshot | diff | preflight | why | impact>';
|
|
20
|
+
const commands = new Set(['diagnose', 'check', 'scan', 'snapshot', 'diff', 'preflight', 'why', 'impact']);
|
|
21
|
+
async function stdinLog() {
|
|
22
|
+
if (process.stdin.isTTY)
|
|
23
|
+
return undefined;
|
|
24
|
+
const chunks = [];
|
|
25
|
+
let size = 0;
|
|
26
|
+
for await (const chunk of process.stdin) {
|
|
27
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
28
|
+
size += buffer.length;
|
|
29
|
+
if (size > maxFailureLogBytes)
|
|
30
|
+
throw new Error('Log exceeds the 1 MiB limit; provide a minimized error log.');
|
|
31
|
+
chunks.push(buffer);
|
|
32
|
+
}
|
|
33
|
+
return chunks.length === 0 ? undefined : Buffer.concat(chunks).toString('utf8');
|
|
34
|
+
}
|
|
35
|
+
async function fileLog(filename) {
|
|
36
|
+
const path = await realpath(resolve(filename));
|
|
37
|
+
if (!/\.(?:log|txt|out)$/i.test(path) || /(?:^|[\\/])(?:\.env[^\\/]*|[^\\/]*(?:credential|token|secret|password)[^\\/]*|sessions?|chats?|workspace)(?:[\\/]|$)/i.test(path)) {
|
|
38
|
+
throw new Error('--log accepts explicit .log/.txt/.out error logs; secret, session and workspace paths are forbidden.');
|
|
39
|
+
}
|
|
40
|
+
const metadata = await lstat(path);
|
|
41
|
+
if (!metadata.isFile() || metadata.size > maxFailureLogBytes)
|
|
42
|
+
throw new Error('Log must be a regular file of at most 1 MiB.');
|
|
43
|
+
const text = await readFile(path, 'utf8');
|
|
44
|
+
if (Buffer.byteLength(text) > maxFailureLogBytes)
|
|
45
|
+
throw new Error('Log exceeds the 1 MiB limit.');
|
|
46
|
+
return text;
|
|
47
|
+
}
|
|
48
|
+
async function diagnose(argv, io, check = false) {
|
|
49
|
+
if (argv.includes('--help')) {
|
|
50
|
+
io.write('Usage: dsh-doctor diagnose --profile <dir> [--log <file> | --log -] [--format human|json|markdown|both] [--output <dir>] [--report-dir <dir> | --publish] [--dsh-version <version>] [--composed]\nPiped stdin is detected automatically. Default: read-only static metadata, no DSH execution. check is the same bounded entry point; --profile is required.');
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
const valued = new Set(['--profile', '--log', '--format', '--output', '--report-dir', '--dsh-version']);
|
|
54
|
+
const flags = new Set(['--publish', '--composed']);
|
|
55
|
+
const seen = new Set();
|
|
56
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
57
|
+
const value = argv[index];
|
|
58
|
+
if ((!valued.has(value) && !flags.has(value)) || seen.has(value))
|
|
59
|
+
return invalid(io, 'diagnose has an unknown or repeated option');
|
|
60
|
+
seen.add(value);
|
|
61
|
+
if (valued.has(value)) {
|
|
62
|
+
if (argv[index + 1] === undefined || argv[index + 1].startsWith('--'))
|
|
63
|
+
return invalid(io, `${value} requires a value`);
|
|
64
|
+
index += 1;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const profile = option(argv, '--profile');
|
|
68
|
+
if (profile === undefined)
|
|
69
|
+
return invalid(io, `${check ? 'check' : 'diagnose'} requires --profile <dir>; no profile is guessed`);
|
|
70
|
+
const format = option(argv, '--format') ?? 'human';
|
|
71
|
+
if (!['human', 'json', 'markdown', 'both'].includes(format))
|
|
72
|
+
return invalid(io, '--format must be human, json, markdown, or both');
|
|
73
|
+
const dshVersion = option(argv, '--dsh-version');
|
|
74
|
+
if (dshVersion !== undefined && !/^\d+\.\d+\.\d+(?:-[a-z0-9.-]+)?(?:\+[a-z0-9.-]+)?$/i.test(dshVersion))
|
|
75
|
+
return invalid(io, '--dsh-version requires an exact semantic version');
|
|
76
|
+
let log;
|
|
77
|
+
const logPath = option(argv, '--log');
|
|
78
|
+
try {
|
|
79
|
+
log = logPath !== undefined && logPath !== '-' ? await fileLog(logPath) : await io.readStdin?.();
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
io.write('Could not read permitted log input. Use a minimized .log/.txt/.out file or stdin, at most 1 MiB; secret/session/workspace paths are forbidden.');
|
|
83
|
+
return 1;
|
|
84
|
+
}
|
|
85
|
+
if (logPath === '-' && log === undefined)
|
|
86
|
+
return invalid(io, '--log - requires piped input');
|
|
87
|
+
if (log !== undefined && Buffer.byteLength(log) > maxFailureLogBytes)
|
|
88
|
+
return invalid(io, 'log exceeds 1 MiB');
|
|
89
|
+
const input = await readProfile({ profileDir: resolve(profile) });
|
|
90
|
+
let model = await readFailureComposition(input, argv.includes('--composed'));
|
|
91
|
+
if (dshVersion !== undefined)
|
|
92
|
+
model = { ...model, runtime: { ...model.runtime, dsh: dshVersion } };
|
|
93
|
+
const report = analyseComposition(model);
|
|
94
|
+
const result = explainFailures({ report, log });
|
|
95
|
+
report.failureExplanation = result;
|
|
96
|
+
const output = option(argv, '--output');
|
|
97
|
+
const reportDir = option(argv, '--report-dir');
|
|
98
|
+
const destinations = [...(output === undefined ? [] : [resolve(output)]),
|
|
99
|
+
...(argv.includes('--publish') || reportDir !== undefined ? [resolveReportDirectory(reportDir)] : [])];
|
|
100
|
+
// Prevent report output from mutating the selected profile, including symlink parents.
|
|
101
|
+
for (const directory of destinations) {
|
|
102
|
+
let parent = directory;
|
|
103
|
+
let canonical;
|
|
104
|
+
for (;;) {
|
|
105
|
+
try {
|
|
106
|
+
canonical = await realpath(parent);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
const next = dirname(parent);
|
|
111
|
+
if (next === parent)
|
|
112
|
+
break;
|
|
113
|
+
parent = next;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const root = input.profileDir.toLowerCase().replaceAll('\\', '/');
|
|
117
|
+
const normalized = (canonical ?? directory).toLowerCase().replaceAll('\\', '/');
|
|
118
|
+
if (normalized === root || normalized.startsWith(`${root}/`))
|
|
119
|
+
return invalid(io, 'diagnose output/report directory must be outside the selected profile');
|
|
120
|
+
}
|
|
121
|
+
for (const directory of new Set(destinations)) {
|
|
122
|
+
await mkdir(directory, { recursive: true });
|
|
123
|
+
for (const filename of ['report.json', 'report.md']) {
|
|
124
|
+
try {
|
|
125
|
+
const metadata = await lstat(resolve(directory, filename));
|
|
126
|
+
if (metadata.isSymbolicLink() || metadata.nlink > 1)
|
|
127
|
+
return invalid(io, 'report destination must not be a symbolic or hard link');
|
|
128
|
+
}
|
|
129
|
+
catch { /* new file */ }
|
|
130
|
+
}
|
|
131
|
+
await writeFile(resolve(directory, 'report.json'), renderJson(report), 'utf8');
|
|
132
|
+
await writeFile(resolve(directory, 'report.md'), renderFailure(result, true), 'utf8');
|
|
133
|
+
}
|
|
134
|
+
io.write(format === 'json' ? `${JSON.stringify(result, null, 2)}\n` : format === 'both'
|
|
135
|
+
? `${JSON.stringify(result, null, 2)}\n${renderFailure(result, true)}` : renderFailure(result, format === 'markdown'));
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
17
138
|
function packageVersion() {
|
|
18
139
|
try {
|
|
19
140
|
const manifest = JSON.parse(readFileSync(resolve(dirname(fileURLToPath(import.meta.url)), '../../package.json'), 'utf8'));
|
|
@@ -209,7 +330,7 @@ async function impact(argv, io) {
|
|
|
209
330
|
}, null, 2)}\n`);
|
|
210
331
|
return 0;
|
|
211
332
|
}
|
|
212
|
-
export async function runCli(argv, io = { write: (line) => console.log(line) }) {
|
|
333
|
+
export async function runCli(argv, io = { write: (line) => console.log(line), readStdin: stdinLog }) {
|
|
213
334
|
const command = argv[0];
|
|
214
335
|
if (argv.length === 0 || command === '--help') {
|
|
215
336
|
io.write(usage);
|
|
@@ -224,6 +345,8 @@ export async function runCli(argv, io = { write: (line) => console.log(line) })
|
|
|
224
345
|
return 2;
|
|
225
346
|
}
|
|
226
347
|
try {
|
|
348
|
+
if (command === 'diagnose' || command === 'check')
|
|
349
|
+
return await diagnose(argv.slice(1), io, command === 'check');
|
|
227
350
|
if (command === 'scan')
|
|
228
351
|
return await scan(argv.slice(1), io);
|
|
229
352
|
if (command === 'snapshot')
|
|
@@ -237,6 +360,10 @@ export async function runCli(argv, io = { write: (line) => console.log(line) })
|
|
|
237
360
|
return await impact(argv.slice(1), io);
|
|
238
361
|
}
|
|
239
362
|
catch (error) {
|
|
363
|
+
if (command === 'diagnose' || command === 'check') {
|
|
364
|
+
io.write('Could not inspect selected profile metadata. Check --profile directory, metadata syntax and file access.');
|
|
365
|
+
return 1;
|
|
366
|
+
}
|
|
240
367
|
io.write(error instanceof Error ? error.message : String(error));
|
|
241
368
|
return 1;
|
|
242
369
|
}
|