@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,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 {
|
|
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,
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
1263
|
+
const staging = stagingSkillDir(workspaceRoot, safeSlug);
|
|
1248
1264
|
const targets = [];
|
|
1249
|
-
if (await fileExists(
|
|
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(
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
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
|
-
*
|
|
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
|
|
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-
|
|
2053
|
+
//# sourceMappingURL=server-AA2kfDfk.js.map
|