@mulmoclaude/core 0.14.0 → 0.15.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 +27 -5
- package/dist/{calendarGrid-kO6rGfm0.js → calendarGrid-CaR-q36Y.js} +65 -2
- package/dist/calendarGrid-CaR-q36Y.js.map +1 -0
- package/dist/{calendarGrid-C1rfCXJ3.cjs → calendarGrid-gN4sei7s.cjs} +100 -1
- package/dist/calendarGrid-gN4sei7s.cjs.map +1 -0
- package/dist/collection/core/backlinks.d.ts +20 -0
- package/dist/collection/core/recordZ.d.ts +2 -4
- package/dist/collection/core/schema.d.ts +7 -0
- package/dist/collection/core/schemaZ.d.ts +32 -0
- package/dist/collection/core/uiTypes.d.ts +22 -0
- package/dist/collection/index.cjs +17 -36
- package/dist/collection/index.cjs.map +1 -1
- package/dist/collection/index.d.ts +1 -0
- package/dist/collection/index.js +11 -33
- package/dist/collection/index.js.map +1 -1
- package/dist/collection/registry/server/index.cjs +1 -1
- package/dist/collection/registry/server/index.js +1 -1
- package/dist/collection/server/index.cjs +6 -2
- package/dist/collection/server/index.d.ts +1 -0
- package/dist/collection/server/index.js +3 -2
- package/dist/collection/server/io.d.ts +9 -0
- package/dist/collection/server/ontology.d.ts +35 -0
- package/dist/collection-watchers/index.cjs +5 -4
- package/dist/collection-watchers/index.cjs.map +1 -1
- package/dist/collection-watchers/index.js +3 -2
- package/dist/collection-watchers/index.js.map +1 -1
- package/dist/feeds/index.cjs +2 -2
- package/dist/feeds/index.js +2 -2
- package/dist/feeds/server/index.cjs +3 -3
- package/dist/feeds/server/index.js +3 -3
- package/dist/{ingestTypes-BIFXlw7M.cjs → ingestTypes-DG8RExWt.cjs} +2 -2
- package/dist/{ingestTypes-BIFXlw7M.cjs.map → ingestTypes-DG8RExWt.cjs.map} +1 -1
- package/dist/{ingestTypes-KuYPX9Ea.js → ingestTypes-V4upKeBx.js} +2 -2
- package/dist/{ingestTypes-KuYPX9Ea.js.map → ingestTypes-V4upKeBx.js.map} +1 -1
- package/dist/{server-Cc6XcyOA.js → server-BNTR7ySK.js} +121 -16
- package/dist/server-BNTR7ySK.js.map +1 -0
- package/dist/{server-Cd5WdM8a.cjs → server-bsNrf2yW.cjs} +139 -22
- package/dist/server-bsNrf2yW.cjs.map +1 -0
- package/package.json +1 -1
- package/dist/calendarGrid-C1rfCXJ3.cjs.map +0 -1
- package/dist/calendarGrid-kO6rGfm0.js.map +0 -1
- package/dist/server-Cc6XcyOA.js.map +0 -1
- package/dist/server-Cd5WdM8a.cjs.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as isSafeSlug, C as projectBacklinkRow, D as SAFE_RECORD_ID_PATTERN, F as embedTargetId, I as isFieldDrivenEvery, M as COMPUTED_TYPES, N as FEED_SCHEDULES, O as SAFE_SLUG_PATTERN, P as INGEST_KINDS, S as backlinkRows, b as selectDynamicRecord, h as deriveAll, j as AGENT_INGEST_KIND, k as isSafeRecordId, l as parseIsoDate, u as parseIsoDateTime, v as firstDateField, y as resolveIcon } from "./calendarGrid-CaR-q36Y.js";
|
|
2
2
|
import { isSafeActionTemplatePath, isSafeCustomViewI18nPath, isSafeCustomViewPath } from "./collection/paths.js";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { promises, realpathSync } from "node:fs";
|
|
@@ -237,7 +237,9 @@ async function writeFileAtomic(filePath, content) {
|
|
|
237
237
|
* inside an otherwise-contained data dir — without this, a record
|
|
238
238
|
* file could symlink to /etc/passwd and the detail endpoint would
|
|
239
239
|
* happily serve it. Returns false on ENOENT and on any other lstat
|
|
240
|
-
* failure so the caller's "missing" branch covers those cases too.
|
|
240
|
+
* failure so the caller's "missing" branch covers those cases too.
|
|
241
|
+
* Exported so `ontology.ts`'s record COUNT classifies entries with the
|
|
242
|
+
* SAME lstat logic — the two must agree on what a record file is. */
|
|
241
243
|
async function isRegularFile(filePath) {
|
|
242
244
|
try {
|
|
243
245
|
return (await lstat(filePath)).isFile();
|
|
@@ -633,13 +635,6 @@ ${templateText}`;
|
|
|
633
635
|
}
|
|
634
636
|
//#endregion
|
|
635
637
|
//#region src/collection/core/recordZ.ts
|
|
636
|
-
/** derived/embed/toggle are host-computed or projected — never written to
|
|
637
|
-
* the record JSON, so required / value checks must not apply to them. */
|
|
638
|
-
var COMPUTED_TYPES = /* @__PURE__ */ new Set([
|
|
639
|
-
"derived",
|
|
640
|
-
"embed",
|
|
641
|
-
"toggle"
|
|
642
|
-
]);
|
|
643
638
|
/** The emptiness rule shared by `required` and the "only check present
|
|
644
639
|
* values" gate. NOT a truthiness check — `0` and `false` are filled. */
|
|
645
640
|
var isEmptyValue = (value) => value === void 0 || value === null || value === "";
|
|
@@ -996,6 +991,24 @@ var EmbedFieldZ = z.object({
|
|
|
996
991
|
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)",
|
|
997
992
|
path: ["id"]
|
|
998
993
|
});
|
|
994
|
+
/** Display-only REVERSE refs (plan step ② of plans/collection-ontology.md):
|
|
995
|
+
* a read-only sub-table of the records in collection `from` whose `via`
|
|
996
|
+
* ref field stores THIS record's primary key. Stores nothing (joins
|
|
997
|
+
* `COMPUTED_TYPES`); resolution is shared server/client via
|
|
998
|
+
* `core/backlinks.ts`. `display` names the `from` columns to show;
|
|
999
|
+
* `filter` (the standard `when` shape, matched against each SOURCE
|
|
1000
|
+
* record) narrows the rows. Validation is shape-only, like `embed`:
|
|
1001
|
+
* `from` must be a safe slug, but whether it exists — and whether `via` /
|
|
1002
|
+
* `display` name real fields there — resolves fail-soft at render
|
|
1003
|
+
* (empty sub-table). Do NOT add cross-schema existence checks here. */
|
|
1004
|
+
var BacklinksFieldZ = z.object({
|
|
1005
|
+
type: z.literal("backlinks"),
|
|
1006
|
+
...fieldBase,
|
|
1007
|
+
from: z.string().min(1),
|
|
1008
|
+
via: z.string().trim().min(1),
|
|
1009
|
+
display: z.array(z.string().trim().min(1)).min(1),
|
|
1010
|
+
filter: WhenZ.optional()
|
|
1011
|
+
}).refine((spec) => isSafeSlug(spec.from), slugMessage("from"));
|
|
999
1012
|
/** A checkbox that is a pure PROJECTION of an `enum` field — it stores
|
|
1000
1013
|
* nothing of its own. Checked when the enum named by `field` equals
|
|
1001
1014
|
* `onValue`; toggling writes `onValue` / `offValue` back to that enum
|
|
@@ -1018,6 +1031,7 @@ var FieldSpecZ = z.discriminatedUnion("type", [
|
|
|
1018
1031
|
TableFieldZ,
|
|
1019
1032
|
DerivedFieldZ,
|
|
1020
1033
|
EmbedFieldZ,
|
|
1034
|
+
BacklinksFieldZ,
|
|
1021
1035
|
ToggleFieldZ
|
|
1022
1036
|
]);
|
|
1023
1037
|
/** A schema-declared record action, rendered as a button in the read-only
|
|
@@ -1548,6 +1562,72 @@ function toDetail(collection) {
|
|
|
1548
1562
|
};
|
|
1549
1563
|
}
|
|
1550
1564
|
//#endregion
|
|
1565
|
+
//#region src/collection/server/ontology.ts
|
|
1566
|
+
/** Extract the relations a schema declares, in field declaration order:
|
|
1567
|
+
* top-level `ref` / `embed` / `backlinks` fields plus `ref` sub-fields
|
|
1568
|
+
* inside `table` columns. Pure — exported so the phase-2 graph panel
|
|
1569
|
+
* can reuse it on already-loaded schemas. */
|
|
1570
|
+
function schemaRelations(schema) {
|
|
1571
|
+
const relations = [];
|
|
1572
|
+
for (const [key, spec] of Object.entries(schema.fields)) {
|
|
1573
|
+
if (spec.type === "ref" || spec.type === "embed") relations.push({
|
|
1574
|
+
field: key,
|
|
1575
|
+
kind: spec.type,
|
|
1576
|
+
to: spec.to
|
|
1577
|
+
});
|
|
1578
|
+
if (spec.type === "backlinks") relations.push({
|
|
1579
|
+
field: key,
|
|
1580
|
+
kind: "backlinks",
|
|
1581
|
+
to: spec.from
|
|
1582
|
+
});
|
|
1583
|
+
if (spec.type !== "table") continue;
|
|
1584
|
+
for (const [subKey, subSpec] of Object.entries(spec.of)) if (subSpec.type === "ref") relations.push({
|
|
1585
|
+
field: `${key}.${subKey}`,
|
|
1586
|
+
kind: "ref",
|
|
1587
|
+
to: subSpec.to
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1590
|
+
return relations;
|
|
1591
|
+
}
|
|
1592
|
+
/** Count the record files in a collection's data dir — the same
|
|
1593
|
+
* `<id>.json` entries `listItems` considers, WITHOUT parsing them (the
|
|
1594
|
+
* ontology is a summary; a malformed record is still a record). Each
|
|
1595
|
+
* candidate is classified by the SHARED `isRegularFile` lstat helper —
|
|
1596
|
+
* the exact file-disclosure defense `listItems` applies — so a
|
|
1597
|
+
* symlinked record never counts and the count can't diverge from the
|
|
1598
|
+
* readable set on any filesystem (Codex review on PR #2099, twice).
|
|
1599
|
+
* Fail-soft: a missing dir or a dataDir escaping the workspace via
|
|
1600
|
+
* symlink counts 0. */
|
|
1601
|
+
async function countRecordFiles(dataDir, workspaceRoot) {
|
|
1602
|
+
if (!isContainedInRoot(dataDir, workspaceRoot)) return 0;
|
|
1603
|
+
try {
|
|
1604
|
+
const names = (await readdir(dataDir)).filter((name) => name.endsWith(".json") && !name.startsWith("."));
|
|
1605
|
+
return (await Promise.all(names.map((name) => isRegularFile(path.join(dataDir, name))))).filter(Boolean).length;
|
|
1606
|
+
} catch {
|
|
1607
|
+
return 0;
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
async function toOntologyEntry(collection, workspaceRoot) {
|
|
1611
|
+
const { schema } = collection;
|
|
1612
|
+
return {
|
|
1613
|
+
slug: collection.slug,
|
|
1614
|
+
title: schema.title,
|
|
1615
|
+
icon: schema.icon,
|
|
1616
|
+
primaryKey: schema.primaryKey,
|
|
1617
|
+
displayField: schema.displayField ?? schema.primaryKey,
|
|
1618
|
+
recordCount: await countRecordFiles(collection.dataDir, workspaceRoot),
|
|
1619
|
+
relations: schemaRelations(schema)
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1622
|
+
/** Build the workspace ontology: every discovered collection (slug-sorted,
|
|
1623
|
+
* discovery's order), each with its outbound relations and a
|
|
1624
|
+
* readdir-cheap record count — fine to call on demand. */
|
|
1625
|
+
async function buildWorkspaceOntology(opts = {}) {
|
|
1626
|
+
const workspaceRoot = opts.workspaceRoot ?? getWorkspaceRoot();
|
|
1627
|
+
const collections = await discoverCollections(opts);
|
|
1628
|
+
return Promise.all(collections.map((collection) => toOntologyEntry(collection, workspaceRoot)));
|
|
1629
|
+
}
|
|
1630
|
+
//#endregion
|
|
1551
1631
|
//#region src/collection/server/derive.ts
|
|
1552
1632
|
/** Slugs of every collection referenced by a `ref` field — top-level
|
|
1553
1633
|
* and one level into `table` sub-fields (nested tables are
|
|
@@ -1570,6 +1650,13 @@ function uniqueEmbedTargets(schema) {
|
|
|
1570
1650
|
for (const field of Object.values(schema.fields)) if (field.type === "embed" && typeof field.to === "string" && field.to.length > 0) targets.add(field.to);
|
|
1571
1651
|
return [...targets];
|
|
1572
1652
|
}
|
|
1653
|
+
/** Slugs of every SOURCE collection a `backlinks` field reverses over —
|
|
1654
|
+
* loaded exactly like ref/embed targets (whole collection, once). */
|
|
1655
|
+
function uniqueBacklinkSources(schema) {
|
|
1656
|
+
const sources = /* @__PURE__ */ new Set();
|
|
1657
|
+
for (const field of Object.values(schema.fields)) if (field.type === "backlinks" && field.from.length > 0) sources.add(field.from);
|
|
1658
|
+
return [...sources];
|
|
1659
|
+
}
|
|
1573
1660
|
async function loadTarget(slug, opts) {
|
|
1574
1661
|
const target = await loadCollection(slug, opts);
|
|
1575
1662
|
if (!target) return null;
|
|
@@ -1584,11 +1671,16 @@ async function loadTarget(slug, opts) {
|
|
|
1584
1671
|
byId
|
|
1585
1672
|
};
|
|
1586
1673
|
}
|
|
1587
|
-
/** Load every ref/embed target collection once.
|
|
1588
|
-
* targets are simply absent — downstream derefs
|
|
1589
|
-
*
|
|
1674
|
+
/** Load every ref/embed target and backlink source collection once.
|
|
1675
|
+
* Unknown / unloadable targets are simply absent — downstream derefs
|
|
1676
|
+
* resolve to null (em-dash) and backlinks to an empty row set, the
|
|
1677
|
+
* same fail-soft the UI renders. */
|
|
1590
1678
|
async function loadLinkedTargets(schema, opts) {
|
|
1591
|
-
const slugs = [.../* @__PURE__ */ new Set([
|
|
1679
|
+
const slugs = [.../* @__PURE__ */ new Set([
|
|
1680
|
+
...uniqueRefTargets(schema),
|
|
1681
|
+
...uniqueEmbedTargets(schema),
|
|
1682
|
+
...uniqueBacklinkSources(schema)
|
|
1683
|
+
])];
|
|
1592
1684
|
const loaded = {};
|
|
1593
1685
|
for (const slug of slugs) {
|
|
1594
1686
|
const target = await loadTarget(slug, opts);
|
|
@@ -1599,9 +1691,21 @@ async function loadLinkedTargets(schema, opts) {
|
|
|
1599
1691
|
function toRefRecords(linked) {
|
|
1600
1692
|
return Object.fromEntries(Object.entries(linked).map(([slug, target]) => [slug, target.byId]));
|
|
1601
1693
|
}
|
|
1694
|
+
/** The matching source rows for one `backlinks` field, projected to the
|
|
1695
|
+
* source primaryKey + `display` columns — so getItems on a
|
|
1696
|
+
* heavily-referenced record stays a summary, not a dump of the source
|
|
1697
|
+
* collection. Missing source ⇒ [] (fail-soft). The rows come from the
|
|
1698
|
+
* DERIVED source records (`byId`), so `display`/`filter` on a derived
|
|
1699
|
+
* source column (an invoice `total`) works. */
|
|
1700
|
+
function projectBacklinks(field, schema, enriched, linked) {
|
|
1701
|
+
const source = linked[field.from];
|
|
1702
|
+
if (!source) return [];
|
|
1703
|
+
return backlinkRows(field, String(enriched[schema.primaryKey] ?? ""), Object.values(source.byId)).map((row) => projectBacklinkRow(row, field.display, source.schema.primaryKey));
|
|
1704
|
+
}
|
|
1602
1705
|
/** Project the computed (never-stored) field kinds onto one derived
|
|
1603
1706
|
* record: `toggle` → boolean off its enum, `embed` → the target record
|
|
1604
|
-
* (fixed `id` or per-record `idField`), or null when missing
|
|
1707
|
+
* (fixed `id` or per-record `idField`), or null when missing,
|
|
1708
|
+
* `backlinks` → the matching source rows (see `projectBacklinks`). */
|
|
1605
1709
|
function projectComputed(schema, enriched, linked) {
|
|
1606
1710
|
for (const [key, field] of Object.entries(schema.fields)) {
|
|
1607
1711
|
if (field.type === "toggle" && field.field) enriched[key] = String(enriched[field.field] ?? "") === field.onValue;
|
|
@@ -1609,6 +1713,7 @@ function projectComputed(schema, enriched, linked) {
|
|
|
1609
1713
|
const targetId = embedTargetId(field, enriched);
|
|
1610
1714
|
enriched[key] = targetId && linked[field.to]?.byId[targetId] || null;
|
|
1611
1715
|
}
|
|
1716
|
+
if (field.type === "backlinks") enriched[key] = projectBacklinks(field, schema, enriched, linked);
|
|
1612
1717
|
}
|
|
1613
1718
|
return enriched;
|
|
1614
1719
|
}
|
|
@@ -2145,6 +2250,6 @@ async function deleteCustomView(collection, viewId, opts = {}) {
|
|
|
2145
2250
|
};
|
|
2146
2251
|
}
|
|
2147
2252
|
//#endregion
|
|
2148
|
-
export {
|
|
2253
|
+
export { getWorkspaceRoot as $, buildCollectionActionSeedPrompt as A, resolveCreateItemId as B, toSummary as C, compileRecordZ as D, validateRecordObject as E, promptPathsFor as F, isContainedInWorkspace as G, writeFileAtomic as H, readCustomViewHtml as I, resolveTemplatePath as J, itemFilePath as K, readCustomViewI18n as L, generateItemId as M, isRegularFile as N, recordFieldProblem as O, listItems as P, configureCollectionHost as Q, readItem as R, toDetail as S, validateCollectionRecords as T, SCHEMA_FILE as U, writeItem as V, isContainedInRoot as W, safeSlugName as X, safeRecordId as Y, collectionsRegistriesConfigPath as Z, buildWorkspaceOntology as _, computeSuccessor as a, discoverCollections as b, isTriggerDue as c, resolveEvery as d, log as et, successorId as f, enrichItems as g, computeCollectionIcon as h, advanceTriggerDate as i, deleteItem as j, buildActionSeedPrompt as k, maybeSpawnSuccessor as l, errorMessage as m, deleteCollection as n, setCollectionChangePublisher as nt, daysInMonth as o, ONE_SECOND_MS as p, resolveDataDir as q, deleteCollectionRefusalMessage as r, formatCivil as s, deleteCustomView as t, publishCollectionChange as tt, parseCivil as u, schemaRelations as v, CollectionSchemaZ as w, loadCollection as x, acceptParsedSchema as y, readSkillTemplate as z };
|
|
2149
2254
|
|
|
2150
|
-
//# sourceMappingURL=server-
|
|
2255
|
+
//# sourceMappingURL=server-BNTR7ySK.js.map
|