@mulmoclaude/core 3.0.0 → 3.1.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.
- package/assets/helps/collection-skills.md +72 -8
- package/assets/helps/custom-view.md +9 -1
- package/assets/helps/error-recovery.md +36 -0
- package/assets/helps/feeds.md +6 -4
- package/dist/calendarGrid-Csy2rpjp.js.map +1 -1
- package/dist/calendarGrid-p8K-7cuB.cjs.map +1 -1
- package/dist/collection/core/presentCollection.d.ts +49 -0
- package/dist/collection/core/schema.d.ts +16 -1
- package/dist/collection/index.cjs +56 -7
- package/dist/collection/index.cjs.map +1 -1
- package/dist/collection/index.js +53 -8
- package/dist/collection/index.js.map +1 -1
- package/dist/collection/registry/server/importWriter.d.ts +2 -1
- package/dist/collection/registry/server/index.cjs +31 -10
- package/dist/collection/registry/server/index.cjs.map +1 -1
- package/dist/collection/registry/server/index.js +30 -9
- package/dist/collection/registry/server/index.js.map +1 -1
- package/dist/collection/server/host.d.ts +34 -4
- package/dist/collection/server/index.cjs +4 -2
- package/dist/collection/server/index.d.ts +1 -1
- package/dist/collection/server/index.js +4 -3
- package/dist/collection/server/manageTool.d.ts +19 -0
- package/dist/collection/server/schemaDocs.d.ts +5 -1
- package/dist/collection-watchers/config.d.ts +15 -2
- package/dist/collection-watchers/index.cjs +310 -198
- package/dist/collection-watchers/index.cjs.map +1 -1
- package/dist/collection-watchers/index.js +310 -198
- package/dist/collection-watchers/index.js.map +1 -1
- package/dist/collection-watchers/reconciler.d.ts +1 -5
- package/dist/collection-watchers/watcher.d.ts +31 -26
- package/dist/{discovery-4Z-ZWFam.js → discovery-CC7kuwHf.js} +21 -7
- package/dist/discovery-CC7kuwHf.js.map +1 -0
- package/dist/{discovery-CtiZMlsX.cjs → discovery-CDOjfctD.cjs} +33 -7
- package/dist/discovery-CDOjfctD.cjs.map +1 -0
- package/dist/feeds/server/index.cjs +26 -6
- package/dist/feeds/server/index.cjs.map +1 -1
- package/dist/feeds/server/index.d.ts +1 -1
- package/dist/feeds/server/index.js +26 -7
- package/dist/feeds/server/index.js.map +1 -1
- package/dist/feeds/server/scheduledRefresh.d.ts +16 -1
- package/dist/files/index.cjs +6 -5
- package/dist/files/index.cjs.map +1 -1
- package/dist/files/index.d.ts +1 -0
- package/dist/files/index.js +2 -2
- package/dist/files/root.d.ts +15 -0
- package/dist/google/index.cjs +3 -3
- package/dist/google/index.cjs.map +1 -1
- package/dist/google/index.js +2 -2
- package/dist/remote-host/index.cjs +3 -1
- package/dist/remote-host/index.d.ts +30 -0
- package/dist/remote-host/index.js +2 -2
- package/dist/remote-host/server/index.cjs +38 -14
- package/dist/remote-host/server/index.cjs.map +1 -1
- package/dist/remote-host/server/index.js +38 -14
- package/dist/remote-host/server/index.js.map +1 -1
- package/dist/remote-host/server/monotonicClock.d.ts +3 -0
- package/dist/remote-host/server/presenceBeat.d.ts +3 -2
- package/dist/remote-host/server/resilientRunner.d.ts +6 -0
- package/dist/{remote-host-Dba4lF3Z.js → remote-host-CFnl1I-L.js} +35 -2
- package/dist/{remote-host-Dba4lF3Z.js.map → remote-host-CFnl1I-L.js.map} +1 -1
- package/dist/{remote-host-CtQjagPt.cjs → remote-host-EQPlccB9.cjs} +46 -1
- package/dist/{remote-host-CtQjagPt.cjs.map → remote-host-EQPlccB9.cjs.map} +1 -1
- package/dist/{atomic-DPpdrJzO.js → root-BMroU_mB.js} +21 -2
- package/dist/root-BMroU_mB.js.map +1 -0
- package/dist/{atomic-DhUk8uiM.cjs → root-rPH6FGDT.cjs} +26 -1
- package/dist/root-rPH6FGDT.cjs.map +1 -0
- package/dist/{server-CedIocRS.js → server-AA2kfDfk.js} +137 -42
- package/dist/server-AA2kfDfk.js.map +1 -0
- package/dist/{server-DOZFzx5l.cjs → server-WU35UYlF.cjs} +138 -43
- package/dist/server-WU35UYlF.cjs.map +1 -0
- package/package.json +1 -1
- package/dist/atomic-DPpdrJzO.js.map +0 -1
- package/dist/atomic-DhUk8uiM.cjs.map +0 -1
- package/dist/discovery-4Z-ZWFam.js.map +0 -1
- package/dist/discovery-CtiZMlsX.cjs.map +0 -1
- package/dist/server-CedIocRS.js.map +0 -1
- package/dist/server-DOZFzx5l.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
|
|
3
|
+
const require_root = require("./root-rPH6FGDT.cjs");
|
|
4
4
|
const require_relPath = require("./relPath-CTAjGdCL.cjs");
|
|
5
5
|
const require_calendarGrid = require("./calendarGrid-p8K-7cuB.cjs");
|
|
6
6
|
const require_promptSafety = require("./promptSafety-CNEW3_qS.cjs");
|
|
7
|
-
const require_discovery = require("./discovery-
|
|
7
|
+
const require_discovery = require("./discovery-CDOjfctD.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_path = require("node:path");
|
|
@@ -39,7 +39,8 @@ async function readSourceAwareFile(collection, relPath, opts) {
|
|
|
39
39
|
const safeSlug = require_discovery.safeSlugName(collection.slug);
|
|
40
40
|
if (safeSlug === null) return null;
|
|
41
41
|
const workspaceRoot = opts.workspaceRoot ?? require_discovery.getWorkspaceRoot();
|
|
42
|
-
const
|
|
42
|
+
const staging = collection.source === "project" ? require_discovery.stagingSkillDir(workspaceRoot, safeSlug) : null;
|
|
43
|
+
const bases = staging === null ? [collection.skillDir] : [staging, collection.skillDir];
|
|
43
44
|
for (const base of bases) {
|
|
44
45
|
const resolved = require_discovery.resolveTemplatePath(base, relPath);
|
|
45
46
|
if (resolved === null) continue;
|
|
@@ -1026,10 +1027,6 @@ function deleteCollectionRefusalMessage(result) {
|
|
|
1026
1027
|
"path-escape": `a directory for collection '${slug}' escapes the workspace`
|
|
1027
1028
|
}[result.kind];
|
|
1028
1029
|
}
|
|
1029
|
-
/** The canonical staging dir for a slug: `data/skills/<slug>`. */
|
|
1030
|
-
function stagingSkillDir(workspaceRoot, slug) {
|
|
1031
|
-
return node_path.default.join(require_discovery.skillsStagingDir(workspaceRoot), slug);
|
|
1032
|
-
}
|
|
1033
1030
|
async function pathExists(target) {
|
|
1034
1031
|
try {
|
|
1035
1032
|
await (0, node_fs_promises.stat)(target);
|
|
@@ -1045,8 +1042,9 @@ function todayStamp() {
|
|
|
1045
1042
|
/** Every directory the delete will touch must resolve under the
|
|
1046
1043
|
* workspace root — guards against a symlinked ancestor escaping it. */
|
|
1047
1044
|
function deleteTargets(collection, workspaceRoot) {
|
|
1045
|
+
const staging = require_discovery.stagingSkillDir(workspaceRoot, collection.slug);
|
|
1048
1046
|
return [
|
|
1049
|
-
|
|
1047
|
+
...staging === null ? [] : [staging],
|
|
1050
1048
|
collection.skillDir,
|
|
1051
1049
|
collection.dataDir,
|
|
1052
1050
|
require_feeds_paths.ingestStatePath(collection.slug, workspaceRoot),
|
|
@@ -1097,14 +1095,22 @@ function restoreRecordsStep(schema) {
|
|
|
1097
1095
|
files, not to these records (there may be many; copy them, do not
|
|
1098
1096
|
Write them one by one).`;
|
|
1099
1097
|
}
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1098
|
+
/** Step 1 of the restore doc, which depends on the root's authoring layout.
|
|
1099
|
+
*
|
|
1100
|
+
* A staged root routes around the `.claude/` permission gate and relies on the
|
|
1101
|
+
* bridge hook; a root with no staging tree has neither, so the same
|
|
1102
|
+
* instructions would have the user restore into `data/skills/<slug>/`, where
|
|
1103
|
+
* nothing would ever read it — the collection would stay deleted while the
|
|
1104
|
+
* document said it was restored. */
|
|
1105
|
+
function restoreSkillStep(slug, staged) {
|
|
1106
|
+
if (!staged) return `1. Recreate the skill files in \`.claude/skills/${slug}/\`: read each
|
|
1107
|
+
file under \`skill/\` and write it to the matching path — \`SKILL.md\`,
|
|
1108
|
+
\`schema.json\`, any \`templates/*\`, and any \`views/*\`.
|
|
1106
1109
|
|
|
1107
|
-
|
|
1110
|
+
This project has no \`data/skills/\` staging tree and no bridge hook, so
|
|
1111
|
+
the skill directory is read directly and no mirroring step follows. Do
|
|
1112
|
+
NOT restore into \`data/skills/${slug}/\` — nothing reads it there.`;
|
|
1113
|
+
return `1. Recreate the skill files in \`data/skills/${slug}/\` using the **Write
|
|
1108
1114
|
tool**: read each file under \`skill/\` and Write it to the matching
|
|
1109
1115
|
path — \`SKILL.md\`, \`schema.json\`, and any \`templates/*\`.
|
|
1110
1116
|
|
|
@@ -1115,7 +1121,16 @@ Follow these steps to restore it.
|
|
|
1115
1121
|
\`cp\` would leave the files in staging with no \`.claude/skills/\`
|
|
1116
1122
|
mirror — the collection would stay invisible. (Writing
|
|
1117
1123
|
\`.claude/skills/\` directly is not an option either: that path is
|
|
1118
|
-
permission-gated.)
|
|
1124
|
+
permission-gated.)`;
|
|
1125
|
+
}
|
|
1126
|
+
function buildRestoreDoc(collection, staged) {
|
|
1127
|
+
const { slug, schema } = collection;
|
|
1128
|
+
return `# Restore "${schema.title}" (collection \`${slug}\`)
|
|
1129
|
+
|
|
1130
|
+
This folder is an automatic backup made when the collection was deleted.
|
|
1131
|
+
Follow these steps to restore it.
|
|
1132
|
+
|
|
1133
|
+
${restoreSkillStep(slug, staged)}
|
|
1119
1134
|
|
|
1120
1135
|
${restoreRecordsStep(schema)}
|
|
1121
1136
|
|
|
@@ -1128,8 +1143,8 @@ ${restoreRecordsStep(schema)}
|
|
|
1128
1143
|
}
|
|
1129
1144
|
/** Copy one skill copy + the records + RESTORE.md into `archiveDir`. */
|
|
1130
1145
|
async function writeArchive(collection, archiveDir, workspaceRoot) {
|
|
1131
|
-
const staging = stagingSkillDir(workspaceRoot, collection.slug);
|
|
1132
|
-
const skillSrc = await pathExists(staging) ? staging : collection.skillDir;
|
|
1146
|
+
const staging = require_discovery.stagingSkillDir(workspaceRoot, collection.slug);
|
|
1147
|
+
const skillSrc = staging !== null && await pathExists(staging) ? staging : collection.skillDir;
|
|
1133
1148
|
await (0, node_fs_promises.cp)(skillSrc, node_path.default.join(archiveDir, "skill"), { recursive: true });
|
|
1134
1149
|
if (await pathExists(collection.dataDir)) await (0, node_fs_promises.cp)(collection.dataDir, node_path.default.join(archiveDir, "records"), { recursive: true });
|
|
1135
1150
|
if (collection.storageFile !== void 0 && await pathExists(collection.storageFile)) {
|
|
@@ -1144,13 +1159,14 @@ async function writeArchive(collection, archiveDir, workspaceRoot) {
|
|
|
1144
1159
|
if (await pathExists(sidecar)) await (0, node_fs_promises.cp)(sidecar, node_path.default.join(archiveDir, node_path.default.basename(sidecar)));
|
|
1145
1160
|
}
|
|
1146
1161
|
}
|
|
1147
|
-
await (0, node_fs_promises.writeFile)(node_path.default.join(archiveDir, "RESTORE.md"), buildRestoreDoc(collection), "utf-8");
|
|
1162
|
+
await (0, node_fs_promises.writeFile)(node_path.default.join(archiveDir, "RESTORE.md"), buildRestoreDoc(collection, staging !== null), "utf-8");
|
|
1148
1163
|
}
|
|
1149
1164
|
/** Remove all three locations. `rm -rf`-style (force) so a missing dir
|
|
1150
1165
|
* is a no-op; the now-empty data parent (`data/<slug>/` after its
|
|
1151
1166
|
* `items/` is gone) is swept too, but only when empty. */
|
|
1152
1167
|
async function removeLocations(collection, workspaceRoot) {
|
|
1153
|
-
|
|
1168
|
+
const staging = require_discovery.stagingSkillDir(workspaceRoot, collection.slug);
|
|
1169
|
+
if (staging !== null) await (0, node_fs_promises.rm)(staging, {
|
|
1154
1170
|
recursive: true,
|
|
1155
1171
|
force: true
|
|
1156
1172
|
});
|
|
@@ -1234,8 +1250,8 @@ async function fileExists(target) {
|
|
|
1234
1250
|
* deletes agree on both layouts. */
|
|
1235
1251
|
async function canonicalBase(collection, workspaceRoot, safeSlug) {
|
|
1236
1252
|
if (collection.source !== "project") return collection.skillDir;
|
|
1237
|
-
const staging =
|
|
1238
|
-
if (await fileExists(node_path.default.join(staging, "schema.json"))) return staging;
|
|
1253
|
+
const staging = require_discovery.stagingSkillDir(workspaceRoot, safeSlug);
|
|
1254
|
+
if (staging !== null && await fileExists(node_path.default.join(staging, "schema.json"))) return staging;
|
|
1239
1255
|
return collection.skillDir;
|
|
1240
1256
|
}
|
|
1241
1257
|
/** Every on-disk schema.json that must reflect the removal. The active
|
|
@@ -1246,9 +1262,9 @@ async function canonicalBase(collection, workspaceRoot, safeSlug) {
|
|
|
1246
1262
|
async function schemaWriteTargets(collection, workspaceRoot, safeSlug) {
|
|
1247
1263
|
const active = node_path.default.join(collection.skillDir, require_discovery.SCHEMA_FILE);
|
|
1248
1264
|
if (collection.source !== "project") return [active];
|
|
1249
|
-
const
|
|
1265
|
+
const staging = require_discovery.stagingSkillDir(workspaceRoot, safeSlug);
|
|
1250
1266
|
const targets = [];
|
|
1251
|
-
if (await fileExists(
|
|
1267
|
+
if (staging !== null && await fileExists(node_path.default.join(staging, "schema.json"))) targets.push(node_path.default.join(staging, require_discovery.SCHEMA_FILE));
|
|
1252
1268
|
targets.push(active);
|
|
1253
1269
|
return targets;
|
|
1254
1270
|
}
|
|
@@ -1274,7 +1290,7 @@ async function removeViewFromSchemas(collection, viewId, workspaceRoot, safeSlug
|
|
|
1274
1290
|
views: parsed.views.filter((entry) => !require_dist.isRecord(entry) || entry.id !== viewId)
|
|
1275
1291
|
} : parsed;
|
|
1276
1292
|
const serialized = `${JSON.stringify(next, null, 2)}\n`;
|
|
1277
|
-
for (const target of await schemaWriteTargets(collection, workspaceRoot, safeSlug)) await
|
|
1293
|
+
for (const target of await schemaWriteTargets(collection, workspaceRoot, safeSlug)) await require_root.writeFileAtomic(target, serialized);
|
|
1278
1294
|
}
|
|
1279
1295
|
/** Delete one custom view from `collection`: unlink its HTML file and drop it
|
|
1280
1296
|
* from every schema.json copy. User-scope and preset (mc-*) collections are
|
|
@@ -1306,6 +1322,43 @@ async function deleteCustomView(collection, viewId, opts = {}) {
|
|
|
1306
1322
|
viewId
|
|
1307
1323
|
};
|
|
1308
1324
|
}
|
|
1325
|
+
//#endregion
|
|
1326
|
+
//#region src/collection/server/schemaDocs.ts
|
|
1327
|
+
/** Fenced authoring variants inside the reference doc.
|
|
1328
|
+
*
|
|
1329
|
+
* A collection's authoring LOCATION depends on the root it lives in, and the
|
|
1330
|
+
* doc is served to the agent by both hosts:
|
|
1331
|
+
*
|
|
1332
|
+
* - a MANAGED workspace gates writes into `.claude/` and runs a skill-bridge
|
|
1333
|
+
* hook, so the agent authors under `data/skills/<slug>/` and the hook
|
|
1334
|
+
* mirrors it across (`staged`);
|
|
1335
|
+
* - a plain project root has neither gate nor bridge, so `data/skills/` is
|
|
1336
|
+
* never discovered — writing there produces nothing, silently — and the
|
|
1337
|
+
* agent must author under `.claude/skills/<slug>/` (`direct`).
|
|
1338
|
+
*
|
|
1339
|
+
* The two variants are fenced in the markdown so everything else stays shared
|
|
1340
|
+
* and the doc keeps reading as one document. Fence lines are removed with
|
|
1341
|
+
* their newline, so the `staged` render is byte-identical to the doc as it was
|
|
1342
|
+
* before the fences existed — which is what protects the single-workspace
|
|
1343
|
+
* host. */
|
|
1344
|
+
var VARIANT_FENCE = /^<!-- \/?authoring:(staged|direct) -->$/;
|
|
1345
|
+
/** Strip the fences and drop whichever variant's body doesn't apply. Runs
|
|
1346
|
+
* BEFORE section parsing, so a variant may contain headings or code fences. */
|
|
1347
|
+
function applyAuthoringVariant(doc, variant) {
|
|
1348
|
+
if (!doc.includes("<!-- authoring:")) return doc;
|
|
1349
|
+
const kept = [];
|
|
1350
|
+
let dropping = null;
|
|
1351
|
+
for (const line of doc.split("\n")) {
|
|
1352
|
+
const match = VARIANT_FENCE.exec(line.trim());
|
|
1353
|
+
if (match) {
|
|
1354
|
+
const [, name = ""] = match;
|
|
1355
|
+
dropping = line.trim().startsWith("<!-- /") ? null : name === variant ? null : name;
|
|
1356
|
+
continue;
|
|
1357
|
+
}
|
|
1358
|
+
if (dropping === null) kept.push(line);
|
|
1359
|
+
}
|
|
1360
|
+
return kept.join("\n");
|
|
1361
|
+
}
|
|
1309
1362
|
/** Ceiling for ANY assembled reply — topic or default — safely inside
|
|
1310
1363
|
* the agent's per-result limit (the full doc is what overflowed it).
|
|
1311
1364
|
* A matched section too large to fit degrades to its own prose plus a
|
|
@@ -1446,7 +1499,8 @@ function renderTopic(lines, sections, topic) {
|
|
|
1446
1499
|
* up outside any single piece's budget, and the ceiling must hold no
|
|
1447
1500
|
* matter what shape of document arrives. Only the explicit \`"all"\`
|
|
1448
1501
|
* opt-in and the small-doc verbatim path may exceed it. */
|
|
1449
|
-
function renderSchemaDocs(
|
|
1502
|
+
function renderSchemaDocs(rawDoc, topic, variant = "staged") {
|
|
1503
|
+
const doc = applyAuthoringVariant(rawDoc, variant);
|
|
1450
1504
|
const requested = topic?.trim() ?? "";
|
|
1451
1505
|
if (normalize(requested) === "all") return doc;
|
|
1452
1506
|
if (doc.length <= 2e4) return doc;
|
|
@@ -1476,11 +1530,33 @@ var MAX_SCHEMA_ISSUES = 20;
|
|
|
1476
1530
|
/** The workspace help-docs dir both hosts seed (`@mulmoclaude/core/workspace-setup`
|
|
1477
1531
|
* syncs the bundled assets here) — the user-editable copy schemaDocs prefers. */
|
|
1478
1532
|
var HELPS_DIR = "config/helps";
|
|
1533
|
+
/** `schemaDocs` names no collection, but the authoring layout is resolved per
|
|
1534
|
+
* slug (staging is `<staging>/<slug>`). This stands in, so the doc variant and
|
|
1535
|
+
* a later `putSchema` on any slug in this root cannot disagree. */
|
|
1536
|
+
var SCHEMA_DOCS_PROBE_SLUG = "_";
|
|
1479
1537
|
/** Resolve the workspace root the same way every collections call does:
|
|
1480
1538
|
* the injected override (tests) or the configured collection host. */
|
|
1481
1539
|
function resolveBase(deps) {
|
|
1482
1540
|
return deps.workspaceRoot ?? require_discovery.getWorkspaceRoot();
|
|
1483
1541
|
}
|
|
1542
|
+
/** Where a collection skill is authored in THIS root, and therefore both which
|
|
1543
|
+
* authoring guide `schemaDocs` serves and where `getSchema` / `putSchema`
|
|
1544
|
+
* read and write. `null` staging means "author in the active skill dir".
|
|
1545
|
+
*
|
|
1546
|
+
* ONE predicate on purpose. Two knobs describe this — the host's
|
|
1547
|
+
* `stagedSkillAuthoring` and its `skillsStagingDir` — and either alone can
|
|
1548
|
+
* disagree with the other: a host that says `stagedSkillAuthoring: false`
|
|
1549
|
+
* while still returning a staging path would have the agent told to write
|
|
1550
|
+
* `.claude/skills/<slug>/` while `putSchema` wrote `data/skills/` and mirrored
|
|
1551
|
+
* from there, so the tool would silently contradict its own documentation.
|
|
1552
|
+
* Staged requires BOTH to agree; anything else is direct. */
|
|
1553
|
+
function authoringTarget(deps, slug) {
|
|
1554
|
+
const stagingDir = deps.stagedSkillAuthoring === false ? null : require_discovery.stagingSkillDir(resolveBase(deps), slug);
|
|
1555
|
+
return {
|
|
1556
|
+
variant: stagingDir === null ? "direct" : "staged",
|
|
1557
|
+
stagingDir
|
|
1558
|
+
};
|
|
1559
|
+
}
|
|
1484
1560
|
/** Shared "unknown collection" message — its schema.json is missing or
|
|
1485
1561
|
* failed validation, so discovery skipped it. */
|
|
1486
1562
|
function unknownCollection(slug) {
|
|
@@ -1747,9 +1823,10 @@ async function handleGetOntology(deps) {
|
|
|
1747
1823
|
* fallback. Both reads guarded; if neither resolves the agent still
|
|
1748
1824
|
* gets an actionable message instead of a thrown call. */
|
|
1749
1825
|
async function handleSchemaDocs(deps, topic) {
|
|
1826
|
+
const { variant } = authoringTarget(deps, SCHEMA_DOCS_PROBE_SLUG);
|
|
1750
1827
|
const candidates = [node_path.default.join(resolveBase(deps), HELPS_DIR, SCHEMA_DOCS_FILE), ...deps.bundledHelpsDir ? [node_path.default.join(deps.bundledHelpsDir(), SCHEMA_DOCS_FILE)] : []];
|
|
1751
1828
|
for (const candidate of candidates) try {
|
|
1752
|
-
return renderSchemaDocs(await (0, node_fs_promises.readFile)(candidate, "utf-8"), topic);
|
|
1829
|
+
return renderSchemaDocs(await (0, node_fs_promises.readFile)(candidate, "utf-8"), topic, variant);
|
|
1753
1830
|
} catch {}
|
|
1754
1831
|
return `manageCollection: could not read the collection-authoring reference (${SCHEMA_DOCS_FILE}).`;
|
|
1755
1832
|
}
|
|
@@ -1760,16 +1837,25 @@ async function handleSchemaDocs(deps, topic) {
|
|
|
1760
1837
|
async function handleGetSchema(slug, deps) {
|
|
1761
1838
|
const collection = await require_discovery.loadCollection(slug, deps);
|
|
1762
1839
|
if (!collection) return unknownCollection(slug);
|
|
1763
|
-
const
|
|
1840
|
+
const { stagingDir } = authoringTarget(deps, collection.slug);
|
|
1841
|
+
const candidates = [...stagingDir === null ? [] : [node_path.default.join(stagingDir, SCHEMA_FILE)], node_path.default.join(collection.skillDir, SCHEMA_FILE)];
|
|
1764
1842
|
for (const candidate of candidates) try {
|
|
1765
1843
|
return await (0, node_fs_promises.readFile)(candidate, "utf-8");
|
|
1766
1844
|
} catch {}
|
|
1767
1845
|
return `manageCollection: '${require_promptSafety.defangForPrompt(slug)}' has no readable ${SCHEMA_FILE}.`;
|
|
1768
1846
|
}
|
|
1847
|
+
/** Where the agent should CREATE a collection skill in this root, named in the
|
|
1848
|
+
* messages that tell it to. Naming `data/skills/` under a root that has no
|
|
1849
|
+
* staging tree sends the agent to a directory nothing reads — the exact
|
|
1850
|
+
* silent failure the root-aware authoring guide exists to prevent, reappearing
|
|
1851
|
+
* in an error string the agent is far more likely to act on immediately. */
|
|
1852
|
+
function authoringDirLabel(deps, slug) {
|
|
1853
|
+
return authoringTarget(deps, slug).variant === "staged" ? `data/skills/${slug}/` : `.claude/skills/${slug}/`;
|
|
1854
|
+
}
|
|
1769
1855
|
/** Refuse a schema edit the host can't honour: user-scope/feed collections
|
|
1770
1856
|
* are read-only, and presets (mc-*) re-seed on restart so an edit is lost. */
|
|
1771
|
-
function schemaEditRefusal(collection, slug) {
|
|
1772
|
-
if (collection.source !== "project") return `manageCollection: '${require_promptSafety.defangForPrompt(slug)}' is ${collection.source}-scope and read-only here — only project collections (
|
|
1857
|
+
function schemaEditRefusal(collection, slug, deps) {
|
|
1858
|
+
if (collection.source !== "project") return `manageCollection: '${require_promptSafety.defangForPrompt(slug)}' is ${collection.source}-scope and read-only here — only project collections (${authoringDirLabel(deps, "<slug>")}) can be edited.`;
|
|
1773
1859
|
if (isPresetSlug(slug)) return `manageCollection: '${require_promptSafety.defangForPrompt(slug)}' is a preset (mc-*) and re-seeds on restart — copy it under a different slug to customise.`;
|
|
1774
1860
|
return null;
|
|
1775
1861
|
}
|
|
@@ -1783,14 +1869,23 @@ function formatSchemaIssues(issues) {
|
|
|
1783
1869
|
}
|
|
1784
1870
|
/** Write the validated schema to the canonical staging copy, then mirror
|
|
1785
1871
|
* it into the active `.claude/skills/` tree discovery reads — an internal
|
|
1786
|
-
* write doesn't fire the skill-bridge hook, so we mirror explicitly.
|
|
1787
|
-
|
|
1872
|
+
* write doesn't fire the skill-bridge hook, so we mirror explicitly.
|
|
1873
|
+
*
|
|
1874
|
+
* A root with NO staging tree has no bridge to mirror through either, so the
|
|
1875
|
+
* active skill dir IS the canonical copy and is written directly. Mirroring
|
|
1876
|
+
* there would read a staging file that never exists. */
|
|
1877
|
+
async function writeAndMirrorSchema(slug, skillDir, schema, deps) {
|
|
1788
1878
|
const base = resolveBase(deps);
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1879
|
+
const serialized = `${JSON.stringify(schema, null, 2)}\n`;
|
|
1880
|
+
const { stagingDir } = authoringTarget(deps, slug);
|
|
1881
|
+
if (stagingDir === null) await require_root.writeFileAtomic(node_path.default.join(skillDir, SCHEMA_FILE), serialized);
|
|
1882
|
+
else {
|
|
1883
|
+
await require_root.writeFileAtomic(node_path.default.join(stagingDir, SCHEMA_FILE), serialized);
|
|
1884
|
+
require_skill_bridge_index.mirrorSkillWrite(base, {
|
|
1885
|
+
slug,
|
|
1886
|
+
relSegments: [SCHEMA_FILE]
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1794
1889
|
try {
|
|
1795
1890
|
await deps.refreshAfterWrite?.();
|
|
1796
1891
|
} catch {}
|
|
@@ -1810,19 +1905,19 @@ function schemaDiscoveryGate(schema, base) {
|
|
|
1810
1905
|
return null;
|
|
1811
1906
|
}
|
|
1812
1907
|
/** Validate a schema against CollectionSchemaZ and, on success, persist it.
|
|
1813
|
-
* Edit-only: a new collection is created by writing SKILL.md + schema.json
|
|
1814
|
-
*
|
|
1908
|
+
* Edit-only: a new collection is created by writing SKILL.md + schema.json in
|
|
1909
|
+
* the root's authoring dir (the normal create flow), not through here. */
|
|
1815
1910
|
async function handlePutSchema(slug, schemaArg, deps) {
|
|
1816
1911
|
if (!schemaArg || typeof schemaArg !== "object" || Array.isArray(schemaArg)) return "manageCollection: `schema` is required for putSchema — the full collection schema object.";
|
|
1817
1912
|
const collection = await require_discovery.loadCollection(slug, deps);
|
|
1818
|
-
if (!collection) return `manageCollection: unknown collection '${require_promptSafety.defangForPrompt(slug)}' — create it by writing SKILL.md + ${SCHEMA_FILE} under
|
|
1819
|
-
const refusal = schemaEditRefusal(collection, slug);
|
|
1913
|
+
if (!collection) return `manageCollection: unknown collection '${require_promptSafety.defangForPrompt(slug)}' — create it by writing SKILL.md + ${SCHEMA_FILE} under ${authoringDirLabel(deps, require_promptSafety.defangForPrompt(slug))}, then edit it here.`;
|
|
1914
|
+
const refusal = schemaEditRefusal(collection, slug, deps);
|
|
1820
1915
|
if (refusal) return refusal;
|
|
1821
1916
|
const parsed = require_discovery.CollectionSchemaZ.safeParse(schemaArg);
|
|
1822
1917
|
if (!parsed.success) return formatSchemaIssues(parsed.error.issues);
|
|
1823
1918
|
const gate = schemaDiscoveryGate(parsed.data, resolveBase(deps));
|
|
1824
1919
|
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.`;
|
|
1825
|
-
await writeAndMirrorSchema(collection.slug, parsed.data, deps);
|
|
1920
|
+
await writeAndMirrorSchema(collection.slug, collection.skillDir, parsed.data, deps);
|
|
1826
1921
|
return JSON.stringify({
|
|
1827
1922
|
collection: collection.slug,
|
|
1828
1923
|
written: true
|
|
@@ -2160,4 +2255,4 @@ Object.defineProperty(exports, "validateRecordObject", {
|
|
|
2160
2255
|
}
|
|
2161
2256
|
});
|
|
2162
2257
|
|
|
2163
|
-
//# sourceMappingURL=server-
|
|
2258
|
+
//# sourceMappingURL=server-WU35UYlF.cjs.map
|