Bundle
dsh-bundle-dedup-guard
DSH plugin guard: on every plugin load checks profile bundle lists for duplicate loader entries (aggregate bundle + sub-plugins listed together → 'duplicate loader entry id' boot crash) and runs site-level health checks — known conflict pairs, vendor @deepseek-ai tree integrity vs lockfile, plugin @deepseek-ai junction integrity — guarding the 2026-08-18 and 2026-08-21 incidents. Read-only diagnostics.
- Source
- Lstalu
- stars
- 2 stars
- License
- MIT
- Updated
- Updated 4 days ago
Readme
# dsh-bundle-dedup-guard [**English**](README.md) | [**简体中文**](README.zh-CN.md) [](https://github.com/Lstalu/dsh-bundle-dedup-guard/actions/workflows/ci.yml) [](https://www.npmjs.com/package/dsh-bundle-dedup-guard) []() [](LICENSE) **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 `bundles` but 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 `dependencies` entry that declares `dsh.bundle` but is *not* in `bundles`. `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 to `devDependencies` (reconcile only reads `dependencies`). 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:** ```bash dsh plugin --profile web add dsh-bundle-dedup-guard ``` ## Usage The plugin checks automatically — no interaction needed. For manual diagnosis (including when boot already crashed): ```bash # 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-site ``` Exit 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 `dependencies` to `devDependencies`** — otherwise `dsh plugin` reconcile 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 ```bash npm test # node --test, zero dependencies npm run check # run the guard against your local profiles ``` - `lib/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
Install
dsh plugin --profile web add github:Lstalu/dsh-bundle-dedup-guard
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-bundle-dedup-guard from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.