@mulmoclaude/core 4.4.2 → 4.6.0

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.
Files changed (81) hide show
  1. package/assets/helps/bug-report-faq.md +13 -0
  2. package/assets/helps/business.md +4 -0
  3. package/assets/helps/error-recovery.md +71 -0
  4. package/assets/helps/gemini.md +1 -1
  5. package/assets/helps/mulmoscript.md +71 -1
  6. package/assets/helps/presentation-deck.md +4 -1
  7. package/assets/helps/storyteller.md +7 -0
  8. package/dist/collection/core/accentColor.d.ts +23 -0
  9. package/dist/collection/core/iconGlyph.d.ts +26 -0
  10. package/dist/collection/core/schema.d.ts +9 -0
  11. package/dist/collection/core/schemaZ.d.ts +19 -1
  12. package/dist/collection/core/shortcutInfo.d.ts +14 -0
  13. package/dist/collection/core/uiTypes.d.ts +6 -0
  14. package/dist/collection/index.cjs +22 -2
  15. package/dist/collection/index.cjs.map +1 -1
  16. package/dist/collection/index.d.ts +3 -0
  17. package/dist/collection/index.js +17 -3
  18. package/dist/collection/index.js.map +1 -1
  19. package/dist/collection/registry/server/index.cjs +3 -3
  20. package/dist/collection/registry/server/index.js +3 -3
  21. package/dist/collection/server/index.cjs +6 -4
  22. package/dist/collection/server/index.js +5 -5
  23. package/dist/collection/server/paths.d.ts +10 -0
  24. package/dist/collection/server/skillAssets.d.ts +20 -0
  25. package/dist/collection-watchers/index.cjs +5 -5
  26. package/dist/collection-watchers/index.js +5 -5
  27. package/dist/{discovery-Dl9nRa-m.cjs → discovery-2TvulIz8.cjs} +25 -5
  28. package/dist/discovery-2TvulIz8.cjs.map +1 -0
  29. package/dist/{discovery-P64fgtuD.js → discovery-BY-nMCuh.js} +20 -6
  30. package/dist/discovery-BY-nMCuh.js.map +1 -0
  31. package/dist/feeds/index.cjs +2 -2
  32. package/dist/feeds/index.js +2 -2
  33. package/dist/feeds/server/index.cjs +5 -5
  34. package/dist/feeds/server/index.js +5 -5
  35. package/dist/files/atomic.d.ts +4 -0
  36. package/dist/files/index.cjs +1 -1
  37. package/dist/files/index.js +1 -1
  38. package/dist/global-config/index.cjs +1 -1
  39. package/dist/google/index.cjs +4 -4
  40. package/dist/google/index.js +3 -3
  41. package/dist/iconGlyph-D8JeNg7R.js +59 -0
  42. package/dist/iconGlyph-D8JeNg7R.js.map +1 -0
  43. package/dist/iconGlyph-fSBp6k4J.cjs +70 -0
  44. package/dist/iconGlyph-fSBp6k4J.cjs.map +1 -0
  45. package/dist/{ingestTypes-CaX7_s-K.cjs → ingestTypes-DsVH9BYy.cjs} +2 -2
  46. package/dist/{ingestTypes-CaX7_s-K.cjs.map → ingestTypes-DsVH9BYy.cjs.map} +1 -1
  47. package/dist/{ingestTypes-CIy1lHjf.js → ingestTypes-GkG7bJdB.js} +2 -2
  48. package/dist/{ingestTypes-CIy1lHjf.js.map → ingestTypes-GkG7bJdB.js.map} +1 -1
  49. package/dist/{itemId-BY2YPBkT.js → itemId-4P9S_ehz.js} +48 -2
  50. package/dist/itemId-4P9S_ehz.js.map +1 -0
  51. package/dist/{itemId-CEQdK01z.cjs → itemId-CGT2J7YK.cjs} +65 -1
  52. package/dist/itemId-CGT2J7YK.cjs.map +1 -0
  53. package/dist/{paths-ClV946_f.cjs → paths-C8-jxR0s.cjs} +1 -2
  54. package/dist/{paths-ClV946_f.cjs.map → paths-C8-jxR0s.cjs.map} +1 -1
  55. package/dist/plugin-vue/IconGlyph.d.ts +27 -0
  56. package/dist/plugin-vue/index.cjs +64 -9
  57. package/dist/plugin-vue/index.cjs.map +1 -1
  58. package/dist/plugin-vue/index.d.ts +1 -0
  59. package/dist/plugin-vue/index.js +65 -11
  60. package/dist/plugin-vue/index.js.map +1 -1
  61. package/dist/{promptSafety-Bp780ezk.js → promptSafety-Bte62OvU.js} +2 -2
  62. package/dist/{promptSafety-Bp780ezk.js.map → promptSafety-Bte62OvU.js.map} +1 -1
  63. package/dist/{promptSafety-C0BWDsfT.cjs → promptSafety-Cv-SoTd4.cjs} +2 -2
  64. package/dist/{promptSafety-C0BWDsfT.cjs.map → promptSafety-Cv-SoTd4.cjs.map} +1 -1
  65. package/dist/{root-BMroU_mB.js → root-BcS4-Kge.js} +12 -2
  66. package/dist/root-BcS4-Kge.js.map +1 -0
  67. package/dist/{root-rPH6FGDT.cjs → root-BvwBUcol.cjs} +12 -2
  68. package/dist/root-BvwBUcol.cjs.map +1 -0
  69. package/dist/{server-CF1qllGV.cjs → server-DXUt34Rt.cjs} +125 -46
  70. package/dist/server-DXUt34Rt.cjs.map +1 -0
  71. package/dist/{server-BvXO49Vj.js → server-DyYwfNB6.js} +120 -47
  72. package/dist/server-DyYwfNB6.js.map +1 -0
  73. package/package.json +9 -9
  74. package/dist/discovery-Dl9nRa-m.cjs.map +0 -1
  75. package/dist/discovery-P64fgtuD.js.map +0 -1
  76. package/dist/itemId-BY2YPBkT.js.map +0 -1
  77. package/dist/itemId-CEQdK01z.cjs.map +0 -1
  78. package/dist/root-BMroU_mB.js.map +0 -1
  79. package/dist/root-rPH6FGDT.cjs.map +0 -1
  80. package/dist/server-BvXO49Vj.js.map +0 -1
  81. package/dist/server-CF1qllGV.cjs.map +0 -1
@@ -1,10 +1,10 @@
1
1
  const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.cjs");
2
2
  const require_dist = require("./dist-CsgSfWwR.cjs");
3
- const require_root = require("./root-rPH6FGDT.cjs");
3
+ const require_root = require("./root-BvwBUcol.cjs");
4
4
  const require_relPath = require("./relPath-CTAjGdCL.cjs");
5
- const require_itemId = require("./itemId-CEQdK01z.cjs");
6
- const require_promptSafety = require("./promptSafety-C0BWDsfT.cjs");
7
- const require_discovery = require("./discovery-Dl9nRa-m.cjs");
5
+ const require_itemId = require("./itemId-CGT2J7YK.cjs");
6
+ const require_promptSafety = require("./promptSafety-Cv-SoTd4.cjs");
7
+ const require_discovery = require("./discovery-2TvulIz8.cjs");
8
8
  const require_feeds_paths = require("./feeds/paths.cjs");
9
9
  const require_skill_bridge_index = require("./skill-bridge/index.cjs");
10
10
  let node_fs = require("node:fs");
@@ -33,27 +33,78 @@ let zod = require("zod");
33
33
  async function readCustomViewHtml(collection, viewFile, opts = {}) {
34
34
  return readSourceAwareFile(collection, viewFile, opts);
35
35
  }
36
- /** Internal helper: read a file using the same source-aware base fallback as
37
- * `readCustomViewHtml`. Used by both `readCustomViewHtml` and
38
- * `readCustomViewI18n` so the two stay in lockstep. */
39
- async function readSourceAwareFile(collection, relPath, opts) {
40
- const safeSlug = require_discovery.safeSlugName(collection.slug);
41
- if (safeSlug === null) return null;
42
- const workspaceRoot = opts.workspaceRoot ?? require_discovery.getWorkspaceRoot();
43
- const staging = collection.source === "project" ? require_discovery.stagingSkillDir(workspaceRoot, safeSlug) : null;
44
- const bases = staging === null ? [collection.skillDir] : [staging, collection.skillDir];
36
+ /** `<staging>/<slug>` when THAT slug is actually staged there, else null.
37
+ *
38
+ * `stagingSkillDir` only says whether the ROOT has a staging tree, and then
39
+ * joins the slug — so on its own it claims every collection in a staged
40
+ * workspace is staged. That is wrong for the two layouts that coexist there:
41
+ * a collection IMPORTED via the discover panel, and one committed directly
42
+ * under `.claude/skills/`, neither of which has a staging copy. A stale
43
+ * `data/skills/<slug>/views/x.html` left beside them then SHADOWED the real
44
+ * view on every read (#3031).
45
+ *
46
+ * The evidence is the slug's own `schema.json`, because that is what the
47
+ * authoring layout actually produces — `putSchema` writes it, and the delete
48
+ * path below has always keyed on it. It must be a REGULAR FILE — `lstat`, not
49
+ * `stat` — so a symlink standing in for the schema is not evidence.
50
+ *
51
+ * Exported so the DELETE path uses this exact function rather than a second
52
+ * copy of the expression: "reads and deletes agree" is a claim `views.ts` has
53
+ * made in its comments since #1836, and sharing the predicate is what makes
54
+ * it structurally true instead of a convention two files have to remember. */
55
+ async function stagedSkillDir(workspaceRoot, safeSlug) {
56
+ const staging = require_discovery.stagingSkillDir(workspaceRoot, safeSlug);
57
+ if (staging === null) return null;
58
+ return await isStagedSchema(node_path.default.join(staging, "schema.json")) ? staging : null;
59
+ }
60
+ /** Is the staged `schema.json` there, as a regular file?
61
+ *
62
+ * NOT `isRegularFile` from `io.ts`, which answers `false` for every `lstat`
63
+ * failure. Here "no schema" and "the schema could not be inspected" must not
64
+ * collapse into one answer: the second would quietly downgrade a staged slug
65
+ * to unstaged and serve the OTHER base's copy — the same masking the read loop
66
+ * below refuses, and for the same reason (Sourcery review on #3032). Only the
67
+ * missing-here codes mean "not staged"; anything else propagates. */
68
+ async function isStagedSchema(target) {
69
+ try {
70
+ return (await (0, node_fs_promises.lstat)(target)).isFile();
71
+ } catch (err) {
72
+ if (!isMissingHere(err)) throw err;
73
+ return false;
74
+ }
75
+ }
76
+ /** The two codes that mean "not at this path" rather than "this path failed".
77
+ * A permission denial / disk error must propagate — silently treating it as
78
+ * absent masks a real failure as a stale-from-another-base success or a
79
+ * misleading 404 (CodeRabbit review on #1836). */
80
+ function isMissingHere(err) {
81
+ return require_dist.isErrorWithCode(err) && (err.code === "ENOENT" || err.code === "ENOTDIR");
82
+ }
83
+ /** Read `relPath` from the first base that holds it, or null when none does.
84
+ * Bases are tried in order and each is containment-checked on its own, so the
85
+ * fallback never widens what a single base would have allowed. */
86
+ async function readFromBases(bases, relPath) {
45
87
  for (const base of bases) {
46
88
  const resolved = require_discovery.resolveTemplatePath(base, relPath);
47
89
  if (resolved === null) continue;
48
90
  try {
49
91
  return await (0, node_fs_promises.readFile)(resolved, "utf-8");
50
92
  } catch (err) {
51
- if (!require_dist.isErrorWithCode(err)) throw err;
52
- if (err.code !== "ENOENT" && err.code !== "ENOTDIR") throw err;
93
+ if (!isMissingHere(err)) throw err;
53
94
  }
54
95
  }
55
96
  return null;
56
97
  }
98
+ /** Internal helper: read a file using the same source-aware base fallback as
99
+ * `readCustomViewHtml`. Used by both `readCustomViewHtml` and
100
+ * `readCustomViewI18n` so the two stay in lockstep. */
101
+ async function readSourceAwareFile(collection, relPath, opts) {
102
+ const safeSlug = require_discovery.safeSlugName(collection.slug);
103
+ if (safeSlug === null) return null;
104
+ const workspaceRoot = opts.workspaceRoot ?? require_discovery.getWorkspaceRoot();
105
+ const staging = collection.source === "project" ? await stagedSkillDir(workspaceRoot, safeSlug) : null;
106
+ return readFromBases(staging === null ? [collection.skillDir] : [staging, collection.skillDir], relPath);
107
+ }
57
108
  var I18N_FALLBACK_LOCALE = "en";
58
109
  var EMPTY_I18N = {
59
110
  locale: "",
@@ -1267,27 +1318,20 @@ async function deleteCollection(collection, opts = {}) {
1267
1318
  }
1268
1319
  //#endregion
1269
1320
  //#region src/collection/server/views.ts
1270
- async function fileExists(target) {
1271
- try {
1272
- await (0, node_fs_promises.stat)(target);
1273
- return true;
1274
- } catch (err) {
1275
- if (!require_dist.isErrorWithCode(err)) throw err;
1276
- if (err.code === "ENOENT" || err.code === "ENOTDIR") return false;
1277
- throw err;
1278
- }
1279
- }
1280
1321
  /** The authoritative base dir for a collection's schema.json + view HTML.
1281
1322
  * For a project collection, prefer the staging tree when its schema.json is
1282
1323
  * present (authoring layout); otherwise fall back to the active skill dir
1283
1324
  * (imported layout — staging never materialised). For feed / user, it's
1284
- * always the discovered skillDir. Matches `readCustomViewHtml` so reads and
1285
- * deletes agree on both layouts. */
1325
+ * always the discovered skillDir.
1326
+ *
1327
+ * Matches `readCustomViewHtml` so reads and deletes agree on both layouts —
1328
+ * through the SAME function now (`stagedSkillDir`), not through two copies of
1329
+ * one expression. The read side had drifted off it and was preferring staging
1330
+ * per root rather than per slug (#3031), which is precisely the drift a shared
1331
+ * predicate makes unrepresentable. */
1286
1332
  async function canonicalBase(collection, workspaceRoot, safeSlug) {
1287
1333
  if (collection.source !== "project") return collection.skillDir;
1288
- const staging = require_discovery.stagingSkillDir(workspaceRoot, safeSlug);
1289
- if (staging !== null && await fileExists(node_path.default.join(staging, "schema.json"))) return staging;
1290
- return collection.skillDir;
1334
+ return await stagedSkillDir(workspaceRoot, safeSlug) ?? collection.skillDir;
1291
1335
  }
1292
1336
  /** Every on-disk schema.json that must reflect the removal. The active
1293
1337
  * `<skillDir>/schema.json` is the discovery anchor and is always present.
@@ -1297,9 +1341,9 @@ async function canonicalBase(collection, workspaceRoot, safeSlug) {
1297
1341
  async function schemaWriteTargets(collection, workspaceRoot, safeSlug) {
1298
1342
  const active = node_path.default.join(collection.skillDir, require_discovery.SCHEMA_FILE);
1299
1343
  if (collection.source !== "project") return [active];
1300
- const staging = require_discovery.stagingSkillDir(workspaceRoot, safeSlug);
1344
+ const staging = await stagedSkillDir(workspaceRoot, safeSlug);
1301
1345
  const targets = [];
1302
- if (staging !== null && await fileExists(node_path.default.join(staging, "schema.json"))) targets.push(node_path.default.join(staging, require_discovery.SCHEMA_FILE));
1346
+ if (staging !== null) targets.push(node_path.default.join(staging, require_discovery.SCHEMA_FILE));
1303
1347
  targets.push(active);
1304
1348
  return targets;
1305
1349
  }
@@ -1878,24 +1922,53 @@ function openItemsFileRefusal(err, shown) {
1878
1922
  * descriptor's inode is the one reachable at a contained path. A hardlink
1879
1923
  * would satisfy it, but the agent cannot create one across the mount boundary
1880
1924
  * — only the workspace is mounted, and a hardlink cannot cross filesystems. */
1881
- async function verifyOpenedItemsFile(handle, hostPath, root, shown) {
1882
- const opened = await handle.stat();
1925
+ /** What the filesystem says about the path the descriptor was opened from:
1926
+ * the link itself (is it a symlink?), the resolved target, the root resolved
1927
+ * BY THE SAME API, and the target's stat.
1928
+ *
1929
+ * Resolving the two sides with different apis is not a style question: on
1930
+ * Windows `realpath` expands an 8.3 short name and `realpathSync` does not, so
1931
+ * a root from `os.tmpdir()` compared against an async-resolved file inside it
1932
+ * read as "outside the workspace" and refused every itemsFile (#2972). */
1933
+ async function resolveOpenedPaths(hostPath, root) {
1934
+ const link = await (0, node_fs_promises.lstat)(hostPath);
1935
+ const real = await (0, node_fs_promises.realpath)(hostPath);
1936
+ return {
1937
+ link,
1938
+ real,
1939
+ rootReal: await (0, node_fs_promises.realpath)(root),
1940
+ atPath: await (0, node_fs_promises.stat)(real)
1941
+ };
1942
+ }
1943
+ /** Judge the opened descriptor against the paths it claims to be. Returns the
1944
+ * refusal text, or null when the descriptor is the contained file it should
1945
+ * be. `opened` is the descriptor's own stat, so the identity check compares
1946
+ * what was OPENED with what is reachable at the path. */
1947
+ function openedFileProblem(paths, opened, shown) {
1948
+ if (paths.link.isSymbolicLink()) return symlinkRefusal(shown);
1949
+ if (!require_discovery.isUnderRealRoot(paths.real, paths.rootReal)) return outsideWorkspaceRefusal(shown);
1950
+ if (paths.atPath.ino !== opened.ino || paths.atPath.dev !== opened.dev) return `manageCollection: \`itemsFile\` '${shown}' changed while it was being opened. Nothing was read; write the file, then call putItems.`;
1951
+ return null;
1952
+ }
1953
+ /** The descriptor's own shape: a regular file, within the byte cap. Returns the
1954
+ * refusal text, or null. Checked before any path work — a fifo or an 8 MiB
1955
+ * blob is refused without paying for a realpath. */
1956
+ function openedShapeProblem(opened, shown) {
1883
1957
  if (!opened.isFile()) return `manageCollection: \`itemsFile\` '${shown}' is not a regular file. It must be a JSON file holding an array of record objects.`;
1884
1958
  if (opened.size > 8388608) return `manageCollection: \`itemsFile\` '${shown}' is ${opened.size} bytes, over the limit of ${MAX_ITEMS_FILE_BYTES}. Nothing was read; split the rows across several files and call once per file.`;
1885
- let real;
1886
- let atPath;
1887
- let link;
1959
+ return null;
1960
+ }
1961
+ async function verifyOpenedItemsFile(handle, hostPath, root, shown) {
1962
+ const opened = await handle.stat();
1963
+ const shapeProblem = openedShapeProblem(opened, shown);
1964
+ if (shapeProblem !== null) return shapeProblem;
1965
+ let paths;
1888
1966
  try {
1889
- link = await (0, node_fs_promises.lstat)(hostPath);
1890
- real = await (0, node_fs_promises.realpath)(hostPath);
1891
- atPath = await (0, node_fs_promises.stat)(real);
1967
+ paths = await resolveOpenedPaths(hostPath, root);
1892
1968
  } catch (err) {
1893
1969
  return openItemsFileRefusal(err, shown);
1894
1970
  }
1895
- if (link.isSymbolicLink()) return symlinkRefusal(shown);
1896
- if (!require_discovery.isContainedInRoot(real, root)) return outsideWorkspaceRefusal(shown);
1897
- if (atPath.ino !== opened.ino || atPath.dev !== opened.dev) return `manageCollection: \`itemsFile\` '${shown}' changed while it was being opened. Nothing was read; write the file, then call putItems.`;
1898
- return { size: opened.size };
1971
+ return openedFileProblem(paths, opened, shown) ?? { size: opened.size };
1899
1972
  }
1900
1973
  /** Open the file ONCE, then prove that descriptor is the contained file.
1901
1974
  * `O_NOFOLLOW` refuses a symlink outright rather than resolving it, and
@@ -2335,7 +2408,7 @@ var MANAGE_COLLECTION_DEFINITION = {
2335
2408
  },
2336
2409
  schema: {
2337
2410
  type: "object",
2338
- description: "putSchema: the full collection schema object (same shape as schema.json — title, icon, dataPath, primaryKey, fields, …). Call getSchema first for the current one, and schemaDocs for the field DSL."
2411
+ description: "putSchema: the full collection schema object (same shape as schema.json — title, icon, dataPath, primaryKey, fields, …). `icon` is either a Material Symbols name (lowercase letters, digits and underscores only — `podcasts`, `rss_feed`, `3d_rotation`; capitals/spaces/hyphens never match) or a SINGLE emoji. Prefer an emoji when the collection would otherwise share a look-alike glyph with another one — it is in colour and tells them apart in the launcher. `color` is an OPTIONAL accent (violet | indigo | sky | teal | emerald | lime | fuchsia) drawn as a pale chip behind the launcher glyph — another way to separate collections that share a generic icon. Omit it rather than guessing; an unknown name simply draws no accent. Call getSchema first for the current one, and schemaDocs for the field DSL."
2339
2412
  },
2340
2413
  topic: {
2341
2414
  type: "string",
@@ -2574,6 +2647,12 @@ Object.defineProperty(exports, "schemaRelations", {
2574
2647
  return schemaRelations;
2575
2648
  }
2576
2649
  });
2650
+ Object.defineProperty(exports, "stagedSkillDir", {
2651
+ enumerable: true,
2652
+ get: function() {
2653
+ return stagedSkillDir;
2654
+ }
2655
+ });
2577
2656
  Object.defineProperty(exports, "successorId", {
2578
2657
  enumerable: true,
2579
2658
  get: function() {
@@ -2593,4 +2672,4 @@ Object.defineProperty(exports, "validateRecordObject", {
2593
2672
  }
2594
2673
  });
2595
2674
 
2596
- //# sourceMappingURL=server-CF1qllGV.cjs.map
2675
+ //# sourceMappingURL=server-DXUt34Rt.cjs.map