dsh-enhancement-suite 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -94,8 +94,8 @@ dsh-enhance update --profile headless
94
94
  ## How it installs
95
95
 
96
96
  1. Every plugin is installed through the **official CLI**: `dsh plugin --profile <profile> add <package>`. This suite never loads the plugins through its own dependency graph or its own cordis patch — the four upstream packages stay independent in the profile.
97
- 2. `dsh-plugin-marketplace` declares `dsh.bundle.patch`, so `dsh plugin add` reconciles it into `dsh.profile.bundles` and its bundle layer auto-mounts it.
98
- 3. The other three are plain dependencies; the suite appends their mount rows to the profile's `cordis.patch.yml` with the same safe, idempotent logic the marketplace's own one-click install uses (an empty `[]` patch is replaced correctly, duplicates are skipped, and a flow-style array is refused instead of corrupted).
97
+ 2. All four plugins now declare `dsh.bundle.patch` (as of vision 0.3.9 / soul-md 0.5.4 / tdai-memory 0.2.9 / marketplace 0.2.4), so `dsh plugin add` reconciles each into `dsh.profile.bundles` and its own bundle layer auto-mounts it.
98
+ 3. For profiles that mounted a plugin manually before it gained its bundle manifest, the suite **removes the stale manual row** from `cordis.patch.yml` — two mounts of the same entry id would abort boot. (Non-bundle third-party plugins still get the safe idempotent mount logic: empty `[]` patches are replaced correctly, duplicates are skipped, flow-style arrays are refused instead of corrupted.)
99
99
  4. Restart `dsh web` and the plugins load.
100
100
 
101
101
  Every step reports a clear `✓` / `✗`, failures are never swallowed, and no API key is read, printed, or forwarded.
@@ -151,13 +151,36 @@ function isMountedInPatch(pkg, profile) {
151
151
  * Mirrors the marketplace's own logic: bundle plugins are skipped (their
152
152
  * bundle layer auto-mounts them), an empty `[]` patch is replaced with a
153
153
  * block-style insert list, and a non-empty flow-style array is refused
154
- * rather than corrupted.
154
+ * rather than corrupted. For bundle plugins, any STALE manual row left by an
155
+ * earlier non-bundle version is removed — two mounts of the same entry id
156
+ * abort boot.
155
157
  */
158
+ function escapeRegExp(text) {
159
+ return String(text).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
160
+ }
161
+
162
+ /** Remove a `- id: ...` + `name: '<pkg>'` manual mount block, if present. */
163
+ function stripManualRow(src, pkg) {
164
+ const pattern = new RegExp(`[ \\t]*- id: [^\\n]*\\n[ \\t]*name: ['"]${escapeRegExp(pkg)}['"]\\n?`, "g");
165
+ return src.replace(pattern, "");
166
+ }
167
+
156
168
  function ensureMounted(pkg, profile) {
157
169
  const patch = join(profileDir(profile), "cordis.patch.yml");
158
170
  if (!existsSync(patch)) return { changed: false, note: "cordis.patch.yml not found; mount manually" };
159
- if (isInProfileBundles(pkg, profile)) return { changed: false, note: "already mounted via dsh.profile.bundles" };
160
- if (isBundlePackage(pkg, profile)) return { changed: false, note: "bundle plugin: auto-mounted via its own dsh.bundle layer" };
171
+ const inBundles = isInProfileBundles(pkg, profile);
172
+ if (inBundles || isBundlePackage(pkg, profile)) {
173
+ // The bundle layer auto-mounts this plugin. Remove a stale manual row
174
+ // (written before the package gained its dsh.bundle manifest) so boot
175
+ // does not fail with a duplicate loader entry.
176
+ let src = readFileSync(patch, "utf8");
177
+ const cleaned = stripManualRow(src, pkg);
178
+ if (cleaned !== src) {
179
+ if (process.env.DSH_ENHANCE_DRY_RUN !== "1") writeFileSync(patch, cleaned, "utf8");
180
+ return { changed: true, note: "removed stale manual mount row (bundle layer auto-mounts)" };
181
+ }
182
+ return { changed: false, note: inBundles ? "already mounted via dsh.profile.bundles" : "bundle plugin: auto-mounted via its own dsh.bundle layer" };
183
+ }
161
184
  let src = readFileSync(patch, "utf8");
162
185
  if (src.includes(`name: '${pkg}'`) || src.includes(`name: "${pkg}"`)) return { changed: false, note: "already mounted in cordis.patch.yml" };
163
186
  const id = pkg.replace(/^@[^/]+\//, "").replace(/[^a-z0-9-]/g, "-") || pkg;
@@ -398,9 +421,14 @@ function cmdDoctor(profile) {
398
421
  continue;
399
422
  }
400
423
  ok(`${p.emoji} ${p.name}: installed (${version})`);
401
- const mounted = isInProfileBundles(p.name, profile) || isMountedInPatch(p.name, profile) || isBundlePackage(p.name, profile);
402
- if (mounted) info(` mounted: ${isInProfileBundles(p.name, profile) ? "dsh.profile.bundles" : isMountedInPatch(p.name, profile) ? "cordis.patch.yml" : "own dsh.bundle layer"}`);
403
- else {
424
+ const inBundles = isInProfileBundles(p.name, profile);
425
+ const inPatch = isMountedInPatch(p.name, profile);
426
+ if (inBundles && inPatch) {
427
+ bad(` duplicate mount: manual row in cordis.patch.yml AND dsh.profile.bundles — run "dsh-enhance install" to clean up`);
428
+ healthy = false;
429
+ } else if (inBundles || inPatch || isBundlePackage(p.name, profile)) {
430
+ info(` mounted: ${inBundles ? "dsh.profile.bundles" : inPatch ? "cordis.patch.yml" : "own dsh.bundle layer"}`);
431
+ } else {
404
432
  bad(` not mounted — run "dsh-enhance install"`);
405
433
  healthy = false;
406
434
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-enhancement-suite",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Enhancement suite for DeepSeek Harness — the official installer and registry for Scorp1o117's Vision, Soul/Persona, Memory & Marketplace plugins.",
5
5
  "type": "module",
6
6
  "license": "MIT",