@mulmoclaude/core 3.0.1 → 3.2.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 (73) hide show
  1. package/assets/helps/collection-skills.md +72 -8
  2. package/assets/helps/custom-view.md +9 -1
  3. package/assets/helps/error-recovery.md +36 -0
  4. package/assets/helps/feeds.md +6 -4
  5. package/dist/calendarGrid-Csy2rpjp.js.map +1 -1
  6. package/dist/calendarGrid-p8K-7cuB.cjs.map +1 -1
  7. package/dist/collection/core/presentCollection.d.ts +49 -0
  8. package/dist/collection/core/schema.d.ts +16 -1
  9. package/dist/collection/index.cjs +56 -7
  10. package/dist/collection/index.cjs.map +1 -1
  11. package/dist/collection/index.js +53 -8
  12. package/dist/collection/index.js.map +1 -1
  13. package/dist/collection/registry/server/importWriter.d.ts +2 -1
  14. package/dist/collection/registry/server/index.cjs +31 -10
  15. package/dist/collection/registry/server/index.cjs.map +1 -1
  16. package/dist/collection/registry/server/index.js +30 -9
  17. package/dist/collection/registry/server/index.js.map +1 -1
  18. package/dist/collection/server/host.d.ts +34 -4
  19. package/dist/collection/server/index.cjs +4 -2
  20. package/dist/collection/server/index.d.ts +1 -1
  21. package/dist/collection/server/index.js +4 -3
  22. package/dist/collection/server/manageTool.d.ts +19 -0
  23. package/dist/collection/server/schemaDocs.d.ts +5 -1
  24. package/dist/collection-watchers/config.d.ts +15 -2
  25. package/dist/collection-watchers/index.cjs +310 -198
  26. package/dist/collection-watchers/index.cjs.map +1 -1
  27. package/dist/collection-watchers/index.js +310 -198
  28. package/dist/collection-watchers/index.js.map +1 -1
  29. package/dist/collection-watchers/reconciler.d.ts +1 -5
  30. package/dist/collection-watchers/watcher.d.ts +31 -26
  31. package/dist/{discovery-4Z-ZWFam.js → discovery-CC7kuwHf.js} +21 -7
  32. package/dist/discovery-CC7kuwHf.js.map +1 -0
  33. package/dist/{discovery-CtiZMlsX.cjs → discovery-CDOjfctD.cjs} +33 -7
  34. package/dist/discovery-CDOjfctD.cjs.map +1 -0
  35. package/dist/feeds/server/host.d.ts +12 -0
  36. package/dist/feeds/server/index.cjs +27 -6
  37. package/dist/feeds/server/index.cjs.map +1 -1
  38. package/dist/feeds/server/index.d.ts +1 -1
  39. package/dist/feeds/server/index.js +27 -7
  40. package/dist/feeds/server/index.js.map +1 -1
  41. package/dist/feeds/server/scheduledRefresh.d.ts +16 -1
  42. package/dist/files/index.cjs +6 -5
  43. package/dist/files/index.cjs.map +1 -1
  44. package/dist/files/index.d.ts +1 -0
  45. package/dist/files/index.js +2 -2
  46. package/dist/files/root.d.ts +15 -0
  47. package/dist/google/index.cjs +3 -3
  48. package/dist/google/index.cjs.map +1 -1
  49. package/dist/google/index.js +2 -2
  50. package/dist/remote-host/index.cjs +3 -1
  51. package/dist/remote-host/index.d.ts +30 -0
  52. package/dist/remote-host/index.js +2 -2
  53. package/dist/remote-host/server/index.cjs +1 -1
  54. package/dist/remote-host/server/index.js +1 -1
  55. package/dist/{remote-host-Dba4lF3Z.js → remote-host-CFnl1I-L.js} +35 -2
  56. package/dist/{remote-host-Dba4lF3Z.js.map → remote-host-CFnl1I-L.js.map} +1 -1
  57. package/dist/{remote-host-CtQjagPt.cjs → remote-host-EQPlccB9.cjs} +46 -1
  58. package/dist/{remote-host-CtQjagPt.cjs.map → remote-host-EQPlccB9.cjs.map} +1 -1
  59. package/dist/{atomic-DPpdrJzO.js → root-BMroU_mB.js} +21 -2
  60. package/dist/root-BMroU_mB.js.map +1 -0
  61. package/dist/{atomic-DhUk8uiM.cjs → root-rPH6FGDT.cjs} +26 -1
  62. package/dist/root-rPH6FGDT.cjs.map +1 -0
  63. package/dist/{server-CedIocRS.js → server-AA2kfDfk.js} +137 -42
  64. package/dist/server-AA2kfDfk.js.map +1 -0
  65. package/dist/{server-DOZFzx5l.cjs → server-WU35UYlF.cjs} +138 -43
  66. package/dist/server-WU35UYlF.cjs.map +1 -0
  67. package/package.json +1 -1
  68. package/dist/atomic-DPpdrJzO.js.map +0 -1
  69. package/dist/atomic-DhUk8uiM.cjs.map +0 -1
  70. package/dist/discovery-4Z-ZWFam.js.map +0 -1
  71. package/dist/discovery-CtiZMlsX.cjs.map +0 -1
  72. package/dist/server-CedIocRS.js.map +0 -1
  73. package/dist/server-DOZFzx5l.cjs.map +0 -1
@@ -1,11 +1,11 @@
1
1
  import { a as isErrorWithCode, c as isStringArray, l as isUnknownArray, s as isRecord, t as errorMessage } from "./dist-D8zokgGo.js";
2
- import { t as writeFileAtomic } from "./atomic-DPpdrJzO.js";
2
+ import { n as writeFileAtomic } from "./root-BMroU_mB.js";
3
3
  import { n as toPosixRelPath } from "./relPath-DW8MC8VO.js";
4
4
  import { A as fieldTextOrNull, E as isFieldDrivenEvery, O as storageKindFor, S as COMPUTED_TYPES, T as embedTargetId, k as fieldText, l as parseIsoDate, u as parseIsoDateTime } from "./calendarGrid-Csy2rpjp.js";
5
5
  import { C as actionVisible, _ as uniqueRefTargets, b as projectBacklinkRow, c as selectDynamicRecord, f as itemIsDone, g as uniqueEmbedTargets, h as uniqueBacklinkSources, i as ownProp, n as deriveAll, o as firstDateField, s as resolveIcon, t as defangForPrompt, v as backlinkRows, x as rollupValue, y as coerceNumeric } from "./promptSafety-ACXw_2Yu.js";
6
- import { B as resolveDataDir, F as resolveCreateItemId, G as archiveDir, L as SCHEMA_FILE$1, M as isRegularFile, R as isContainedInRoot, S as queryCsv, T as CollectionQueryZ, U as safeSlugName, V as resolveTemplatePath, X as isPresetSlug$1, Y as getWorkspaceRoot, Z as log, c as resolveMutateSet, d as storeFor, f as checkpointSqliteDatabase, g as cacheDir, i as resolvePrimaryField, k as isBackendUnavailable, n as discoverCollections, r as loadCollection, s as CollectionSchemaZ, tt as skillsStagingDir, u as readOnlyRefusal, w as compileJsonlQuery, x as normalizeCsvValue } from "./discovery-4Z-ZWFam.js";
6
+ import { B as resolveDataDir, F as resolveCreateItemId, G as archiveDir, L as SCHEMA_FILE$1, M as isRegularFile, R as isContainedInRoot, S as queryCsv, T as CollectionQueryZ, U as safeSlugName, V as resolveTemplatePath, X as isPresetSlug$1, Y as getWorkspaceRoot, Z as log, c as resolveMutateSet, d as storeFor, f as checkpointSqliteDatabase, g as cacheDir, i as resolvePrimaryField, k as isBackendUnavailable, n as discoverCollections, r as loadCollection, rt as stagingSkillDir, s as CollectionSchemaZ, u as readOnlyRefusal, w as compileJsonlQuery, x as normalizeCsvValue } from "./discovery-CC7kuwHf.js";
7
7
  import { ingestStatePath } from "./feeds/paths.js";
8
- import { dataSkillDir, mirrorSkillWrite } from "./skill-bridge/index.js";
8
+ import { mirrorSkillWrite } from "./skill-bridge/index.js";
9
9
  import path from "node:path";
10
10
  import { randomBytes, randomUUID } from "node:crypto";
11
11
  import { cp, lstat, mkdir, open, readFile, readdir, rm, rmdir, stat, unlink, writeFile } from "node:fs/promises";
@@ -37,7 +37,8 @@ async function readSourceAwareFile(collection, relPath, opts) {
37
37
  const safeSlug = safeSlugName(collection.slug);
38
38
  if (safeSlug === null) return null;
39
39
  const workspaceRoot = opts.workspaceRoot ?? getWorkspaceRoot();
40
- const bases = collection.source === "project" ? [path.join(skillsStagingDir(workspaceRoot), safeSlug), collection.skillDir] : [collection.skillDir];
40
+ const staging = collection.source === "project" ? stagingSkillDir(workspaceRoot, safeSlug) : null;
41
+ const bases = staging === null ? [collection.skillDir] : [staging, collection.skillDir];
41
42
  for (const base of bases) {
42
43
  const resolved = resolveTemplatePath(base, relPath);
43
44
  if (resolved === null) continue;
@@ -1024,10 +1025,6 @@ function deleteCollectionRefusalMessage(result) {
1024
1025
  "path-escape": `a directory for collection '${slug}' escapes the workspace`
1025
1026
  }[result.kind];
1026
1027
  }
1027
- /** The canonical staging dir for a slug: `data/skills/<slug>`. */
1028
- function stagingSkillDir(workspaceRoot, slug) {
1029
- return path.join(skillsStagingDir(workspaceRoot), slug);
1030
- }
1031
1028
  async function pathExists(target) {
1032
1029
  try {
1033
1030
  await stat(target);
@@ -1043,8 +1040,9 @@ function todayStamp() {
1043
1040
  /** Every directory the delete will touch must resolve under the
1044
1041
  * workspace root — guards against a symlinked ancestor escaping it. */
1045
1042
  function deleteTargets(collection, workspaceRoot) {
1043
+ const staging = stagingSkillDir(workspaceRoot, collection.slug);
1046
1044
  return [
1047
- stagingSkillDir(workspaceRoot, collection.slug),
1045
+ ...staging === null ? [] : [staging],
1048
1046
  collection.skillDir,
1049
1047
  collection.dataDir,
1050
1048
  ingestStatePath(collection.slug, workspaceRoot),
@@ -1095,14 +1093,22 @@ function restoreRecordsStep(schema) {
1095
1093
  files, not to these records (there may be many; copy them, do not
1096
1094
  Write them one by one).`;
1097
1095
  }
1098
- function buildRestoreDoc(collection) {
1099
- const { slug, schema } = collection;
1100
- return `# Restore "${schema.title}" (collection \`${slug}\`)
1101
-
1102
- This folder is an automatic backup made when the collection was deleted.
1103
- Follow these steps to restore it.
1096
+ /** Step 1 of the restore doc, which depends on the root's authoring layout.
1097
+ *
1098
+ * A staged root routes around the `.claude/` permission gate and relies on the
1099
+ * bridge hook; a root with no staging tree has neither, so the same
1100
+ * instructions would have the user restore into `data/skills/<slug>/`, where
1101
+ * nothing would ever read it — the collection would stay deleted while the
1102
+ * document said it was restored. */
1103
+ function restoreSkillStep(slug, staged) {
1104
+ if (!staged) return `1. Recreate the skill files in \`.claude/skills/${slug}/\`: read each
1105
+ file under \`skill/\` and write it to the matching path — \`SKILL.md\`,
1106
+ \`schema.json\`, any \`templates/*\`, and any \`views/*\`.
1104
1107
 
1105
- 1. Recreate the skill files in \`data/skills/${slug}/\` using the **Write
1108
+ This project has no \`data/skills/\` staging tree and no bridge hook, so
1109
+ the skill directory is read directly and no mirroring step follows. Do
1110
+ NOT restore into \`data/skills/${slug}/\` — nothing reads it there.`;
1111
+ return `1. Recreate the skill files in \`data/skills/${slug}/\` using the **Write
1106
1112
  tool**: read each file under \`skill/\` and Write it to the matching
1107
1113
  path — \`SKILL.md\`, \`schema.json\`, and any \`templates/*\`.
1108
1114
 
@@ -1113,7 +1119,16 @@ Follow these steps to restore it.
1113
1119
  \`cp\` would leave the files in staging with no \`.claude/skills/\`
1114
1120
  mirror — the collection would stay invisible. (Writing
1115
1121
  \`.claude/skills/\` directly is not an option either: that path is
1116
- permission-gated.)
1122
+ permission-gated.)`;
1123
+ }
1124
+ function buildRestoreDoc(collection, staged) {
1125
+ const { slug, schema } = collection;
1126
+ return `# Restore "${schema.title}" (collection \`${slug}\`)
1127
+
1128
+ This folder is an automatic backup made when the collection was deleted.
1129
+ Follow these steps to restore it.
1130
+
1131
+ ${restoreSkillStep(slug, staged)}
1117
1132
 
1118
1133
  ${restoreRecordsStep(schema)}
1119
1134
 
@@ -1127,7 +1142,7 @@ ${restoreRecordsStep(schema)}
1127
1142
  /** Copy one skill copy + the records + RESTORE.md into `archiveDir`. */
1128
1143
  async function writeArchive(collection, archiveDir, workspaceRoot) {
1129
1144
  const staging = stagingSkillDir(workspaceRoot, collection.slug);
1130
- const skillSrc = await pathExists(staging) ? staging : collection.skillDir;
1145
+ const skillSrc = staging !== null && await pathExists(staging) ? staging : collection.skillDir;
1131
1146
  await cp(skillSrc, path.join(archiveDir, "skill"), { recursive: true });
1132
1147
  if (await pathExists(collection.dataDir)) await cp(collection.dataDir, path.join(archiveDir, "records"), { recursive: true });
1133
1148
  if (collection.storageFile !== void 0 && await pathExists(collection.storageFile)) {
@@ -1142,13 +1157,14 @@ async function writeArchive(collection, archiveDir, workspaceRoot) {
1142
1157
  if (await pathExists(sidecar)) await cp(sidecar, path.join(archiveDir, path.basename(sidecar)));
1143
1158
  }
1144
1159
  }
1145
- await writeFile(path.join(archiveDir, "RESTORE.md"), buildRestoreDoc(collection), "utf-8");
1160
+ await writeFile(path.join(archiveDir, "RESTORE.md"), buildRestoreDoc(collection, staging !== null), "utf-8");
1146
1161
  }
1147
1162
  /** Remove all three locations. `rm -rf`-style (force) so a missing dir
1148
1163
  * is a no-op; the now-empty data parent (`data/<slug>/` after its
1149
1164
  * `items/` is gone) is swept too, but only when empty. */
1150
1165
  async function removeLocations(collection, workspaceRoot) {
1151
- await rm(stagingSkillDir(workspaceRoot, collection.slug), {
1166
+ const staging = stagingSkillDir(workspaceRoot, collection.slug);
1167
+ if (staging !== null) await rm(staging, {
1152
1168
  recursive: true,
1153
1169
  force: true
1154
1170
  });
@@ -1232,8 +1248,8 @@ async function fileExists(target) {
1232
1248
  * deletes agree on both layouts. */
1233
1249
  async function canonicalBase(collection, workspaceRoot, safeSlug) {
1234
1250
  if (collection.source !== "project") return collection.skillDir;
1235
- const staging = path.join(skillsStagingDir(workspaceRoot), safeSlug);
1236
- if (await fileExists(path.join(staging, "schema.json"))) return staging;
1251
+ const staging = stagingSkillDir(workspaceRoot, safeSlug);
1252
+ if (staging !== null && await fileExists(path.join(staging, "schema.json"))) return staging;
1237
1253
  return collection.skillDir;
1238
1254
  }
1239
1255
  /** Every on-disk schema.json that must reflect the removal. The active
@@ -1244,9 +1260,9 @@ async function canonicalBase(collection, workspaceRoot, safeSlug) {
1244
1260
  async function schemaWriteTargets(collection, workspaceRoot, safeSlug) {
1245
1261
  const active = path.join(collection.skillDir, SCHEMA_FILE$1);
1246
1262
  if (collection.source !== "project") return [active];
1247
- const stagingSchema = path.join(skillsStagingDir(workspaceRoot), safeSlug, SCHEMA_FILE$1);
1263
+ const staging = stagingSkillDir(workspaceRoot, safeSlug);
1248
1264
  const targets = [];
1249
- if (await fileExists(stagingSchema)) targets.push(stagingSchema);
1265
+ if (staging !== null && await fileExists(path.join(staging, "schema.json"))) targets.push(path.join(staging, SCHEMA_FILE$1));
1250
1266
  targets.push(active);
1251
1267
  return targets;
1252
1268
  }
@@ -1304,6 +1320,43 @@ async function deleteCustomView(collection, viewId, opts = {}) {
1304
1320
  viewId
1305
1321
  };
1306
1322
  }
1323
+ //#endregion
1324
+ //#region src/collection/server/schemaDocs.ts
1325
+ /** Fenced authoring variants inside the reference doc.
1326
+ *
1327
+ * A collection's authoring LOCATION depends on the root it lives in, and the
1328
+ * doc is served to the agent by both hosts:
1329
+ *
1330
+ * - a MANAGED workspace gates writes into `.claude/` and runs a skill-bridge
1331
+ * hook, so the agent authors under `data/skills/<slug>/` and the hook
1332
+ * mirrors it across (`staged`);
1333
+ * - a plain project root has neither gate nor bridge, so `data/skills/` is
1334
+ * never discovered — writing there produces nothing, silently — and the
1335
+ * agent must author under `.claude/skills/<slug>/` (`direct`).
1336
+ *
1337
+ * The two variants are fenced in the markdown so everything else stays shared
1338
+ * and the doc keeps reading as one document. Fence lines are removed with
1339
+ * their newline, so the `staged` render is byte-identical to the doc as it was
1340
+ * before the fences existed — which is what protects the single-workspace
1341
+ * host. */
1342
+ var VARIANT_FENCE = /^<!-- \/?authoring:(staged|direct) -->$/;
1343
+ /** Strip the fences and drop whichever variant's body doesn't apply. Runs
1344
+ * BEFORE section parsing, so a variant may contain headings or code fences. */
1345
+ function applyAuthoringVariant(doc, variant) {
1346
+ if (!doc.includes("<!-- authoring:")) return doc;
1347
+ const kept = [];
1348
+ let dropping = null;
1349
+ for (const line of doc.split("\n")) {
1350
+ const match = VARIANT_FENCE.exec(line.trim());
1351
+ if (match) {
1352
+ const [, name = ""] = match;
1353
+ dropping = line.trim().startsWith("<!-- /") ? null : name === variant ? null : name;
1354
+ continue;
1355
+ }
1356
+ if (dropping === null) kept.push(line);
1357
+ }
1358
+ return kept.join("\n");
1359
+ }
1307
1360
  /** Ceiling for ANY assembled reply — topic or default — safely inside
1308
1361
  * the agent's per-result limit (the full doc is what overflowed it).
1309
1362
  * A matched section too large to fit degrades to its own prose plus a
@@ -1444,7 +1497,8 @@ function renderTopic(lines, sections, topic) {
1444
1497
  * up outside any single piece's budget, and the ceiling must hold no
1445
1498
  * matter what shape of document arrives. Only the explicit \`"all"\`
1446
1499
  * opt-in and the small-doc verbatim path may exceed it. */
1447
- function renderSchemaDocs(doc, topic) {
1500
+ function renderSchemaDocs(rawDoc, topic, variant = "staged") {
1501
+ const doc = applyAuthoringVariant(rawDoc, variant);
1448
1502
  const requested = topic?.trim() ?? "";
1449
1503
  if (normalize(requested) === "all") return doc;
1450
1504
  if (doc.length <= 2e4) return doc;
@@ -1474,11 +1528,33 @@ var MAX_SCHEMA_ISSUES = 20;
1474
1528
  /** The workspace help-docs dir both hosts seed (`@mulmoclaude/core/workspace-setup`
1475
1529
  * syncs the bundled assets here) — the user-editable copy schemaDocs prefers. */
1476
1530
  var HELPS_DIR = "config/helps";
1531
+ /** `schemaDocs` names no collection, but the authoring layout is resolved per
1532
+ * slug (staging is `<staging>/<slug>`). This stands in, so the doc variant and
1533
+ * a later `putSchema` on any slug in this root cannot disagree. */
1534
+ var SCHEMA_DOCS_PROBE_SLUG = "_";
1477
1535
  /** Resolve the workspace root the same way every collections call does:
1478
1536
  * the injected override (tests) or the configured collection host. */
1479
1537
  function resolveBase(deps) {
1480
1538
  return deps.workspaceRoot ?? getWorkspaceRoot();
1481
1539
  }
1540
+ /** Where a collection skill is authored in THIS root, and therefore both which
1541
+ * authoring guide `schemaDocs` serves and where `getSchema` / `putSchema`
1542
+ * read and write. `null` staging means "author in the active skill dir".
1543
+ *
1544
+ * ONE predicate on purpose. Two knobs describe this — the host's
1545
+ * `stagedSkillAuthoring` and its `skillsStagingDir` — and either alone can
1546
+ * disagree with the other: a host that says `stagedSkillAuthoring: false`
1547
+ * while still returning a staging path would have the agent told to write
1548
+ * `.claude/skills/<slug>/` while `putSchema` wrote `data/skills/` and mirrored
1549
+ * from there, so the tool would silently contradict its own documentation.
1550
+ * Staged requires BOTH to agree; anything else is direct. */
1551
+ function authoringTarget(deps, slug) {
1552
+ const stagingDir = deps.stagedSkillAuthoring === false ? null : stagingSkillDir(resolveBase(deps), slug);
1553
+ return {
1554
+ variant: stagingDir === null ? "direct" : "staged",
1555
+ stagingDir
1556
+ };
1557
+ }
1482
1558
  /** Shared "unknown collection" message — its schema.json is missing or
1483
1559
  * failed validation, so discovery skipped it. */
1484
1560
  function unknownCollection(slug) {
@@ -1745,9 +1821,10 @@ async function handleGetOntology(deps) {
1745
1821
  * fallback. Both reads guarded; if neither resolves the agent still
1746
1822
  * gets an actionable message instead of a thrown call. */
1747
1823
  async function handleSchemaDocs(deps, topic) {
1824
+ const { variant } = authoringTarget(deps, SCHEMA_DOCS_PROBE_SLUG);
1748
1825
  const candidates = [path.join(resolveBase(deps), HELPS_DIR, SCHEMA_DOCS_FILE), ...deps.bundledHelpsDir ? [path.join(deps.bundledHelpsDir(), SCHEMA_DOCS_FILE)] : []];
1749
1826
  for (const candidate of candidates) try {
1750
- return renderSchemaDocs(await readFile(candidate, "utf-8"), topic);
1827
+ return renderSchemaDocs(await readFile(candidate, "utf-8"), topic, variant);
1751
1828
  } catch {}
1752
1829
  return `manageCollection: could not read the collection-authoring reference (${SCHEMA_DOCS_FILE}).`;
1753
1830
  }
@@ -1758,16 +1835,25 @@ async function handleSchemaDocs(deps, topic) {
1758
1835
  async function handleGetSchema(slug, deps) {
1759
1836
  const collection = await loadCollection(slug, deps);
1760
1837
  if (!collection) return unknownCollection(slug);
1761
- const candidates = [path.join(dataSkillDir(resolveBase(deps), collection.slug), SCHEMA_FILE), path.join(collection.skillDir, SCHEMA_FILE)];
1838
+ const { stagingDir } = authoringTarget(deps, collection.slug);
1839
+ const candidates = [...stagingDir === null ? [] : [path.join(stagingDir, SCHEMA_FILE)], path.join(collection.skillDir, SCHEMA_FILE)];
1762
1840
  for (const candidate of candidates) try {
1763
1841
  return await readFile(candidate, "utf-8");
1764
1842
  } catch {}
1765
1843
  return `manageCollection: '${defangForPrompt(slug)}' has no readable ${SCHEMA_FILE}.`;
1766
1844
  }
1845
+ /** Where the agent should CREATE a collection skill in this root, named in the
1846
+ * messages that tell it to. Naming `data/skills/` under a root that has no
1847
+ * staging tree sends the agent to a directory nothing reads — the exact
1848
+ * silent failure the root-aware authoring guide exists to prevent, reappearing
1849
+ * in an error string the agent is far more likely to act on immediately. */
1850
+ function authoringDirLabel(deps, slug) {
1851
+ return authoringTarget(deps, slug).variant === "staged" ? `data/skills/${slug}/` : `.claude/skills/${slug}/`;
1852
+ }
1767
1853
  /** Refuse a schema edit the host can't honour: user-scope/feed collections
1768
1854
  * are read-only, and presets (mc-*) re-seed on restart so an edit is lost. */
1769
- function schemaEditRefusal(collection, slug) {
1770
- if (collection.source !== "project") return `manageCollection: '${defangForPrompt(slug)}' is ${collection.source}-scope and read-only here — only project collections (data/skills/) can be edited.`;
1855
+ function schemaEditRefusal(collection, slug, deps) {
1856
+ if (collection.source !== "project") return `manageCollection: '${defangForPrompt(slug)}' is ${collection.source}-scope and read-only here — only project collections (${authoringDirLabel(deps, "<slug>")}) can be edited.`;
1771
1857
  if (isPresetSlug(slug)) return `manageCollection: '${defangForPrompt(slug)}' is a preset (mc-*) and re-seeds on restart — copy it under a different slug to customise.`;
1772
1858
  return null;
1773
1859
  }
@@ -1781,14 +1867,23 @@ function formatSchemaIssues(issues) {
1781
1867
  }
1782
1868
  /** Write the validated schema to the canonical staging copy, then mirror
1783
1869
  * it into the active `.claude/skills/` tree discovery reads — an internal
1784
- * write doesn't fire the skill-bridge hook, so we mirror explicitly. */
1785
- async function writeAndMirrorSchema(slug, schema, deps) {
1870
+ * write doesn't fire the skill-bridge hook, so we mirror explicitly.
1871
+ *
1872
+ * A root with NO staging tree has no bridge to mirror through either, so the
1873
+ * active skill dir IS the canonical copy and is written directly. Mirroring
1874
+ * there would read a staging file that never exists. */
1875
+ async function writeAndMirrorSchema(slug, skillDir, schema, deps) {
1786
1876
  const base = resolveBase(deps);
1787
- await writeFileAtomic(path.join(dataSkillDir(base, slug), SCHEMA_FILE), `${JSON.stringify(schema, null, 2)}\n`);
1788
- mirrorSkillWrite(base, {
1789
- slug,
1790
- relSegments: [SCHEMA_FILE]
1791
- });
1877
+ const serialized = `${JSON.stringify(schema, null, 2)}\n`;
1878
+ const { stagingDir } = authoringTarget(deps, slug);
1879
+ if (stagingDir === null) await writeFileAtomic(path.join(skillDir, SCHEMA_FILE), serialized);
1880
+ else {
1881
+ await writeFileAtomic(path.join(stagingDir, SCHEMA_FILE), serialized);
1882
+ mirrorSkillWrite(base, {
1883
+ slug,
1884
+ relSegments: [SCHEMA_FILE]
1885
+ });
1886
+ }
1792
1887
  try {
1793
1888
  await deps.refreshAfterWrite?.();
1794
1889
  } catch {}
@@ -1808,19 +1903,19 @@ function schemaDiscoveryGate(schema, base) {
1808
1903
  return null;
1809
1904
  }
1810
1905
  /** Validate a schema against CollectionSchemaZ and, on success, persist it.
1811
- * Edit-only: a new collection is created by writing SKILL.md + schema.json
1812
- * under data/skills/<slug>/ (the normal create flow), not through here. */
1906
+ * Edit-only: a new collection is created by writing SKILL.md + schema.json in
1907
+ * the root's authoring dir (the normal create flow), not through here. */
1813
1908
  async function handlePutSchema(slug, schemaArg, deps) {
1814
1909
  if (!schemaArg || typeof schemaArg !== "object" || Array.isArray(schemaArg)) return "manageCollection: `schema` is required for putSchema — the full collection schema object.";
1815
1910
  const collection = await loadCollection(slug, deps);
1816
- if (!collection) return `manageCollection: unknown collection '${defangForPrompt(slug)}' — create it by writing SKILL.md + ${SCHEMA_FILE} under data/skills/${defangForPrompt(slug)}/, then edit it here.`;
1817
- const refusal = schemaEditRefusal(collection, slug);
1911
+ if (!collection) return `manageCollection: unknown collection '${defangForPrompt(slug)}' — create it by writing SKILL.md + ${SCHEMA_FILE} under ${authoringDirLabel(deps, defangForPrompt(slug))}, then edit it here.`;
1912
+ const refusal = schemaEditRefusal(collection, slug, deps);
1818
1913
  if (refusal) return refusal;
1819
1914
  const parsed = CollectionSchemaZ.safeParse(schemaArg);
1820
1915
  if (!parsed.success) return formatSchemaIssues(parsed.error.issues);
1821
1916
  const gate = schemaDiscoveryGate(parsed.data, resolveBase(deps));
1822
1917
  if (gate) return `manageCollection: schema rejected — ${gate} (call schemaDocs for the field reference). It passes basic validation but discovery would skip it, hiding the collection.`;
1823
- await writeAndMirrorSchema(collection.slug, parsed.data, deps);
1918
+ await writeAndMirrorSchema(collection.slug, collection.skillDir, parsed.data, deps);
1824
1919
  return JSON.stringify({
1825
1920
  collection: collection.slug,
1826
1921
  written: true
@@ -1955,4 +2050,4 @@ function makeManageCollectionTool(deps = {}) {
1955
2050
  //#endregion
1956
2051
  export { buildCollectionActionSeedPrompt as A, validateRecordObject as C, enrichItems as D, runCollectionQuery as E, readCustomViewHtml as M, readCustomViewI18n as N, runQueryOverRows as O, readSkillTemplate as P, validateCollectionRecords as S, recordFieldProblem as T, computeCollectionIcon as _, deleteCollection as a, applyMutateAction as b, computeSuccessor as c, isTriggerDue as d, maybeSpawnSuccessor as f, ONE_SECOND_MS as g, successorId as h, deleteCustomView as i, promptPathsFor as j, buildActionSeedPrompt as k, daysInMonth as l, resolveEvery as m, MAX_UNSELECTIVE_ITEMS as n, deleteCollectionRefusalMessage as o, parseCivil as p, makeManageCollectionTool as r, advanceTriggerDate as s, MAX_SCHEMA_ISSUES as t, formatCivil as u, buildWorkspaceOntology as v, compileRecordZ as w, firstMutateParamProblem as x, schemaRelations as y };
1957
2052
 
1958
- //# sourceMappingURL=server-CedIocRS.js.map
2053
+ //# sourceMappingURL=server-AA2kfDfk.js.map