# Publishing checklist The release recipe used for this plugin (learned the hard way — see *Pitfalls*). ## Layout (community convention) ``` dsh-permissions/ ├── lib/index.js # host half (settings namespace + pre-execute engine + /api/dperm route) ├── lib/client.js # browser bundle (window.__ModuleLoader__.load, id MUST equal package name) ├── cordis.patch.yml # root patch (the manual-install manifest) ├── package.json # dsh.client manifest + dsh.bundle.patch declaration + files whitelist ├── README.md ├── PUBLISH.md └── LICENSE ``` ## Release steps 1. Bump `version` in `package.json`. 2. `git add -A && git commit` — push to GitHub (topics: `dsh-plugin`, `deepseek-harness`, `permissions`). 3. `npm publish --access public` (unscoped name: `dsh-permissions`). 4. Keep the awesome-dsh-plugin entry in sync (one line under the matching category in both `README.md` and `README.zh.md`). ## Pitfalls hit during development (so the next release doesn't) 0. **`link:`-installed profiles resolve imports from the real source path** — `dsh plugin --profile

add link:` keeps the package external, so Node resolves `@deepseek-ai/schemastery` from the checkout, not the profile. Fix: install the peer into the profile (`dsh plugin --profile

add @deepseek-ai/schemastery`) and bridge it into the checkout with a junction at `/node_modules/@deepseek-ai/schemastery` → `/node_modules/@deepseek-ai/schemastery` (gitignored). `file:` specs copy and install deps instead, at the cost of losing live-edit. 1. **Client package `exports` must include `"./package.json"`** — the client-modules node half reads the package manifest through `require.resolve('/package.json')`; without the export the row is silently skipped (no bundle route, no settings page). 2. **Bundle `__ModuleLoader__.load` id must equal the package name** — the graph row is keyed by entry name. 3. **Never pass unbounded method references to React's `useSyncExternalStore`** (`controller.subscribe` loses `this` and crashes the render). Corollary: **every hook (useState/useRef/useEffect/…) must sit above any early `return`** — a hook below a conditional return changes the hook count between renders and crashes with "Rendered more hooks than during the previous render" (bit us in v1.0.9). 4. **The api-proxy settings allowlist does not expose third-party namespaces** — the engine serves its own HTTP route (`/api/dperm/rules`) over its own `SettingsScope` instead. 5. **`serveBundle` reads the bundle from disk with `cache-control: no-cache`** — client-side edits apply on a plain F5; host-side edits need a restart (Node module cache). 6. **Dynamic plugin state resets on every package update** — persist via the `settings` namespace, not in-process memory. 7. **Model tool-call argument key order is not stable** — extract semantic fields (`file_path`, `pattern`, `command`), never `Object.values(args)[0]`.