dsh-bundle-dedup-guard
  ![node]() 
A DeepSeek Harness plugin that catches duplicate loader entry ids in profile bundle lists — and audits the whole plugin environment (vendor tree, plugin junctions, known conflict pairs) — on every plugin load.
If a profile's dsh.profile.bundles lists an aggregate bundle (a bundle whose patch inserts all of its sub-plugins, e.g. @linxin666/dsh-web-ui-all) and its sub-plugins individually, the loader receives the same loader-entry id twice. EntryGroup.update throws duplicate loader entry id: <id> before any plugin starts, and the whole profile fails to boot. This plugin exists so that never happens silently again.
---
Why this exists
Incident, 2026-08-18: a web profile listed @linxin666/dsh-web-ui-all (which aggregates 13 sub-plugins into one patch) and all 13 sub-plugins separately. Every sub-plugin id was inserted twice; the first collision reported was duplicate loader entry id: ui-dsh-aionui-panel. Fixing only the bundles list was not enough — the dsh plugin command's reconcilePlugins re-appends every dependencies entry that declares dsh.bundle to the bundle list after each pnpm operation, so the sub-plugins came back an hour later and crashed the next boot.
Incident, 2026-08-21: a bulk plugin update wiped plugin node_modules (8 plugins failed to boot with Cannot find package); a repair npm install followed an @deepseek-ai junction into the vendor tree and corrupted it; and once dependencies were restored, dsh-better-sidebar ended up mounted twice (/sidebar/api duplicate-route crash). v0.2.0 turns these red lines into automatic site-level checks — see [Site-level health checks](#site-level-health-checks-v020) below.
Full incident records: [docs/KNOWN-ISSUE-bundle-duplicate.md](docs/KNOWN-ISSUE-bundle-duplicate.md) (2026-08-18) and the 2026-08-21 self-repair manual surfaced by the site audit.
How it works
The loader's failure path is: cordis-plugin-include's applyEntryPatches flattens every bundle's insert entries without deduplicating, then cordis-plugin-loader's EntryGroup.update dedups by id and throws on the first duplicate — before any plugin entry is created. This plugin re-implements exactly that "flatten + dedup by id" semantics in pure Node, and reports the offending ids, their sources (which bundle/patch inserted each), and the fix.
Checks on every plugin load
| Trigger | When | Notes |
|---|---|---|
| Apply | every boot | instant health check as the plugin mounts |
| Loader events | loader/entry-init / loader/partial-dispose | runtime hot loads / plugin additions, debounced 800 ms |
| Manifest watch | fs.watch on the profile dir | the moment package.json or cordis.patch.yml changes — i.e. dsh plugin add, marketplace installs, or hand edits — warn immediately, before the next restart |
What it reports
- Duplicate loader entry ids — each id inserted by more than one source, with the full source chain (e.g.
ui-dsh-aionui-panel: @linxin666/dsh-web-ui-all ← @linxin666/dsh-client-ui-aionui-panel). - Unresolved bundles — listed in
bundlesbut not resolvable (the loader would loud-fail too). - Bundle-less packages — listed but without a
dsh.bundle.patch(a misconfiguration per the loader contract). - Predictive reconcile warning — a
dependenciesentry that declaresdsh.bundlebut is not inbundles.dsh plugin's reconcile will append it on the next install/update; if it's a sub-plugin covered by an aggregate, that re-creates the crash. The warning names the covered ids. Fix: move such packages todevDependencies(reconcile only readsdependencies).
Reports are written to $DSH_HOME/dsh-bundle-dedup-guard/reports/<profile>-<timestamp>.json and <profile>.latest.json.
Site-level health checks (v0.2.0)
Beyond the bundle-list checks, every run also audits the whole plugin environment, turning the 2026-08-21 incident's red lines into automatic checks (lib/site-health.mjs, zero-dependency, read-only):
| Check | Detects | Incident |
|---|---|---|
| Known conflict pairs | bundles lists @linxin666/dsh-web-ui-all and dsh-better-sidebar together — both entries execute the same lib/index.js and register the same /sidebar/api route (duplicate prefix route crash at apply time) | 2026-08-21 |
| Vendor tree integrity | every @deepseek-ai/* package vs resources/vendor/dsh/node_modules/.package-lock.json: missing / empty dir / package.json name mismatch (wrong content installed) / version mismatch; .name-* temp-dir leftovers (informational) | 2026-08-21 |
| Plugin junction integrity | plugins whose runtime code imports @deepseek-ai/* but whose package/node_modules/@deepseek-ai junction is missing (the exact Cannot find package boot crash), points at the wrong target, or dangles; real-dir copies (works, informational) and .npmbak leftovers (an npm install ran inside a junction dir) | 2026-08-21 |
| Incident manual pointer | surfaces the latest $DSH_HOME/incidents/<date>/README.md self-repair manual | — |
Every problem is reported with a copy-paste fix command (recreate the junction / run repair-vendor.ps1 / remove the bundles entry). The guard never modifies anything itself.
> The junction check decides "does this plugin need a junction" by source-scanning runtime > import/require of @deepseek-ai/* (not by package.json declarations), and only audits > plugins actually listed in some profile's bundles — dormant plugin dirs are skipped.
Known limitation
The loader deduplicates before creating any plugin entry, so when duplicates already exist at boot, an in-process check cannot run — the tree never mounts. For that case use the standalone CLI below: it is pure disk reads and works even when boot is broken.
Installation
As a profile bundle (recommended while in development):
1. Add to the profile's package.json dependencies: ``json "dsh-bundle-dedup-guard": "link:F:/path/to/dsh-bundle-dedup-guard" ` 2. Add "dsh-bundle-dedup-guard" to dsh.profile.bundles (first entry is fine). 3. Link it into the profile's node_modules (pnpm does this for dsh plugin add`).
From npm:
dsh plugin --profile web add dsh-bundle-dedup-guardUsage
The plugin checks automatically — no interaction needed. For manual diagnosis (including when boot already crashed):
# check all profiles + site-level health (DSH_HOME defaults to ~/.dsh)
node bin/check.mjs
# a specific profile
node bin/check.mjs --profile web
# a specific manifest file (e.g. a pre-fix backup, for testing)
node bin/check.mjs --manifest <path-to-package.json>
# machine-readable JSON, skip report files
node bin/check.mjs --profile web --json --no-write
# skip the site-level health audit
node bin/check.mjs --no-siteExit codes: 0 = healthy, 1 = duplicates / unresolved bundles / bundle-less packages / site-level problems found (useful as a CI gate).
Fixing duplicates
Edit dsh.profile.bundles so each id has exactly one source. The common shape is "aggregate + sub-plugins":
- keep the aggregate (e.g.
@linxin666/dsh-web-ui-all) - remove the individually listed sub-plugin entries
- also move the sub-plugins from
dependenciestodevDependencies— otherwisedsh pluginreconcile re-appends them on the next install/update (the exact recurrence from 2026-08-18)
Then re-run node bin/check.mjs --profile <name> until green, and restart.
Development
npm test # node --test, zero dependencies
npm run check # run the guard against your local profileslib/check.mjs— the check core (pure Node, no third-party deps)lib/site-health.mjs— site-level health checks (v0.2.0: conflict pairs, vendor tree, junctions)index.mjs— the Cordis plugin entry (apply+ listeners)bin/check.mjs— standalone CLI (works without a booted tree)test/— unit tests with fixture profiles
License
MIT