dsh-publish-guard
Pre-flight checks for publishing DeepSeek Harness (DSH) plugins. One command
answers ship / fix / block, and a second command clears the mechanical blockers
it finds.
It exists because the DSH ecosystem has ~11k plugins and the same failures repeat:
a package that installs but never activates (no dsh.bundle.patch), a repository
that cannot be indexed (private), a plugin that installs on Linux and dies on
Android (native module), a README that answers nothing a user asks.
Install
# no install needed
npx @xiciyang/dsh-publish-guard <dir>
# or globally
npm i -g @xiciyang/dsh-publish-guard
As a DSH plugin (tool)
dsh plugin --profile <name> add @xiciyang/dsh-publish-guard # or github:xiciyang/dsh-publish-guard
Registers one tool, publish_readiness({ dir, tests?, lint? }), so an agent can answer
"can this package ship?" without knowing the script path. Same checks, one implementation.
Usage
publish-readiness ./my-plugin # human report
publish-readiness ./my-plugin --json # machine report
publish-readiness ./my-plugin --lint # also run npx dsh-plugin-lint
publish-readiness ./my-plugin --tests # also run npm test in the package
publish-fix ./my-plugin # dry-run: show the mechanical fixes
publish-fix ./my-plugin --apply # write them
publish-fix ./my-plugin --apply --keep-private # do not remove `private: true`
The verdict is data, not an exit code: publish-readiness always exits 0 and
prints ready / needs-work / blocked. In CI, gate on fails.length from
--json so the build fails for the reason recorded in the report.
What it checks
| id | severity | what it means |
|---|
dsh-bundle | fail | no dsh.bundle.patch — the plugin installs but no layer activates |
engines-dsh | fail | no engines.dsh range — install/start cannot verify compatibility |
license-field / license-file | fail | no SPDX id or no license text |
private-flag | fail | private: true blocks publishing |
repo-public | fail | repository not public — awesome lists and indexes skip it |
deps-resolvable | fail | link: / file: / ../ dependencies cannot be installed by users |
abs-home-path | fail | /home/<you>/… leaks into shipped files |
secret-scan | fail | key-shaped strings in the package |
readme + 5 sections | fail / warn | README under 1500 chars, or missing install / usage / config / compatibility / license |
keyword | warn | no dsh-plugin keyword — npm discovery misses it |
session-artifacts | warn | logs, .playwright-mcp/, captured pages shipped by accident |
test-script | warn | no npm test entry |
plugin-lint | warn / fail | dsh-plugin-lint exit code, when --lint is given |
Configuration
| env | effect |
|---|
PUBLISH_GUARD_OFFLINE=1 | skip the GitHub visibility check (tests, CI without gh) |
PUBLISH_GUARD_CANDIDATES=path | default candidate list for --all |
publish-fix never touches engines.dsh, absolute paths or repository: those
need a real decision (was it tested on that version? where does the path point?).
Compatibility
- Node.js >= 22 (no runtime dependencies).
- Works on any DSH version; the DSH-specific rules are read from
package.json,
not from a running harness. Verified against DSH 0.2.0-rc.2.
--lint delegates to dsh-plugin-lint via npx; without network it is skipped.
Why the checks are split into fail / warn
A failing check means the package cannot work for a user or cannot be found.
A warning means it works but will be ignored or misread. Mixing the two is how
people end up "fixing" a README while the plugin still never activates.
License
MIT