@mulmoclaude/core 0.18.0 → 0.19.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 (34) hide show
  1. package/assets/helps/index.md +1 -1
  2. package/dist/collection/index.cjs +44 -51
  3. package/dist/collection/index.cjs.map +1 -1
  4. package/dist/collection/index.js +1 -8
  5. package/dist/collection/index.js.map +1 -1
  6. package/dist/collection/registry/server/index.cjs +2 -2
  7. package/dist/collection/registry/server/index.js +2 -2
  8. package/dist/collection/server/index.cjs +6 -3
  9. package/dist/collection/server/index.d.ts +1 -0
  10. package/dist/collection/server/index.js +3 -3
  11. package/dist/collection/server/manageTool.d.ts +82 -0
  12. package/dist/collection-watchers/index.cjs +4 -4
  13. package/dist/collection-watchers/index.js +2 -2
  14. package/dist/feeds/index.cjs +4 -4
  15. package/dist/feeds/index.js +2 -2
  16. package/dist/feeds/server/index.cjs +5 -5
  17. package/dist/feeds/server/index.js +3 -3
  18. package/dist/{ingestTypes-DvsJhqMx.js → ingestTypes-DEjpiZGM.js} +2 -2
  19. package/dist/{ingestTypes-DvsJhqMx.js.map → ingestTypes-DEjpiZGM.js.map} +1 -1
  20. package/dist/{ingestTypes-Cq65Bv-N.cjs → ingestTypes-Dh5lniBL.cjs} +3 -3
  21. package/dist/{ingestTypes-Cq65Bv-N.cjs.map → ingestTypes-Dh5lniBL.cjs.map} +1 -1
  22. package/dist/{calendarGrid-CRaMAguM.cjs → promptSafety-DN5V__Ku.cjs} +14 -1
  23. package/dist/promptSafety-DN5V__Ku.cjs.map +1 -0
  24. package/dist/{calendarGrid-B3dtFN-m.js → promptSafety-DyG3EuC7.js} +9 -2
  25. package/dist/promptSafety-DyG3EuC7.js.map +1 -0
  26. package/dist/{server-0IVR-Y_G.js → server-Bd8l1bhv.js} +400 -14
  27. package/dist/server-Bd8l1bhv.js.map +1 -0
  28. package/dist/{server-BeoOWQ-a.cjs → server-Bh-DPK-H.cjs} +447 -43
  29. package/dist/server-Bh-DPK-H.cjs.map +1 -0
  30. package/package.json +1 -1
  31. package/dist/calendarGrid-B3dtFN-m.js.map +0 -1
  32. package/dist/calendarGrid-CRaMAguM.cjs.map +0 -1
  33. package/dist/server-0IVR-Y_G.js.map +0 -1
  34. package/dist/server-BeoOWQ-a.cjs.map +0 -1
@@ -1,6 +1,7 @@
1
1
  const require_rolldown_runtime = require("./rolldown-runtime-D6vf50IK.cjs");
2
- const require_calendarGrid = require("./calendarGrid-CRaMAguM.cjs");
2
+ const require_promptSafety = require("./promptSafety-DN5V__Ku.cjs");
3
3
  const require_collection_paths = require("./collection/paths.cjs");
4
+ const require_skill_bridge_index = require("./skill-bridge/index.cjs");
4
5
  let node_path = require("node:path");
5
6
  node_path = require_rolldown_runtime.__toESM(node_path, 1);
6
7
  let node_fs = require("node:fs");
@@ -62,7 +63,7 @@ function collectionsRegistriesConfigPath() {
62
63
  const host = requireHost();
63
64
  return host.paths.collectionsRegistriesConfig(host.workspaceRoot);
64
65
  }
65
- function isPresetSlug(slug) {
66
+ function isPresetSlug$1(slug) {
66
67
  return requireHost().isPresetSlug(slug);
67
68
  }
68
69
  /** Logger proxy so engine modules can `import { log }` and use it exactly like
@@ -79,7 +80,7 @@ var log = {
79
80
  };
80
81
  //#endregion
81
82
  //#region src/collection/server/paths.ts
82
- var SCHEMA_FILE = "schema.json";
83
+ var SCHEMA_FILE$1 = "schema.json";
83
84
  /** Sanitise a user-supplied slug into a safe directory-name leaf.
84
85
  * Returns null for anything that fails the slug whitelist OR isn't a
85
86
  * basename (i.e. survives `path.basename` round-trip unchanged).
@@ -87,7 +88,7 @@ var SCHEMA_FILE = "schema.json";
87
88
  * `js/path-injection` sanitiser. */
88
89
  function safeSlugName(slug) {
89
90
  if (typeof slug !== "string") return null;
90
- if (!require_calendarGrid.SAFE_SLUG_PATTERN.test(slug)) return null;
91
+ if (!require_promptSafety.SAFE_SLUG_PATTERN.test(slug)) return null;
91
92
  const basename = node_path.default.basename(slug);
92
93
  if (basename !== slug) return null;
93
94
  return basename;
@@ -99,7 +100,7 @@ function safeSlugName(slug) {
99
100
  * `js/path-injection` sanitiser CodeQL recognises on `safeSlugName`. */
100
101
  function safeRecordId(recordId) {
101
102
  if (typeof recordId !== "string") return null;
102
- if (!require_calendarGrid.SAFE_RECORD_ID_PATTERN.test(recordId)) return null;
103
+ if (!require_promptSafety.SAFE_RECORD_ID_PATTERN.test(recordId)) return null;
103
104
  if (recordId.includes("..")) return null;
104
105
  const basename = node_path.default.basename(recordId);
105
106
  if (basename !== recordId) return null;
@@ -658,10 +659,10 @@ function enforcedProblem(key, spec, value) {
658
659
  function strictTypeProblem(key, spec, value) {
659
660
  switch (spec.type) {
660
661
  case "number":
661
- case "money": return Number.isFinite(require_calendarGrid.coerceNumeric(value)) ? null : `'${key}' = '${String(value)}' is not numeric (a '${spec.type}' field stores a plain number)`;
662
+ case "money": return Number.isFinite(require_promptSafety.coerceNumeric(value)) ? null : `'${key}' = '${String(value)}' is not numeric (a '${spec.type}' field stores a plain number)`;
662
663
  case "boolean": return value === true || value === false ? null : `'${key}' = '${String(value)}' is not a boolean (store true or false, unquoted)`;
663
- case "date": return require_calendarGrid.parseIsoDate(value) !== null ? null : `'${key}' = '${String(value)}' is not a real YYYY-MM-DD date`;
664
- case "datetime": return require_calendarGrid.parseIsoDateTime(value) !== null ? null : `'${key}' = '${String(value)}' is not a YYYY-MM-DDTHH:MM datetime (seconds optional, no timezone suffix — the shape the calendar parses)`;
664
+ case "date": return require_promptSafety.parseIsoDate(value) !== null ? null : `'${key}' = '${String(value)}' is not a real YYYY-MM-DD date`;
665
+ case "datetime": return require_promptSafety.parseIsoDateTime(value) !== null ? null : `'${key}' = '${String(value)}' is not a YYYY-MM-DDTHH:MM datetime (seconds optional, no timezone suffix — the shape the calendar parses)`;
665
666
  default: return null;
666
667
  }
667
668
  }
@@ -706,7 +707,7 @@ var compiled = /* @__PURE__ */ new WeakMap();
706
707
  function compileRecordZ(schema, tier) {
707
708
  const cached = compiled.get(schema)?.[tier];
708
709
  if (cached) return cached;
709
- const stored = Object.entries(schema.fields).filter(([, spec]) => !require_calendarGrid.COMPUTED_TYPES.has(spec.type));
710
+ const stored = Object.entries(schema.fields).filter(([, spec]) => !require_promptSafety.COMPUTED_TYPES.has(spec.type));
710
711
  const validator = zod.z.looseObject({}).superRefine((record, ctx) => {
711
712
  for (const [key, spec] of stored) {
712
713
  const problem = recordFieldProblem(key, spec, record[key], tier);
@@ -872,14 +873,14 @@ async function applyMutateAction(collection, action, itemId, params, opts = {})
872
873
  status: "not-found",
873
874
  problem: `item '${itemId}' not found`
874
875
  };
875
- if (!require_calendarGrid.actionVisible(action, existing)) return {
876
+ if (!require_promptSafety.actionVisible(action, existing)) return {
876
877
  ok: false,
877
878
  status: "require-unmet",
878
879
  problem: `action '${action.id}' is not available for item '${itemId}' in its current state`
879
880
  };
880
881
  const stored = Object.entries(existing).filter(([key]) => {
881
882
  const spec = collection.schema.fields[key];
882
- return !spec || !require_calendarGrid.COMPUTED_TYPES.has(spec.type);
883
+ return !spec || !require_promptSafety.COMPUTED_TYPES.has(spec.type);
883
884
  });
884
885
  const merged = {
885
886
  ...Object.fromEntries(stored),
@@ -972,7 +973,7 @@ var RefFieldZ = zod.z.object({
972
973
  type: zod.z.literal("ref"),
973
974
  ...fieldBase,
974
975
  to: zod.z.string().min(1)
975
- }).refine((spec) => require_calendarGrid.isSafeSlug(spec.to), slugMessage("to"));
976
+ }).refine((spec) => require_promptSafety.isSafeSlug(spec.to), slugMessage("to"));
976
977
  /** A money amount. See `currencyKeys` for the currency-source contract. */
977
978
  var MoneyFieldZ = zod.z.object({
978
979
  type: zod.z.literal("money"),
@@ -1007,7 +1008,7 @@ var SubRefFieldZ = zod.z.object({
1007
1008
  type: zod.z.literal("ref"),
1008
1009
  ...subFieldBase,
1009
1010
  to: zod.z.string().min(1)
1010
- }).refine((spec) => require_calendarGrid.isSafeSlug(spec.to), slugMessage("to"));
1011
+ }).refine((spec) => require_promptSafety.isSafeSlug(spec.to), slugMessage("to"));
1011
1012
  var SubMoneyFieldZ = zod.z.object({
1012
1013
  type: zod.z.literal("money"),
1013
1014
  ...subFieldBase,
@@ -1070,11 +1071,11 @@ var EmbedFieldZ = zod.z.object({
1070
1071
  to: zod.z.string().min(1),
1071
1072
  id: zod.z.string().trim().min(1).optional(),
1072
1073
  idField: zod.z.string().trim().min(1).optional()
1073
- }).refine((spec) => require_calendarGrid.isSafeSlug(spec.to) && spec.id !== void 0 !== (spec.idField !== void 0), {
1074
+ }).refine((spec) => require_promptSafety.isSafeSlug(spec.to) && spec.id !== void 0 !== (spec.idField !== void 0), {
1074
1075
  message: "fields with type 'embed' must declare a `to` (valid collection slug) and exactly one of `id` (a fixed record's primary key) or `idField` (a sibling field naming the per-record target)",
1075
1076
  path: ["id"]
1076
1077
  });
1077
- /** Display-only REVERSE refs (plan step ② of plans/collection-ontology.md):
1078
+ /** Display-only REVERSE refs (plan step ② of plans/done/collection-ontology.md):
1078
1079
  * a read-only sub-table of the records in collection `from` whose `via`
1079
1080
  * ref field stores THIS record's primary key. Stores nothing (joins
1080
1081
  * `COMPUTED_TYPES`); resolution is shared server/client via
@@ -1091,9 +1092,9 @@ var BacklinksFieldZ = zod.z.object({
1091
1092
  via: zod.z.string().trim().min(1),
1092
1093
  display: zod.z.array(zod.z.string().trim().min(1)).min(1),
1093
1094
  filter: WhenZ.optional()
1094
- }).refine((spec) => require_calendarGrid.isSafeSlug(spec.from), slugMessage("from"));
1095
+ }).refine((spec) => require_promptSafety.isSafeSlug(spec.from), slugMessage("from"));
1095
1096
  /** A cross-collection AGGREGATE over a backlink relation (plan step ⑤ of
1096
- * plans/collection-ontology.md): a computed number — never stored — that
1097
+ * plans/done/collection-ontology.md): a computed number — never stored — that
1097
1098
  * sums a source column (or counts rows) over the records in `from` whose
1098
1099
  * `via` ref points at this record. Same `from`/`via`/`filter` vocabulary
1099
1100
  * and reverse-loading machinery as `backlinks`; resolution shared
@@ -1112,7 +1113,7 @@ var RollupFieldZ = zod.z.object({
1112
1113
  op: zod.z.enum(["sum", "count"]),
1113
1114
  column: zod.z.string().trim().min(1).optional(),
1114
1115
  filter: WhenZ.optional()
1115
- }).refine((spec) => require_calendarGrid.isSafeSlug(spec.from), slugMessage("from")).refine((spec) => spec.op === "sum" === (spec.column !== void 0), {
1116
+ }).refine((spec) => require_promptSafety.isSafeSlug(spec.from), slugMessage("from")).refine((spec) => spec.op === "sum" === (spec.column !== void 0), {
1116
1117
  message: "a rollup's `column` names the source column to aggregate: required for op \"sum\", meaningless for op \"count\"",
1117
1118
  path: ["column"]
1118
1119
  });
@@ -1163,7 +1164,7 @@ var SeededActionZ = zod.z.object({
1163
1164
  when: WhenZ.optional()
1164
1165
  });
1165
1166
  /** `kind: "mutate"` — a declarative, HOST-executed write; no LLM, no
1166
- * tokens (plan step ④ of plans/collection-ontology.md). Clicking the
1167
+ * tokens (plan step ④ of plans/done/collection-ontology.md). Clicking the
1167
1168
  * button (after an optional `params` mini-form) merges `set` into the
1168
1169
  * record: values are literals or `$params.<name>` references. `require`
1169
1170
  * is the state gate — the standard `when` shape, both the visibility
@@ -1261,9 +1262,9 @@ var SpawnZ = zod.z.object({
1261
1262
  * array) only when the response body isn't itself the array; rss/atom yield
1262
1263
  * items natively and ignore it — so no kind-specific requirement here. */
1263
1264
  var DeclarativeIngestZ = zod.z.object({
1264
- kind: zod.z.enum(require_calendarGrid.INGEST_KINDS),
1265
+ kind: zod.z.enum(require_promptSafety.INGEST_KINDS),
1265
1266
  url: zod.z.string().url(),
1266
- schedule: zod.z.enum(require_calendarGrid.FEED_SCHEDULES),
1267
+ schedule: zod.z.enum(require_promptSafety.FEED_SCHEDULES),
1267
1268
  atHour: zod.z.number().int().min(0).max(23).optional(),
1268
1269
  itemsAt: zod.z.string().trim().min(1).optional(),
1269
1270
  map: zod.z.record(zod.z.string().trim().min(1), zod.z.string().trim().min(1)),
@@ -1277,8 +1278,8 @@ var DeclarativeIngestZ = zod.z.object({
1277
1278
  * validated the SAME way an action's template is (safe path under
1278
1279
  * `templates/`), so the skill-bridge mirrors it identically. */
1279
1280
  var AgentIngestZ = zod.z.object({
1280
- kind: zod.z.literal(require_calendarGrid.AGENT_INGEST_KIND),
1281
- schedule: zod.z.enum(require_calendarGrid.FEED_SCHEDULES),
1281
+ kind: zod.z.literal(require_promptSafety.AGENT_INGEST_KIND),
1282
+ schedule: zod.z.enum(require_promptSafety.FEED_SCHEDULES),
1282
1283
  atHour: zod.z.number().int().min(0).max(23).optional(),
1283
1284
  role: zod.z.string().trim().min(1),
1284
1285
  template: zod.z.string().trim().min(1).refine(require_collection_paths.isSafeActionTemplatePath, "must be a safe path under `templates/` (e.g. `templates/refresh.md`; no `..`, no leading `/`, no backslash)")
@@ -1424,7 +1425,7 @@ var CollectionSchemaZ = zod.z.object({
1424
1425
  notifyWhen: WhenZ.optional(),
1425
1426
  ingest: IngestZ.optional(),
1426
1427
  dynamicIcon: DynamicIconSpecZ.optional()
1427
- }).refine((schema) => schema.singleton === void 0 || require_calendarGrid.isSafeRecordId(schema.singleton), {
1428
+ }).refine((schema) => schema.singleton === void 0 || require_promptSafety.isSafeRecordId(schema.singleton), {
1428
1429
  message: "schema `singleton` must be a valid item id (alphanumeric / hyphen / underscore / interior dot, no `..` or path separators)",
1429
1430
  path: ["singleton"]
1430
1431
  }).refine((schema) => schema.actions === void 0 || new Set(schema.actions.map((action) => action.id)).size === schema.actions.length, {
@@ -1435,7 +1436,7 @@ var CollectionSchemaZ = zod.z.object({
1435
1436
  path: ["collectionActions"]
1436
1437
  }).refine((schema) => (schema.actions ?? []).every((action) => action.kind !== "mutate" || Object.keys(action.set).every((key) => {
1437
1438
  const target = schema.fields[key];
1438
- return target !== void 0 && !require_calendarGrid.COMPUTED_TYPES.has(target.type) && key !== schema.primaryKey;
1439
+ return target !== void 0 && !require_promptSafety.COMPUTED_TYPES.has(target.type) && key !== schema.primaryKey;
1439
1440
  })), {
1440
1441
  message: "a mutate action's `set` keys must name declared, non-computed fields (and never the primaryKey)",
1441
1442
  path: ["actions"]
@@ -1530,7 +1531,7 @@ var CollectionSchemaZ = zod.z.object({
1530
1531
  }).refine((schema) => schema.notifyWhen === void 0 || schema.fields[schema.notifyWhen.field] !== void 0, {
1531
1532
  message: "schema `notifyWhen.field` must name a top-level field declared in `fields`",
1532
1533
  path: ["notifyWhen"]
1533
- }).refine((schema) => schema.views === void 0 || schema.views.every((view) => require_calendarGrid.isSafeSlug(view.id)), {
1534
+ }).refine((schema) => schema.views === void 0 || schema.views.every((view) => require_promptSafety.isSafeSlug(view.id)), {
1534
1535
  message: "every `views[].id` must be a valid slug (alphanumeric / hyphen / underscore, no path separators)",
1535
1536
  path: ["views"]
1536
1537
  }).refine((schema) => schema.views === void 0 || new Set(schema.views.map((view) => view.id)).size === schema.views.length, {
@@ -1591,7 +1592,7 @@ function acceptParsedSchema(schema, opts) {
1591
1592
  async function loadOneCollection(skillsRoot, slug, source, workspaceRoot) {
1592
1593
  const safeName = safeSlugName(slug);
1593
1594
  if (safeName === null) return null;
1594
- const schemaPath = node_path.default.join(skillsRoot, safeName, SCHEMA_FILE);
1595
+ const schemaPath = node_path.default.join(skillsRoot, safeName, SCHEMA_FILE$1);
1595
1596
  let raw;
1596
1597
  try {
1597
1598
  if (!(await (0, node_fs_promises.stat)(schemaPath)).isFile()) return null;
@@ -1824,7 +1825,7 @@ async function loadTarget(slug, opts) {
1824
1825
  const byId = {};
1825
1826
  for (const item of items) {
1826
1827
  const itemId = item[target.schema.primaryKey];
1827
- if (typeof itemId === "string" && itemId.length > 0) byId[itemId] = require_calendarGrid.deriveAll(target.schema, item, {});
1828
+ if (typeof itemId === "string" && itemId.length > 0) byId[itemId] = require_promptSafety.deriveAll(target.schema, item, {});
1828
1829
  }
1829
1830
  return {
1830
1831
  schema: target.schema,
@@ -1860,7 +1861,7 @@ function toRefRecords(linked) {
1860
1861
  function projectBacklinks(field, schema, enriched, linked) {
1861
1862
  const source = linked[field.from];
1862
1863
  if (!source) return [];
1863
- return require_calendarGrid.backlinkRows(field, String(enriched[schema.primaryKey] ?? ""), Object.values(source.byId)).map((row) => require_calendarGrid.projectBacklinkRow(row, field.display, source.schema.primaryKey));
1864
+ return require_promptSafety.backlinkRows(field, String(enriched[schema.primaryKey] ?? ""), Object.values(source.byId)).map((row) => require_promptSafety.projectBacklinkRow(row, field.display, source.schema.primaryKey));
1864
1865
  }
1865
1866
  /** Project the computed (never-stored) field kinds onto one derived
1866
1867
  * record: `toggle` → boolean off its enum, `embed` → the target record
@@ -1870,7 +1871,7 @@ function projectComputed(schema, enriched, linked) {
1870
1871
  for (const [key, field] of Object.entries(schema.fields)) {
1871
1872
  if (field.type === "toggle" && field.field) enriched[key] = String(enriched[field.field] ?? "") === field.onValue;
1872
1873
  if (field.type === "embed" && field.to) {
1873
- const targetId = require_calendarGrid.embedTargetId(field, enriched);
1874
+ const targetId = require_promptSafety.embedTargetId(field, enriched);
1874
1875
  enriched[key] = targetId && linked[field.to]?.byId[targetId] || null;
1875
1876
  }
1876
1877
  if (field.type === "backlinks") enriched[key] = projectBacklinks(field, schema, enriched, linked);
@@ -1891,7 +1892,7 @@ function projectRollups(schema, record, linked) {
1891
1892
  if (out === record) out = { ...record };
1892
1893
  const source = linked[field.from];
1893
1894
  const selfId = String(record[schema.primaryKey] ?? "");
1894
- out[key] = source ? require_calendarGrid.rollupValue(field, selfId, Object.values(source.byId)) : null;
1895
+ out[key] = source ? require_promptSafety.rollupValue(field, selfId, Object.values(source.byId)) : null;
1895
1896
  }
1896
1897
  return out;
1897
1898
  }
@@ -1903,7 +1904,7 @@ async function enrichItems(collection, items, opts = {}) {
1903
1904
  const { schema } = collection;
1904
1905
  const linked = await loadLinkedTargets(schema, opts);
1905
1906
  const refRecords = toRefRecords(linked);
1906
- return items.map((item) => projectComputed(schema, require_calendarGrid.deriveAll(schema, projectRollups(schema, item, linked), refRecords), linked));
1907
+ return items.map((item) => projectComputed(schema, require_promptSafety.deriveAll(schema, projectRollups(schema, item, linked), refRecords), linked));
1907
1908
  }
1908
1909
  //#endregion
1909
1910
  //#region src/collection/server/dynamicIcon.ts
@@ -1939,9 +1940,9 @@ async function computeCollectionIcon(collection, opts = {}) {
1939
1940
  const source = await loadCollection(spec.source.collection, opts);
1940
1941
  if (!source) return spec.fallback ?? schema.icon;
1941
1942
  const ordered = sortByPrimaryKey(await listItems(source.dataDir, { workspaceRoot: opts.workspaceRoot }), source.schema.primaryKey);
1942
- const orderBy = spec.source.orderBy ?? require_calendarGrid.firstDateField(source.schema);
1943
+ const orderBy = spec.source.orderBy ?? require_promptSafety.firstDateField(source.schema);
1943
1944
  const recordsById = buildRecordsById(ordered, source.schema.primaryKey);
1944
- return require_calendarGrid.resolveIcon(require_calendarGrid.selectDynamicRecord(ordered, spec.source, orderBy, recordsById), spec, schema.icon, recordsById);
1945
+ return require_promptSafety.resolveIcon(require_promptSafety.selectDynamicRecord(ordered, spec.source, orderBy, recordsById), spec, schema.icon, recordsById);
1945
1946
  } catch (err) {
1946
1947
  log.warn("collections", "dynamic icon compute failed, falling back", {
1947
1948
  slug: collection.slug,
@@ -2075,7 +2076,7 @@ function matchesWhen(when, schema, item) {
2075
2076
  * Discovery rejects a map that doesn't cover the enum's values, so null
2076
2077
  * here means a record that predates a map/enum edit. */
2077
2078
  function resolveEvery(every, sourceItem) {
2078
- if (!require_calendarGrid.isFieldDrivenEvery(every)) return every;
2079
+ if (!require_promptSafety.isFieldDrivenEvery(every)) return every;
2079
2080
  const raw = sourceItem[every.fromField];
2080
2081
  if (raw === void 0 || raw === null || raw === "") return null;
2081
2082
  return every.map[String(raw)] ?? null;
@@ -2115,7 +2116,7 @@ function logSpawnSkip(slug, triggerField, every, sourceItem, sourceId) {
2115
2116
  });
2116
2117
  return;
2117
2118
  }
2118
- const fromField = require_calendarGrid.isFieldDrivenEvery(every) ? every.fromField : void 0;
2119
+ const fromField = require_promptSafety.isFieldDrivenEvery(every) ? every.fromField : void 0;
2119
2120
  log.warn("collections", "spawn skipped: no `every` mapping for frequency value", {
2120
2121
  slug,
2121
2122
  sourceId,
@@ -2302,7 +2303,7 @@ async function deleteCollection(collection, opts = {}) {
2302
2303
  kind: "user-scope",
2303
2304
  slug
2304
2305
  };
2305
- if (isPresetSlug(slug)) return {
2306
+ if (isPresetSlug$1(slug)) return {
2306
2307
  kind: "preset",
2307
2308
  slug
2308
2309
  };
@@ -2368,9 +2369,9 @@ async function canonicalBase(collection, workspaceRoot, safeSlug) {
2368
2369
  * project collection (no staging mirror) doesn't have an empty staging tree
2369
2370
  * materialised by a side effect of the delete. */
2370
2371
  async function schemaWriteTargets(collection, workspaceRoot, safeSlug) {
2371
- const active = node_path.default.join(collection.skillDir, SCHEMA_FILE);
2372
+ const active = node_path.default.join(collection.skillDir, SCHEMA_FILE$1);
2372
2373
  if (collection.source !== "project") return [active];
2373
- const stagingSchema = node_path.default.join(skillsStagingDir(workspaceRoot), safeSlug, SCHEMA_FILE);
2374
+ const stagingSchema = node_path.default.join(skillsStagingDir(workspaceRoot), safeSlug, SCHEMA_FILE$1);
2374
2375
  const targets = [];
2375
2376
  if (await fileExists(stagingSchema)) targets.push(stagingSchema);
2376
2377
  targets.push(active);
@@ -2391,7 +2392,7 @@ async function unlinkIfPresent(target) {
2391
2392
  * preserved verbatim. */
2392
2393
  async function removeViewFromSchemas(collection, viewId, workspaceRoot, safeSlug) {
2393
2394
  const base = await canonicalBase(collection, workspaceRoot, safeSlug);
2394
- const canonical = node_path.default.join(base, SCHEMA_FILE);
2395
+ const canonical = node_path.default.join(base, SCHEMA_FILE$1);
2395
2396
  const parsed = JSON.parse(await (0, node_fs_promises.readFile)(canonical, "utf-8"));
2396
2397
  if (Array.isArray(parsed.views)) parsed.views = parsed.views.filter((entry) => entry?.id !== viewId);
2397
2398
  const serialized = `${JSON.stringify(parsed, null, 2)}\n`;
@@ -2402,7 +2403,7 @@ async function removeViewFromSchemas(collection, viewId, workspaceRoot, safeSlug
2402
2403
  * refused (read-only / re-seeded on boot), consistent with `deleteCollection`. */
2403
2404
  async function deleteCustomView(collection, viewId, opts = {}) {
2404
2405
  if (collection.source === "user") return { kind: "user-scope" };
2405
- if (isPresetSlug(collection.slug)) return { kind: "preset" };
2406
+ if (isPresetSlug$1(collection.slug)) return { kind: "preset" };
2406
2407
  const safeSlug = safeSlugName(collection.slug);
2407
2408
  if (safeSlug === null) return {
2408
2409
  kind: "unsafe-path",
@@ -2427,6 +2428,391 @@ async function deleteCustomView(collection, viewId, opts = {}) {
2427
2428
  viewId
2428
2429
  };
2429
2430
  }
2431
+ /** True for a launcher-managed preset slug (`mc-<something>`). The sync logic uses
2432
+ * this to bound which active skills it may refresh/prune, so user-authored skills are
2433
+ * never touched. */
2434
+ function isPresetSlug(slug) {
2435
+ return slug.startsWith("mc-") && slug.length > 3;
2436
+ }
2437
+ //#endregion
2438
+ //#region src/collection/server/manageTool.ts
2439
+ /** Refuse an unselective getItems beyond this many records — a silent
2440
+ * truncation would read as "covered everything", and an unbounded dump
2441
+ * of a large collection is a token bomb. `ids` or `fields` lifts it. */
2442
+ var MAX_UNSELECTIVE_ITEMS = 200;
2443
+ /** schema.json basename under a skill dir (canonical staging + active mirror). */
2444
+ var SCHEMA_FILE = "schema.json";
2445
+ /** The collection-authoring reference, served by the `schemaDocs` action. */
2446
+ var SCHEMA_DOCS_FILE = "collection-skills.md";
2447
+ /** Cap the rejected-schema issue list so a deeply-broken schema can't flood the result. */
2448
+ var MAX_SCHEMA_ISSUES = 20;
2449
+ /** The workspace help-docs dir both hosts seed (`@mulmoclaude/core/workspace-setup`
2450
+ * syncs the bundled assets here) — the user-editable copy schemaDocs prefers. */
2451
+ var HELPS_DIR = "config/helps";
2452
+ /** Resolve the workspace root the same way every collections call does:
2453
+ * the injected override (tests) or the configured collection host. */
2454
+ function resolveBase(deps) {
2455
+ return deps.workspaceRoot ?? getWorkspaceRoot();
2456
+ }
2457
+ /** Shared "unknown collection" message — its schema.json is missing or
2458
+ * failed validation, so discovery skipped it. */
2459
+ function unknownCollection(slug) {
2460
+ return `manageCollection: unknown collection '${require_promptSafety.defangForPrompt(slug)}' — its schema.json is missing or failed validation.`;
2461
+ }
2462
+ function optionalStringArray(value, name) {
2463
+ if (value === void 0) return { ok: true };
2464
+ if (!Array.isArray(value) || !value.every((entry) => typeof entry === "string" && entry.length > 0)) return {
2465
+ ok: false,
2466
+ error: `manageCollection: \`${name}\` must be an array of non-empty strings when present.`
2467
+ };
2468
+ return {
2469
+ ok: true,
2470
+ value
2471
+ };
2472
+ }
2473
+ /** Project a record down to the requested fields. The primary key is
2474
+ * always kept so every returned record stays addressable for a
2475
+ * follow-up getItems/putItems. */
2476
+ function projectFields(record, fields, primaryKey) {
2477
+ const keys = fields.includes(primaryKey) ? fields : [primaryKey, ...fields];
2478
+ return Object.fromEntries(keys.filter((key) => key in record).map((key) => [key, record[key]]));
2479
+ }
2480
+ /** The validation warning appended to a getItems result when stored
2481
+ * record files are malformed (they're silently skipped at read time,
2482
+ * so without this they'd just look missing). Issue strings quote
2483
+ * record-controlled text → defanged, mirroring the presentCollection
2484
+ * dispatch. The record VALUES in `items` ride verbatim, like a raw
2485
+ * file Read — only host-composed report strings are defanged. */
2486
+ async function recordIssuesWarning(collection, deps) {
2487
+ if (deps.ablateValidation) return void 0;
2488
+ const issues = await validateCollectionRecords(collection, { workspaceRoot: deps.workspaceRoot });
2489
+ if (issues.length === 0) return void 0;
2490
+ const lines = issues.map((issue) => `- ${require_promptSafety.defangForPrompt(issue.file)}: ${require_promptSafety.defangForPrompt(issue.problem)}`).join("\n");
2491
+ return `${issues.length} record file(s) have data problems and are missing from this result. Fix each (Read → correct → Write):\n${lines}`;
2492
+ }
2493
+ async function loadRequestedItems(collection, ids, deps) {
2494
+ if (!ids) return {
2495
+ items: await listItems(collection.dataDir, { workspaceRoot: deps.workspaceRoot }),
2496
+ missing: []
2497
+ };
2498
+ const items = [];
2499
+ const missing = [];
2500
+ for (const recordId of ids) {
2501
+ const item = await readItem(collection.dataDir, recordId, { workspaceRoot: deps.workspaceRoot }).catch(() => null);
2502
+ if (item) items.push(item);
2503
+ else missing.push(recordId);
2504
+ }
2505
+ return {
2506
+ items,
2507
+ missing
2508
+ };
2509
+ }
2510
+ async function handleGetItems(collection, args, deps) {
2511
+ const { items, missing } = await loadRequestedItems(collection, args.ids, deps);
2512
+ if (!args.ids && !args.fields && items.length > 200) return `manageCollection: refused — '${collection.slug}' has ${items.length} records, over the unselective limit of 200. Pass \`ids\` for specific records or \`fields\` to project only the columns you need.`;
2513
+ const enriched = await enrichItems(collection, items, deps);
2514
+ const projected = args.fields ? enriched.map((item) => projectFields(item, args.fields, collection.schema.primaryKey)) : enriched;
2515
+ const warning = !args.ids || missing.length > 0 ? await recordIssuesWarning(collection, deps) : void 0;
2516
+ return JSON.stringify({
2517
+ collection: collection.slug,
2518
+ count: projected.length,
2519
+ items: projected,
2520
+ ...missing.length > 0 ? { missing: missing.map((recordId) => require_promptSafety.defangForPrompt(recordId)) } : {},
2521
+ ...warning ? { warning } : {}
2522
+ });
2523
+ }
2524
+ /** Reject writes that set host-computed keys, with a pointer at the
2525
+ * writable source of truth (the toggle's enum) where one exists. */
2526
+ function computedKeyProblem(record, schema) {
2527
+ for (const key of Object.keys(record)) {
2528
+ const spec = schema.fields[key];
2529
+ if (!spec || !require_promptSafety.COMPUTED_TYPES.has(spec.type)) continue;
2530
+ if (spec.type === "toggle" && spec.field) return `'${key}' is a toggle projection — write the enum field '${spec.field}' instead`;
2531
+ return `'${key}' is ${{
2532
+ derived: "derived",
2533
+ embed: "an embed",
2534
+ backlinks: "a backlinks view",
2535
+ rollup: "a rollup"
2536
+ }[spec.type] ?? "computed"} — computed by the host, remove it from the record`;
2537
+ }
2538
+ return null;
2539
+ }
2540
+ /** `mode: "merge"` resolves the row against the EXISTING record —
2541
+ * a partial row updates just the fields it carries, instead of a
2542
+ * whole-record upsert silently erasing the optional fields it omits
2543
+ * (an upsert of `{id, status}` would pass validation yet drop
2544
+ * `notes`/`lesson`/…). Merge is a partial UPDATE by definition, so a
2545
+ * missing record is a reject, not an implicit create — a merged-over-
2546
+ * nothing partial record is exactly the data shape this mode exists
2547
+ * to prevent.
2548
+ *
2549
+ * Computed keys found in the STORED record are stripped before the
2550
+ * merge: the caller's own row was already computed-key-rejected, but a
2551
+ * raw-written / legacy record can carry a stale `derived`/`embed`/
2552
+ * `toggle` value, and re-writing it would perpetuate a forged
2553
+ * host-computed value. A merge heals the record instead.
2554
+ *
2555
+ * readItem THROWS on a malformed stored file (only ENOENT is null) —
2556
+ * downgraded to a per-row rejection here, like loadRequestedItems'
2557
+ * `missing`, so one broken file can't abort the whole putItems batch. */
2558
+ async function mergeWithExisting(collection, record, itemId, deps) {
2559
+ let existing;
2560
+ try {
2561
+ existing = await readItem(collection.dataDir, itemId, { workspaceRoot: deps.workspaceRoot });
2562
+ } catch {
2563
+ return `'${itemId}' has a malformed stored file — mode "merge" needs to read it; fix the file (Read → correct → Write) or replace it whole with "upsert"`;
2564
+ }
2565
+ if (!existing) return `'${itemId}' not found — mode "merge" updates an existing record; use "upsert" or "create" to add it`;
2566
+ const stored = Object.entries(existing).filter(([key]) => {
2567
+ const spec = collection.schema.fields[key];
2568
+ return !spec || !require_promptSafety.COMPUTED_TYPES.has(spec.type);
2569
+ });
2570
+ return {
2571
+ ...Object.fromEntries(stored),
2572
+ ...record
2573
+ };
2574
+ }
2575
+ async function putOneItem(collection, record, mode, deps) {
2576
+ const { schema } = collection;
2577
+ const itemId = resolveCreateItemId(schema, record);
2578
+ const reject = (about, problem) => ({ rejected: {
2579
+ id: require_promptSafety.defangForPrompt(about),
2580
+ problem: require_promptSafety.defangForPrompt(problem)
2581
+ } });
2582
+ if (itemId === null) return reject("(no id)", `record has no '${schema.primaryKey}' value — set it (it doubles as the filename)`);
2583
+ const computed = computedKeyProblem(record, schema);
2584
+ if (computed) return reject(itemId, computed);
2585
+ let toWrite = record;
2586
+ if (mode === "merge") {
2587
+ const merged = await mergeWithExisting(collection, record, itemId, deps);
2588
+ if (typeof merged === "string") return reject(itemId, merged);
2589
+ toWrite = merged;
2590
+ }
2591
+ if (!deps.ablateValidation) {
2592
+ const invalid = validateRecordObject(toWrite, itemId, schema);
2593
+ if (invalid) return reject(itemId, invalid);
2594
+ }
2595
+ const result = await writeItem(collection.dataDir, itemId, toWrite, {
2596
+ refuseOverwrite: mode === "create",
2597
+ workspaceRoot: deps.workspaceRoot,
2598
+ slug: collection.slug
2599
+ });
2600
+ if (result.kind === "ok") return { written: result.itemId };
2601
+ if (result.kind === "invalid-id") return reject(itemId, `'${itemId}' is not a valid record id (letters/digits at the ends; -, _, or . inside; no '..' or path characters)`);
2602
+ if (result.kind === "conflict") return reject(itemId, `'${itemId}' already exists — mode "create" refuses overwrite; use "upsert" to update it`);
2603
+ return reject(itemId, "write refused: the collection's data dir escapes the workspace");
2604
+ }
2605
+ async function handlePutItems(collection, args, deps) {
2606
+ const written = [];
2607
+ const rejected = [];
2608
+ for (const record of args.items) {
2609
+ const outcome = await putOneItem(collection, record, args.mode, deps);
2610
+ if (outcome.written) written.push(outcome.written);
2611
+ if (outcome.rejected) rejected.push(outcome.rejected);
2612
+ }
2613
+ return JSON.stringify({
2614
+ collection: collection.slug,
2615
+ written,
2616
+ rejected
2617
+ });
2618
+ }
2619
+ function parsePutItems(args, slug) {
2620
+ const { items, mode } = args;
2621
+ if (!(Array.isArray(items) && items.length > 0 && items.every((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)))) return "manageCollection: `items` is required for putItems — a non-empty array of record objects.";
2622
+ if (mode !== void 0 && mode !== "upsert" && mode !== "create" && mode !== "merge") return "manageCollection: `mode` must be \"upsert\" (default), \"create\", or \"merge\".";
2623
+ return {
2624
+ slug,
2625
+ items,
2626
+ mode: mode ?? "upsert"
2627
+ };
2628
+ }
2629
+ /** The machine-readable workspace ontology: every collection with its
2630
+ * identity, record count, and outbound `ref`/`embed` relations. Slugs
2631
+ * are discovery-sanitized; titles/labels are workspace-authored schema
2632
+ * text and ride verbatim — the same trust class as the record values
2633
+ * getItems returns. */
2634
+ async function handleGetOntology(deps) {
2635
+ const collections = await buildWorkspaceOntology(deps);
2636
+ return JSON.stringify({
2637
+ count: collections.length,
2638
+ collections
2639
+ });
2640
+ }
2641
+ /** Return the collection-authoring reference (`collection-skills.md`).
2642
+ * Workspace copy first (reflects user edits), bundled asset as the
2643
+ * always-present fallback. Both reads guarded; if neither resolves the
2644
+ * agent still gets an actionable message instead of a thrown call. */
2645
+ async function handleSchemaDocs(deps) {
2646
+ const candidates = [node_path.default.join(resolveBase(deps), HELPS_DIR, SCHEMA_DOCS_FILE), ...deps.bundledHelpsDir ? [node_path.default.join(deps.bundledHelpsDir(), SCHEMA_DOCS_FILE)] : []];
2647
+ for (const candidate of candidates) try {
2648
+ return await (0, node_fs_promises.readFile)(candidate, "utf-8");
2649
+ } catch {}
2650
+ return `manageCollection: could not read the collection-authoring reference (${SCHEMA_DOCS_FILE}).`;
2651
+ }
2652
+ /** Return the raw schema.json of an existing collection, for editing.
2653
+ * Staging (the canonical writable copy) first, the active mirror as a
2654
+ * fallback for user-scope skills that have no staging copy. Raw text —
2655
+ * not the parsed schema — so the agent edits the true on-disk source. */
2656
+ async function handleGetSchema(slug, deps) {
2657
+ const collection = await loadCollection(slug, deps);
2658
+ if (!collection) return unknownCollection(slug);
2659
+ const candidates = [node_path.default.join(require_skill_bridge_index.dataSkillDir(resolveBase(deps), collection.slug), SCHEMA_FILE), node_path.default.join(collection.skillDir, SCHEMA_FILE)];
2660
+ for (const candidate of candidates) try {
2661
+ return await (0, node_fs_promises.readFile)(candidate, "utf-8");
2662
+ } catch {}
2663
+ return `manageCollection: '${require_promptSafety.defangForPrompt(slug)}' has no readable ${SCHEMA_FILE}.`;
2664
+ }
2665
+ /** Refuse a schema edit the host can't honour: user-scope/feed collections
2666
+ * are read-only, and presets (mc-*) re-seed on restart so an edit is lost. */
2667
+ function schemaEditRefusal(collection, slug) {
2668
+ if (collection.source !== "project") return `manageCollection: '${require_promptSafety.defangForPrompt(slug)}' is ${collection.source}-scope and read-only here — only project collections (data/skills/) can be edited.`;
2669
+ 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.`;
2670
+ return null;
2671
+ }
2672
+ /** Turn a CollectionSchemaZ failure into a short, actionable list the
2673
+ * agent can fix, pointing back at the field reference. */
2674
+ function formatSchemaIssues(issues) {
2675
+ const shown = issues.slice(0, 20);
2676
+ const lines = shown.map((issue) => `- ${issue.path.map(String).join(".") || "(root)"}: ${require_promptSafety.defangForPrompt(issue.message)}`).join("\n");
2677
+ const omitted = issues.length - shown.length;
2678
+ return `manageCollection: schema rejected — fix and retry (call schemaDocs for the field reference):\n${lines}${omitted > 0 ? `\n- …and ${omitted} more issue(s); fix these first and retry.` : ""}`;
2679
+ }
2680
+ /** Write the validated schema to the canonical staging copy, then mirror
2681
+ * it into the active `.claude/skills/` tree discovery reads — an internal
2682
+ * write doesn't fire the skill-bridge hook, so we mirror explicitly. */
2683
+ async function writeAndMirrorSchema(slug, schema, deps) {
2684
+ const base = resolveBase(deps);
2685
+ await writeFileAtomic(node_path.default.join(require_skill_bridge_index.dataSkillDir(base, slug), SCHEMA_FILE), `${JSON.stringify(schema, null, 2)}\n`);
2686
+ require_skill_bridge_index.mirrorSkillWrite(base, {
2687
+ slug,
2688
+ relSegments: [SCHEMA_FILE]
2689
+ });
2690
+ try {
2691
+ await deps.refreshAfterWrite?.();
2692
+ } catch {}
2693
+ }
2694
+ /** The post-Zod acceptance gates discovery applies before a parsed schema
2695
+ * becomes a live collection. Mirrors discovery's checks (`loadOneCollection`)
2696
+ * so a write can't pass here yet be silently skipped on the next load.
2697
+ * putSchema only runs for project-scope collections, so the feed-`ingest`
2698
+ * gate doesn't apply. Returns a one-line reason, or null when the schema
2699
+ * would be accepted. */
2700
+ function schemaDiscoveryGate(schema, base) {
2701
+ const primaryField = schema.fields[schema.primaryKey];
2702
+ if (!primaryField) return `primaryKey '${schema.primaryKey}' is not one of the declared fields`;
2703
+ if (primaryField.primary !== true) return `the primaryKey field '${schema.primaryKey}' must be flagged \`primary: true\``;
2704
+ if (resolveDataDir(schema.dataPath, base) === null) return `dataPath '${schema.dataPath}' escapes the workspace`;
2705
+ return null;
2706
+ }
2707
+ /** Validate a schema against CollectionSchemaZ and, on success, persist it.
2708
+ * Edit-only: a new collection is created by writing SKILL.md + schema.json
2709
+ * under data/skills/<slug>/ (the normal create flow), not through here. */
2710
+ async function handlePutSchema(slug, schemaArg, deps) {
2711
+ if (!schemaArg || typeof schemaArg !== "object" || Array.isArray(schemaArg)) return "manageCollection: `schema` is required for putSchema — the full collection schema object.";
2712
+ const collection = await loadCollection(slug, deps);
2713
+ if (!collection) return `manageCollection: unknown collection '${require_promptSafety.defangForPrompt(slug)}' — create it by writing SKILL.md + ${SCHEMA_FILE} under data/skills/${require_promptSafety.defangForPrompt(slug)}/, then edit it here.`;
2714
+ const refusal = schemaEditRefusal(collection, slug);
2715
+ if (refusal) return refusal;
2716
+ const parsed = CollectionSchemaZ.safeParse(schemaArg);
2717
+ if (!parsed.success) return formatSchemaIssues(parsed.error.issues);
2718
+ const gate = schemaDiscoveryGate(parsed.data, resolveBase(deps));
2719
+ 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.`;
2720
+ await writeAndMirrorSchema(collection.slug, parsed.data, deps);
2721
+ return JSON.stringify({
2722
+ collection: collection.slug,
2723
+ written: true
2724
+ });
2725
+ }
2726
+ var MANAGE_COLLECTION_PROMPT = "Use `manageCollection` instead of raw Read/Write/Edit when working with a collection's records OR its schema (raw file I/O stays available as the escape hatch). Before authoring or changing a collection's `schema.json`, call `schemaDocs` to load the field/DSL reference, then read with `getSchema` and write with `putSchema` — `putSchema` validates the whole schema before writing and returns actionable errors instead of silently failing discovery's validation. `getItems` is the only way to see computed values — `derived` fields (e.g. a portfolio's value), `toggle` projections, and `embed` records are host-computed and never present in the stored JSON files. On large collections pass `ids` and/or `fields` to keep the result small. For a question that spans collections (\"which clients have unpaid invoices?\"), start with `getOntology`: it lists every collection with its primaryKey, record count, and outbound `ref`/`embed` relations, so you know which collections to join before reading any records. `putItems` validates every row against the schema before writing (required fields, enum values, primaryKey = record id) and returns `{ written, rejected }`; fix each rejected row using its `problem` text and retry just those rows. Never include computed fields in a row you write. To update a few fields of an existing record, use `mode: \"merge\"` with a partial row ({ id, <changed fields> }) — the default upsert replaces the WHOLE record, so a partial upsert would silently erase every optional field it omits.";
2727
+ /** Validate getItems' optional `ids`/`fields` args, then delegate. */
2728
+ async function dispatchGetItems(collection, args, deps) {
2729
+ const ids = optionalStringArray(args.ids, "ids");
2730
+ if (!ids.ok) return ids.error;
2731
+ const fields = optionalStringArray(args.fields, "fields");
2732
+ if (!fields.ok) return fields.error;
2733
+ return handleGetItems(collection, {
2734
+ slug: collection.slug,
2735
+ ids: ids.value,
2736
+ fields: fields.value
2737
+ }, deps);
2738
+ }
2739
+ async function manageCollectionHandler(deps, args) {
2740
+ const action = typeof args.action === "string" ? args.action : "";
2741
+ if (action === "schemaDocs") return handleSchemaDocs(deps);
2742
+ if (action === "getOntology") return handleGetOntology(deps);
2743
+ const slug = typeof args.slug === "string" ? args.slug.trim() : "";
2744
+ if (!slug) return "manageCollection: `slug` is required (the collection's slug).";
2745
+ if (action === "getSchema") return handleGetSchema(slug, deps);
2746
+ if (action === "putSchema") return handlePutSchema(slug, args.schema, deps);
2747
+ if (action !== "getItems" && action !== "putItems") return "manageCollection: `action` must be \"getItems\", \"putItems\", \"getOntology\", \"schemaDocs\", \"getSchema\", or \"putSchema\".";
2748
+ const collection = await loadCollection(slug, deps);
2749
+ if (!collection) return unknownCollection(slug);
2750
+ if (action === "getItems") return dispatchGetItems(collection, args, deps);
2751
+ const parsed = parsePutItems(args, slug);
2752
+ if (typeof parsed === "string") return parsed;
2753
+ return handlePutItems(collection, parsed, deps);
2754
+ }
2755
+ function makeManageCollectionTool(deps = {}) {
2756
+ return {
2757
+ definition: {
2758
+ name: "manageCollection",
2759
+ description: "Read and write a schema-driven collection through the host — both its records and its structure. getItems returns records WITH computed values (derived formulas, toggles, embeds) the stored JSON files don't contain; putItems validates each row against the schema before writing. getOntology maps the whole workspace: every collection with its record count and outbound ref/embed relations — call it first for cross-collection questions. schemaDocs returns the collection-authoring reference; getSchema/putSchema read and validate-then-write the collection's schema.json. Prefer it over raw file I/O on collections.",
2760
+ inputSchema: {
2761
+ type: "object",
2762
+ properties: {
2763
+ action: {
2764
+ type: "string",
2765
+ enum: [
2766
+ "getItems",
2767
+ "putItems",
2768
+ "getOntology",
2769
+ "schemaDocs",
2770
+ "getSchema",
2771
+ "putSchema"
2772
+ ],
2773
+ description: "What to do."
2774
+ },
2775
+ slug: {
2776
+ type: "string",
2777
+ description: "The collection's slug (its directory name, e.g. `stock-quotes`). Required for everything except schemaDocs and getOntology."
2778
+ },
2779
+ ids: {
2780
+ type: "array",
2781
+ items: { type: "string" },
2782
+ description: "getItems: only these record ids (primary-key values). Omit for all records."
2783
+ },
2784
+ fields: {
2785
+ type: "array",
2786
+ items: { type: "string" },
2787
+ description: "getItems: only these fields per record (the primary key is always included). Omit for all fields. Use on large collections."
2788
+ },
2789
+ items: {
2790
+ type: "array",
2791
+ items: { type: "object" },
2792
+ description: "putItems: the record objects to store. Each must carry the schema's primaryKey value (it doubles as the filename)."
2793
+ },
2794
+ mode: {
2795
+ type: "string",
2796
+ enum: [
2797
+ "upsert",
2798
+ "create",
2799
+ "merge"
2800
+ ],
2801
+ description: "putItems: \"upsert\" (default) replaces existing records WHOLE; \"create\" rejects rows whose id already exists; \"merge\" updates only the fields a row carries, keeping the rest of the existing record (rejects unknown ids). Use \"merge\" when changing a few fields."
2802
+ },
2803
+ schema: {
2804
+ type: "object",
2805
+ 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."
2806
+ }
2807
+ },
2808
+ required: ["action"]
2809
+ }
2810
+ },
2811
+ alwaysActive: true,
2812
+ prompt: MANAGE_COLLECTION_PROMPT,
2813
+ handler: (args) => manageCollectionHandler(deps, args)
2814
+ };
2815
+ }
2430
2816
  //#endregion
2431
2817
  Object.defineProperty(exports, "CollectionSchemaZ", {
2432
2818
  enumerable: true,
@@ -2434,6 +2820,18 @@ Object.defineProperty(exports, "CollectionSchemaZ", {
2434
2820
  return CollectionSchemaZ;
2435
2821
  }
2436
2822
  });
2823
+ Object.defineProperty(exports, "MAX_SCHEMA_ISSUES", {
2824
+ enumerable: true,
2825
+ get: function() {
2826
+ return MAX_SCHEMA_ISSUES;
2827
+ }
2828
+ });
2829
+ Object.defineProperty(exports, "MAX_UNSELECTIVE_ITEMS", {
2830
+ enumerable: true,
2831
+ get: function() {
2832
+ return MAX_UNSELECTIVE_ITEMS;
2833
+ }
2834
+ });
2437
2835
  Object.defineProperty(exports, "ONE_SECOND_MS", {
2438
2836
  enumerable: true,
2439
2837
  get: function() {
@@ -2443,7 +2841,7 @@ Object.defineProperty(exports, "ONE_SECOND_MS", {
2443
2841
  Object.defineProperty(exports, "SCHEMA_FILE", {
2444
2842
  enumerable: true,
2445
2843
  get: function() {
2446
- return SCHEMA_FILE;
2844
+ return SCHEMA_FILE$1;
2447
2845
  }
2448
2846
  });
2449
2847
  Object.defineProperty(exports, "acceptParsedSchema", {
@@ -2632,6 +3030,12 @@ Object.defineProperty(exports, "log", {
2632
3030
  return log;
2633
3031
  }
2634
3032
  });
3033
+ Object.defineProperty(exports, "makeManageCollectionTool", {
3034
+ enumerable: true,
3035
+ get: function() {
3036
+ return makeManageCollectionTool;
3037
+ }
3038
+ });
2635
3039
  Object.defineProperty(exports, "maybeSpawnSuccessor", {
2636
3040
  enumerable: true,
2637
3041
  get: function() {
@@ -2777,4 +3181,4 @@ Object.defineProperty(exports, "writeItem", {
2777
3181
  }
2778
3182
  });
2779
3183
 
2780
- //# sourceMappingURL=server-BeoOWQ-a.cjs.map
3184
+ //# sourceMappingURL=server-Bh-DPK-H.cjs.map