@mulmoclaude/core 0.24.0 → 0.25.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 (76) hide show
  1. package/assets/helps/google-calendar-collection.md +79 -0
  2. package/assets/helps/index.md +1 -0
  3. package/dist/collection/core/schemaZ.d.ts +29 -0
  4. package/dist/collection/index.cjs +15 -14
  5. package/dist/collection/index.cjs.map +1 -1
  6. package/dist/collection/index.js +2 -1
  7. package/dist/collection/index.js.map +1 -1
  8. package/dist/collection/paths.cjs +5 -55
  9. package/dist/collection/paths.js +1 -51
  10. package/dist/collection/registry/server/index.cjs +20 -19
  11. package/dist/collection/registry/server/index.cjs.map +1 -1
  12. package/dist/collection/registry/server/index.js +3 -2
  13. package/dist/collection/registry/server/index.js.map +1 -1
  14. package/dist/collection/server/index.cjs +41 -40
  15. package/dist/collection/server/index.js +4 -3
  16. package/dist/collection-watchers/index.cjs +12 -11
  17. package/dist/collection-watchers/index.cjs.map +1 -1
  18. package/dist/collection-watchers/index.js +3 -2
  19. package/dist/collection-watchers/index.js.map +1 -1
  20. package/dist/discovery-3K_Wtkvn.js +1636 -0
  21. package/dist/discovery-3K_Wtkvn.js.map +1 -0
  22. package/dist/discovery-BZ2ms1tE.cjs +1865 -0
  23. package/dist/discovery-BZ2ms1tE.cjs.map +1 -0
  24. package/dist/feeds/index.cjs +4 -4
  25. package/dist/feeds/index.js +2 -2
  26. package/dist/feeds/server/index.cjs +16 -15
  27. package/dist/feeds/server/index.cjs.map +1 -1
  28. package/dist/feeds/server/index.js +4 -3
  29. package/dist/feeds/server/index.js.map +1 -1
  30. package/dist/google/apiClient.d.ts +8 -1
  31. package/dist/google/calendar.d.ts +35 -0
  32. package/dist/google/calendarSyncStore.d.ts +7 -0
  33. package/dist/google/collectionSync.d.ts +70 -0
  34. package/dist/google/index.cjs +296 -4
  35. package/dist/google/index.cjs.map +1 -1
  36. package/dist/google/index.d.ts +4 -2
  37. package/dist/google/index.js +281 -5
  38. package/dist/google/index.js.map +1 -1
  39. package/dist/ids-BR4U1dJD.cjs +150 -0
  40. package/dist/ids-BR4U1dJD.cjs.map +1 -0
  41. package/dist/ids-D4e10971.js +85 -0
  42. package/dist/ids-D4e10971.js.map +1 -0
  43. package/dist/{ingestTypes-CFEgTItj.js → ingestTypes-BsGOjR2l.js} +2 -2
  44. package/dist/{ingestTypes-CFEgTItj.js.map → ingestTypes-BsGOjR2l.js.map} +1 -1
  45. package/dist/{ingestTypes-Stc7iQyA.cjs → ingestTypes-CygUXsjf.cjs} +3 -3
  46. package/dist/{ingestTypes-Stc7iQyA.cjs.map → ingestTypes-CygUXsjf.cjs.map} +1 -1
  47. package/dist/{promptSafety-BLUcImzd.js → promptSafety-Boz5qOs-.js} +2 -84
  48. package/dist/promptSafety-Boz5qOs-.js.map +1 -0
  49. package/dist/{promptSafety-DI_1Rvhk.cjs → promptSafety-Co4cYQ_Z.cjs} +1 -149
  50. package/dist/promptSafety-Co4cYQ_Z.cjs.map +1 -0
  51. package/dist/remote-host/server/index.cjs +6 -4
  52. package/dist/remote-host/server/index.cjs.map +1 -1
  53. package/dist/remote-host/server/index.js +6 -4
  54. package/dist/remote-host/server/index.js.map +1 -1
  55. package/dist/scheduler/index.cjs +3 -1
  56. package/dist/scheduler/index.cjs.map +1 -1
  57. package/dist/scheduler/index.js +3 -1
  58. package/dist/scheduler/index.js.map +1 -1
  59. package/dist/{server-CkysfLh4.cjs → server-DLRz-U7F.cjs} +76 -1884
  60. package/dist/server-DLRz-U7F.cjs.map +1 -0
  61. package/dist/{server-BpEo0FOd.js → server-DZH7OjOD.js} +5 -1609
  62. package/dist/server-DZH7OjOD.js.map +1 -0
  63. package/dist/skill-bridge/index.cjs +2 -2
  64. package/dist/skill-bridge/index.cjs.map +1 -1
  65. package/dist/skill-bridge/index.js +1 -1
  66. package/dist/templatePath-27vUZowm.cjs +75 -0
  67. package/dist/templatePath-27vUZowm.cjs.map +1 -0
  68. package/dist/templatePath-k_WNbL_Q.js +52 -0
  69. package/dist/templatePath-k_WNbL_Q.js.map +1 -0
  70. package/package.json +1 -1
  71. package/dist/collection/paths.cjs.map +0 -1
  72. package/dist/collection/paths.js.map +0 -1
  73. package/dist/promptSafety-BLUcImzd.js.map +0 -1
  74. package/dist/promptSafety-DI_1Rvhk.cjs.map +0 -1
  75. package/dist/server-BpEo0FOd.js.map +0 -1
  76. package/dist/server-CkysfLh4.cjs.map +0 -1
@@ -0,0 +1,150 @@
1
+ //#region src/collection/core/schema.ts
2
+ /** Declarative retriever kinds a Feed's `ingest.kind` may declare. The host's
3
+ * feeds engine dispatches on these; they live here (with the schema contract)
4
+ * so the schema validator can enforce them. The host re-exports these from
5
+ * `server/workspace/feeds/ingestTypes.ts`. */
6
+ var INGEST_KINDS = [
7
+ "rss",
8
+ "atom",
9
+ "http-json"
10
+ ];
11
+ /** The agent-performed ingest kind. Instead of a declarative fetch, the host
12
+ * dispatches a hidden background chat (origin `system`) in `ingest.role`,
13
+ * seeded with `ingest.template` + a summary of every record, on the
14
+ * `ingest.schedule` cadence; the worker edits records via the collections io
15
+ * layer. Kept separate from {@link INGEST_KINDS} (which the declarative
16
+ * retriever registry keys on) so the schema validator can model `ingest` as a
17
+ * discriminated union without the feeds engine gaining an "agent" retriever. */
18
+ var AGENT_INGEST_KIND = "agent";
19
+ /** Refresh cadences a Feed's `ingest.schedule` may declare. */
20
+ var FEED_SCHEDULES = [
21
+ "hourly",
22
+ "daily",
23
+ "weekly",
24
+ "on-demand"
25
+ ];
26
+ /** derived/embed/backlinks/rollup/toggle/flag are host-computed or
27
+ * projected — never written to the record JSON, so required / value
28
+ * checks and edit-draft slots must not apply to them. THE single source
29
+ * for "computed" — lives here (zod-free at runtime) so browser code
30
+ * (`./draft`) and the zod record compiler (`./recordZ`, which re-exports
31
+ * it) share one set instead of drifting copies. */
32
+ var COMPUTED_TYPES = /* @__PURE__ */ new Set([
33
+ "derived",
34
+ "embed",
35
+ "backlinks",
36
+ "rollup",
37
+ "toggle",
38
+ "flag"
39
+ ]);
40
+ /** Narrowing guard: true when `every` is the field-driven arm. */
41
+ function isFieldDrivenEvery(every) {
42
+ return "fromField" in every;
43
+ }
44
+ /** True when `schema` declares an external `dataSource` — i.e. the
45
+ * collection is READ-ONLY through every UI/tool write path (updates
46
+ * happen by editing/replacing the data file itself). Isomorphic: both
47
+ * the server write guards and the client's control hiding key off this
48
+ * one predicate. */
49
+ function isReadOnlySchema(schema) {
50
+ return schema.dataSource !== void 0;
51
+ }
52
+ /** Resolve an `embed` field's target record id: the fixed `id`, or the value
53
+ * of the sibling `idField` on this record (empty string when neither applies
54
+ * — the caller renders that as "no record"). Pure + isomorphic so the server
55
+ * projection (`derive.ts`) and the client preview (`useCollectionRendering`)
56
+ * resolve embeds identically. Non-`embed` fields resolve to "no record". */
57
+ function embedTargetId(field, record) {
58
+ if (field.type !== "embed") return "";
59
+ if (field.id) return field.id;
60
+ if (field.idField && record) return String(record[field.idField] ?? "");
61
+ return "";
62
+ }
63
+ //#endregion
64
+ //#region src/collection/core/ids.ts
65
+ var SAFE_SLUG_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9_-]*[a-zA-Z0-9])?$/;
66
+ var SAFE_RECORD_ID_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9_.-]*[a-zA-Z0-9])?$/;
67
+ /** True when `value` is a well-formed collection slug (alphanumeric /
68
+ * hyphen / underscore, no path separators). The pattern admits no `/`,
69
+ * `\`, or `.`, so a passing value is trivially also a safe basename —
70
+ * validation callers need no `path.basename` round-trip (path-building
71
+ * callers use `../server/paths#safeSlugName`, which adds it). */
72
+ function isSafeSlug(value) {
73
+ return typeof value === "string" && SAFE_SLUG_PATTERN.test(value);
74
+ }
75
+ /** True when `value` is a well-formed record id (slug charset plus interior
76
+ * dots), with any `..` substring rejected explicitly. Validation-only
77
+ * counterpart of `../server/paths#safeRecordId`. */
78
+ function isSafeRecordId(value) {
79
+ if (typeof value !== "string" || !SAFE_RECORD_ID_PATTERN.test(value)) return false;
80
+ return !value.includes("..");
81
+ }
82
+ //#endregion
83
+ Object.defineProperty(exports, "AGENT_INGEST_KIND", {
84
+ enumerable: true,
85
+ get: function() {
86
+ return AGENT_INGEST_KIND;
87
+ }
88
+ });
89
+ Object.defineProperty(exports, "COMPUTED_TYPES", {
90
+ enumerable: true,
91
+ get: function() {
92
+ return COMPUTED_TYPES;
93
+ }
94
+ });
95
+ Object.defineProperty(exports, "FEED_SCHEDULES", {
96
+ enumerable: true,
97
+ get: function() {
98
+ return FEED_SCHEDULES;
99
+ }
100
+ });
101
+ Object.defineProperty(exports, "INGEST_KINDS", {
102
+ enumerable: true,
103
+ get: function() {
104
+ return INGEST_KINDS;
105
+ }
106
+ });
107
+ Object.defineProperty(exports, "SAFE_RECORD_ID_PATTERN", {
108
+ enumerable: true,
109
+ get: function() {
110
+ return SAFE_RECORD_ID_PATTERN;
111
+ }
112
+ });
113
+ Object.defineProperty(exports, "SAFE_SLUG_PATTERN", {
114
+ enumerable: true,
115
+ get: function() {
116
+ return SAFE_SLUG_PATTERN;
117
+ }
118
+ });
119
+ Object.defineProperty(exports, "embedTargetId", {
120
+ enumerable: true,
121
+ get: function() {
122
+ return embedTargetId;
123
+ }
124
+ });
125
+ Object.defineProperty(exports, "isFieldDrivenEvery", {
126
+ enumerable: true,
127
+ get: function() {
128
+ return isFieldDrivenEvery;
129
+ }
130
+ });
131
+ Object.defineProperty(exports, "isReadOnlySchema", {
132
+ enumerable: true,
133
+ get: function() {
134
+ return isReadOnlySchema;
135
+ }
136
+ });
137
+ Object.defineProperty(exports, "isSafeRecordId", {
138
+ enumerable: true,
139
+ get: function() {
140
+ return isSafeRecordId;
141
+ }
142
+ });
143
+ Object.defineProperty(exports, "isSafeSlug", {
144
+ enumerable: true,
145
+ get: function() {
146
+ return isSafeSlug;
147
+ }
148
+ });
149
+
150
+ //# sourceMappingURL=ids-BR4U1dJD.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ids-BR4U1dJD.cjs","names":[],"sources":["../src/collection/core/schema.ts","../src/collection/core/ids.ts"],"sourcesContent":["// Schema-driven collection types. A \"collection\" is a skill (under\n// .claude/skills/<slug>/) that also ships a sibling `schema.json`.\n// The host's <CollectionView> reads the schema + records and renders\n// a table/form; Claude reads SKILL.md and CRUDs the records as JSON\n// files.\n//\n// SINGLE SOURCE OF TRUTH: every type describing the schema.json contract is\n// derived (`z.infer`) from the zod definitions in `./schemaZ` — the shapes,\n// their doc comments, and the validation rules live THERE; this module only\n// re-derives the TypeScript names consumers import. The imports from\n// `./schemaZ` are type-only, so zod never reaches the browser bundle through\n// the isomorphic barrel; at runtime the dependency points the other way\n// (schemaZ imports this module's consts).\n//\n// Field specs are a DISCRIMINATED UNION on `type`: narrow with\n// `field.type === \"enum\"` (etc.) before reading a variant key like `values`,\n// `to`, `formula`, or `of`.\n\nimport type { z } from \"zod\";\nimport type {\n ActionSpecZ,\n CollectionSchemaZ,\n CustomViewZ,\n DataSourceZ,\n DynamicIconRuleZ,\n DynamicIconSourceZ,\n DynamicIconSpecZ,\n EveryFieldDrivenZ,\n EveryLiteralZ,\n EveryZ,\n FieldSpecZ,\n IngestZ,\n SpawnZ,\n SubFieldSpecZ,\n WhenZ,\n} from \"./schemaZ\";\n\n/** Minimal \"this collection is a feed\" descriptor carried on the schema.\n * Deliberately narrow — the canonical collection contract stays\n * independent of the host's feeds subsystem. The host's richer retrieval\n * spec (`IngestSpec` in `feeds/ingestTypes.ts`) is a subtype, so feed code\n * reads the extra fields by typing feed schemas with that subtype;\n * collection rendering only needs these three + the presence check. */\nexport interface CollectionIngest {\n kind: string;\n schedule: string;\n /** Optional time-of-day anchor for `schedule: \"daily\"` — the hour (0–23) to\n * refresh around (the host ticks hourly, so the run lands within that hour).\n * Ignored for non-daily schedules. Absent ⇒ elapsed-based daily (\"≥24 h since\n * the last run\"). NOTE: **UTC**, not local — compared via `getUTCHours()` for\n * an unambiguous, DST-free check (matching the rest of the scheduler), so\n * convert local times before writing (e.g. 07:00 JST → `atHour: 22`). */\n atHour?: number;\n /** Declarative retrievers (`rss`/`atom`/`http-json`) only — the host fetches\n * this URL on the schedule. Absent for `kind: \"agent\"`, where the agent owns\n * retrieval. */\n url?: string;\n /** `kind: \"agent\"` only: role id the scheduled hidden worker runs in. */\n role?: string;\n /** `kind: \"agent\"` only: skill-relative template path (under `templates/`)\n * whose prose tells the worker how to refresh the records. */\n template?: string;\n}\n\n/** Declarative retriever kinds a Feed's `ingest.kind` may declare. The host's\n * feeds engine dispatches on these; they live here (with the schema contract)\n * so the schema validator can enforce them. The host re-exports these from\n * `server/workspace/feeds/ingestTypes.ts`. */\nexport const INGEST_KINDS = [\"rss\", \"atom\", \"http-json\"] as const;\nexport type IngestKind = (typeof INGEST_KINDS)[number];\n\n/** The agent-performed ingest kind. Instead of a declarative fetch, the host\n * dispatches a hidden background chat (origin `system`) in `ingest.role`,\n * seeded with `ingest.template` + a summary of every record, on the\n * `ingest.schedule` cadence; the worker edits records via the collections io\n * layer. Kept separate from {@link INGEST_KINDS} (which the declarative\n * retriever registry keys on) so the schema validator can model `ingest` as a\n * discriminated union without the feeds engine gaining an \"agent\" retriever. */\nexport const AGENT_INGEST_KIND = \"agent\" as const;\nexport type AgentIngestKind = typeof AGENT_INGEST_KIND;\n\n/** Refresh cadences a Feed's `ingest.schedule` may declare. */\nexport const FEED_SCHEDULES = [\"hourly\", \"daily\", \"weekly\", \"on-demand\"] as const;\nexport type FeedSchedule = (typeof FEED_SCHEDULES)[number];\n\n// \"feed\" collections live in the non-skill `<workspace>/feeds/` registry\n// and carry an `ingest` block; they reuse the same storage + rendering\n// as skill-backed collections but are never loaded into the agent prompt.\nexport type CollectionSource = \"user\" | \"project\" | \"feed\";\n\n/** One field of a record — a discriminated union on `type`; see the variant\n * docs in `./schemaZ` (`FieldSpecZ`). */\nexport type CollectionFieldSpec = z.infer<typeof FieldSpecZ>;\n\n/** A `table` field's row sub-schema entry — the field union minus `table` /\n * `derived` / display-only types (see `SubFieldSpecZ`). */\nexport type CollectionSubFieldSpec = z.infer<typeof SubFieldSpecZ>;\n\nexport type CollectionFieldType = CollectionFieldSpec[\"type\"];\n\n/** The computed-boolean variant — a `where` predicate bound to a field\n * name; see `FlagFieldZ`. */\nexport type CollectionFlagField = Extract<CollectionFieldSpec, { type: \"flag\" }>;\n\n/** derived/embed/backlinks/rollup/toggle/flag are host-computed or\n * projected — never written to the record JSON, so required / value\n * checks and edit-draft slots must not apply to them. THE single source\n * for \"computed\" — lives here (zod-free at runtime) so browser code\n * (`./draft`) and the zod record compiler (`./recordZ`, which re-exports\n * it) share one set instead of drifting copies. */\nexport const COMPUTED_TYPES: ReadonlySet<CollectionFieldType> = new Set<CollectionFieldType>([\"derived\", \"embed\", \"backlinks\", \"rollup\", \"toggle\", \"flag\"]);\n\n/** Optional visibility predicate: the target (an action button or a\n * field) renders only when the open record's `field` (stringified) is\n * one of `in`. Generic and domain-free — the host evaluates it against\n * the record with no knowledge of what the field means. Absent ⇒\n * always shown. */\nexport type CollectionWhen = z.infer<typeof WhenZ>;\n\n/** @deprecated Name retained for back-compat; use {@link CollectionWhen}.\n * Both actions and fields share the same predicate shape. No in-repo\n * consumers, but the package is public API (MulmoTerminal). */\n// eslint-disable-next-line sonarjs/redundant-type-aliases -- deliberate deprecated back-compat export\nexport type CollectionActionWhen = CollectionWhen;\n\n/** A schema-declared, per-record action rendered as a button in the\n * read-only detail view. Pure UI/behaviour directive — never stored,\n * never validated against record data. All domain specifics (label,\n * role, template — or the declarative `set`) live in the schema / skill\n * folder, so the host stays generic. A discriminated union on `kind`;\n * see `ActionSpecZ`. */\nexport type CollectionAction = z.infer<typeof ActionSpecZ>;\n\n/** The kind of work an action kicks off: `\"chat\"` (visible LLM chat),\n * `\"agent\"` (hidden LLM worker), or `\"mutate\"` (declarative host write,\n * no LLM). */\nexport type CollectionActionKind = CollectionAction[\"kind\"];\n\n/** The LLM-seeded action variants (`role` + `template`). */\nexport type CollectionSeededAction = Extract<CollectionAction, { kind: \"chat\" | \"agent\" }>;\n\n/** The declarative host-write variant (`set` + optional `require`/`params`). */\nexport type CollectionMutateAction = Extract<CollectionAction, { kind: \"mutate\" }>;\n\n/** A custom (LLM-authored) HTML view for a collection. The host renders\n * `file` in a sandboxed iframe over the collection's records; the view\n * reaches its data only through a slug- and capability-scoped token (see\n * `server/api/auth/viewToken.ts`). Pure data — the host holds no\n * view-specific code; meaning lives in the HTML file + this registration.\n * See `CustomViewZ` for the per-key contracts. */\nexport type CollectionCustomView = z.infer<typeof CustomViewZ>;\n\n/** What a custom view's capability token is allowed to do against the\n * collection's data endpoint. `read` returns enriched records (getItems\n * semantics); `write` validates-and-stores rows (putItems semantics).\n * There is deliberately no `delete` — a view can never do more than the\n * agent's own `manageCollection` tool. */\nexport type CollectionViewCapability = NonNullable<CollectionCustomView[\"capabilities\"]>[number];\n\n/** How a `spawn` advances the source item's `triggerField` date to\n * produce the successor's. All arithmetic is done on the civil\n * (year, month, day) triple — never by adding milliseconds — so month\n * lengths and leap years are handled correctly. */\nexport type CollectionEvery = z.infer<typeof EveryLiteralZ>;\n\n/** Recurrence unit for a `spawn.every` advance. */\nexport type CollectionRecurUnit = CollectionEvery[\"unit\"];\n\n/** Field-driven recurrence: the advance interval is selected PER RECORD by\n * the value of an `enum` field (`fromField`), looked up in `map`. See\n * `EveryFieldDrivenZ`. */\nexport type CollectionEveryFieldDriven = z.infer<typeof EveryFieldDrivenZ>;\n\n/** The `every` of a `spawn`: either a single literal interval applied to\n * every record, or a per-record interval selected by an `enum` field. The\n * literal arm is what `advanceTriggerDate` consumes — the field-driven arm\n * is resolved down to one of its `map` values before the date math runs. */\nexport type CollectionSpawnEvery = z.infer<typeof EveryZ>;\n\n/** Narrowing guard: true when `every` is the field-driven arm. */\nexport function isFieldDrivenEvery(every: CollectionSpawnEvery): every is CollectionEveryFieldDriven {\n return \"fromField\" in every;\n}\n\n/** Host-driven recurrence. See `SpawnZ`. */\nexport type CollectionSpawn = z.infer<typeof SpawnZ>;\n\n/** One rule in a `dynamicIcon.rules` list: when the resolved source\n * record matches `where` (an AND of typed conditions, see `./where`),\n * the collection's effective launcher icon becomes `icon`. Evaluated top\n * to bottom — the first match wins. */\nexport type DynamicIconRule = z.infer<typeof DynamicIconRuleZ>;\n\n/** Where a {@link DynamicIconSpec}'s source record comes from: a (possibly\n * cross-collection) pool of records, optionally narrowed by `where` and\n * reduced to a single record by `from`. */\nexport type DynamicIconSource = z.infer<typeof DynamicIconSourceZ>;\n\n/** Declarative \"data state → icon\" mapping for a collection's launcher\n * shortcut icon (see `CollectionSchema.dynamicIcon`). When absent, the\n * launcher icon is the static `schema.icon`. */\nexport type DynamicIconSpec = z.infer<typeof DynamicIconSpecZ>;\n\n/** The `ingest` block as the schema validator accepts it — a discriminated\n * union on `kind` (declarative retrievers | agent worker). The feeds\n * subsystem's `IngestSpec` is the same union under its historical name. */\nexport type CollectionIngestSpec = z.infer<typeof IngestZ>;\n\n/** The `dataSource` block: this collection's records are the rows of an\n * external read-only data file (v1: CSV). See `DataSourceZ`. */\nexport type CollectionDataSource = z.infer<typeof DataSourceZ>;\n\n/** The whole `schema.json` contract. Key-level docs live on\n * `CollectionSchemaZ` in `./schemaZ`. */\nexport type CollectionSchema = z.infer<typeof CollectionSchemaZ>;\n\n/** True when `schema` declares an external `dataSource` — i.e. the\n * collection is READ-ONLY through every UI/tool write path (updates\n * happen by editing/replacing the data file itself). Isomorphic: both\n * the server write guards and the client's control hiding key off this\n * one predicate. */\nexport function isReadOnlySchema(schema: Pick<CollectionSchema, \"dataSource\">): boolean {\n return schema.dataSource !== undefined;\n}\n\nexport interface CollectionSummary {\n slug: string;\n title: string;\n icon: string;\n source: CollectionSource;\n /** Present (true) when the collection is backed by an external\n * `dataSource` and therefore read-only in every UI/tool write path.\n * Absent-when-writable, matching the other optional summary flags. */\n readonly?: true;\n /** Slugs of the source collection(s) a `dynamicIcon` icon was computed\n * from — present only when `schema.dynamicIcon` is set. Lets a client\n * know which collection change-channel(s) to watch for a live icon\n * update (see `useDynamicShortcutIcons`). */\n iconSources?: string[];\n}\n\nexport interface CollectionDetail extends CollectionSummary {\n schema: CollectionSchema;\n}\n\nexport type CollectionItem = Record<string, unknown>;\n\n/** Resolve an `embed` field's target record id: the fixed `id`, or the value\n * of the sibling `idField` on this record (empty string when neither applies\n * — the caller renders that as \"no record\"). Pure + isomorphic so the server\n * projection (`derive.ts`) and the client preview (`useCollectionRendering`)\n * resolve embeds identically. Non-`embed` fields resolve to \"no record\". */\nexport function embedTargetId(field: CollectionFieldSpec, record: CollectionItem | null): string {\n if (field.type !== \"embed\") return \"\";\n if (field.id) return field.id;\n if (field.idField && record) return String(record[field.idField] ?? \"\");\n return \"\";\n}\n","// Pure slug / record-id character rules. Shared by the isomorphic schema\n// validator (`./schemaZ`) — which must stay node-free — and the server-side\n// path sanitisers (`../server/paths`), which wrap these patterns with the\n// `path.basename` round-trip CodeQL recognises as a `js/path-injection`\n// sanitiser. Both layers MUST gate on the same patterns; importing them from\n// here is what keeps them in sync.\n\n// The ONE slug pattern — `server/workspace/skills/catalog.ts` imports it\n// for its own sanitiser, so there is no second copy to keep in sync.\n// Bounded character classes, no nested quantifiers; ReDoS-safe.\n// eslint-disable-next-line security/detect-unsafe-regex -- non-overlapping character classes, no catastrophic backtracking\nexport const SAFE_SLUG_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9_-]*[a-zA-Z0-9])?$/;\n\n// Record ids are a superset of slugs: they're only ever filename stems\n// (`<id>.json`), never directory names or URL segments, so they may carry\n// dots — natural keys like a Slack ts (`1718900000.123456`), a SemVer\n// (`1.2.3`), or a decimal timestamp. The interior class adds `.` to the slug\n// set; the explicit `..` reject in `isSafeRecordId` keeps a\n// parent-dir-looking segment out while still allowing repeated `-`/`_`\n// (`a--b`, `a__b`). Start/end stay alphanumeric so leading/trailing dots\n// (hidden files, the special `.`/`..` names) and `..`-only ids are all\n// excluded.\n// eslint-disable-next-line security/detect-unsafe-regex -- non-overlapping character classes, no catastrophic backtracking\nexport const SAFE_RECORD_ID_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9_.-]*[a-zA-Z0-9])?$/;\n\n/** True when `value` is a well-formed collection slug (alphanumeric /\n * hyphen / underscore, no path separators). The pattern admits no `/`,\n * `\\`, or `.`, so a passing value is trivially also a safe basename —\n * validation callers need no `path.basename` round-trip (path-building\n * callers use `../server/paths#safeSlugName`, which adds it). */\nexport function isSafeSlug(value: string): boolean {\n return typeof value === \"string\" && SAFE_SLUG_PATTERN.test(value);\n}\n\n/** True when `value` is a well-formed record id (slug charset plus interior\n * dots), with any `..` substring rejected explicitly. Validation-only\n * counterpart of `../server/paths#safeRecordId`. */\nexport function isSafeRecordId(value: string): boolean {\n if (typeof value !== \"string\" || !SAFE_RECORD_ID_PATTERN.test(value)) return false;\n return !value.includes(\"..\");\n}\n"],"mappings":";;;;;AAoEA,IAAa,eAAe;CAAC;CAAO;CAAQ;AAAW;;;;;;;;AAUvD,IAAa,oBAAoB;;AAIjC,IAAa,iBAAiB;CAAC;CAAU;CAAS;CAAU;AAAW;;;;;;;AA4BvE,IAAa,iCAAmD,IAAI,IAAyB;CAAC;CAAW;CAAS;CAAa;CAAU;CAAU;AAAM,CAAC;;AAsE1J,SAAgB,mBAAmB,OAAkE;CACnG,OAAO,eAAe;AACxB;;;;;;AAuCA,SAAgB,iBAAiB,QAAuD;CACtF,OAAO,OAAO,eAAe,KAAA;AAC/B;;;;;;AA6BA,SAAgB,cAAc,OAA4B,QAAuC;CAC/F,IAAI,MAAM,SAAS,SAAS,OAAO;CACnC,IAAI,MAAM,IAAI,OAAO,MAAM;CAC3B,IAAI,MAAM,WAAW,QAAQ,OAAO,OAAO,OAAO,MAAM,YAAY,EAAE;CACtE,OAAO;AACT;;;ACtPA,IAAa,oBAAoB;AAYjC,IAAa,yBAAyB;;;;;;AAOtC,SAAgB,WAAW,OAAwB;CACjD,OAAO,OAAO,UAAU,YAAY,kBAAkB,KAAK,KAAK;AAClE;;;;AAKA,SAAgB,eAAe,OAAwB;CACrD,IAAI,OAAO,UAAU,YAAY,CAAC,uBAAuB,KAAK,KAAK,GAAG,OAAO;CAC7E,OAAO,CAAC,MAAM,SAAS,IAAI;AAC7B"}
@@ -0,0 +1,85 @@
1
+ //#region src/collection/core/schema.ts
2
+ /** Declarative retriever kinds a Feed's `ingest.kind` may declare. The host's
3
+ * feeds engine dispatches on these; they live here (with the schema contract)
4
+ * so the schema validator can enforce them. The host re-exports these from
5
+ * `server/workspace/feeds/ingestTypes.ts`. */
6
+ var INGEST_KINDS = [
7
+ "rss",
8
+ "atom",
9
+ "http-json"
10
+ ];
11
+ /** The agent-performed ingest kind. Instead of a declarative fetch, the host
12
+ * dispatches a hidden background chat (origin `system`) in `ingest.role`,
13
+ * seeded with `ingest.template` + a summary of every record, on the
14
+ * `ingest.schedule` cadence; the worker edits records via the collections io
15
+ * layer. Kept separate from {@link INGEST_KINDS} (which the declarative
16
+ * retriever registry keys on) so the schema validator can model `ingest` as a
17
+ * discriminated union without the feeds engine gaining an "agent" retriever. */
18
+ var AGENT_INGEST_KIND = "agent";
19
+ /** Refresh cadences a Feed's `ingest.schedule` may declare. */
20
+ var FEED_SCHEDULES = [
21
+ "hourly",
22
+ "daily",
23
+ "weekly",
24
+ "on-demand"
25
+ ];
26
+ /** derived/embed/backlinks/rollup/toggle/flag are host-computed or
27
+ * projected — never written to the record JSON, so required / value
28
+ * checks and edit-draft slots must not apply to them. THE single source
29
+ * for "computed" — lives here (zod-free at runtime) so browser code
30
+ * (`./draft`) and the zod record compiler (`./recordZ`, which re-exports
31
+ * it) share one set instead of drifting copies. */
32
+ var COMPUTED_TYPES = /* @__PURE__ */ new Set([
33
+ "derived",
34
+ "embed",
35
+ "backlinks",
36
+ "rollup",
37
+ "toggle",
38
+ "flag"
39
+ ]);
40
+ /** Narrowing guard: true when `every` is the field-driven arm. */
41
+ function isFieldDrivenEvery(every) {
42
+ return "fromField" in every;
43
+ }
44
+ /** True when `schema` declares an external `dataSource` — i.e. the
45
+ * collection is READ-ONLY through every UI/tool write path (updates
46
+ * happen by editing/replacing the data file itself). Isomorphic: both
47
+ * the server write guards and the client's control hiding key off this
48
+ * one predicate. */
49
+ function isReadOnlySchema(schema) {
50
+ return schema.dataSource !== void 0;
51
+ }
52
+ /** Resolve an `embed` field's target record id: the fixed `id`, or the value
53
+ * of the sibling `idField` on this record (empty string when neither applies
54
+ * — the caller renders that as "no record"). Pure + isomorphic so the server
55
+ * projection (`derive.ts`) and the client preview (`useCollectionRendering`)
56
+ * resolve embeds identically. Non-`embed` fields resolve to "no record". */
57
+ function embedTargetId(field, record) {
58
+ if (field.type !== "embed") return "";
59
+ if (field.id) return field.id;
60
+ if (field.idField && record) return String(record[field.idField] ?? "");
61
+ return "";
62
+ }
63
+ //#endregion
64
+ //#region src/collection/core/ids.ts
65
+ var SAFE_SLUG_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9_-]*[a-zA-Z0-9])?$/;
66
+ var SAFE_RECORD_ID_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9_.-]*[a-zA-Z0-9])?$/;
67
+ /** True when `value` is a well-formed collection slug (alphanumeric /
68
+ * hyphen / underscore, no path separators). The pattern admits no `/`,
69
+ * `\`, or `.`, so a passing value is trivially also a safe basename —
70
+ * validation callers need no `path.basename` round-trip (path-building
71
+ * callers use `../server/paths#safeSlugName`, which adds it). */
72
+ function isSafeSlug(value) {
73
+ return typeof value === "string" && SAFE_SLUG_PATTERN.test(value);
74
+ }
75
+ /** True when `value` is a well-formed record id (slug charset plus interior
76
+ * dots), with any `..` substring rejected explicitly. Validation-only
77
+ * counterpart of `../server/paths#safeRecordId`. */
78
+ function isSafeRecordId(value) {
79
+ if (typeof value !== "string" || !SAFE_RECORD_ID_PATTERN.test(value)) return false;
80
+ return !value.includes("..");
81
+ }
82
+ //#endregion
83
+ export { AGENT_INGEST_KIND as a, INGEST_KINDS as c, isReadOnlySchema as d, isSafeSlug as i, embedTargetId as l, SAFE_SLUG_PATTERN as n, COMPUTED_TYPES as o, isSafeRecordId as r, FEED_SCHEDULES as s, SAFE_RECORD_ID_PATTERN as t, isFieldDrivenEvery as u };
84
+
85
+ //# sourceMappingURL=ids-D4e10971.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ids-D4e10971.js","names":[],"sources":["../src/collection/core/schema.ts","../src/collection/core/ids.ts"],"sourcesContent":["// Schema-driven collection types. A \"collection\" is a skill (under\n// .claude/skills/<slug>/) that also ships a sibling `schema.json`.\n// The host's <CollectionView> reads the schema + records and renders\n// a table/form; Claude reads SKILL.md and CRUDs the records as JSON\n// files.\n//\n// SINGLE SOURCE OF TRUTH: every type describing the schema.json contract is\n// derived (`z.infer`) from the zod definitions in `./schemaZ` — the shapes,\n// their doc comments, and the validation rules live THERE; this module only\n// re-derives the TypeScript names consumers import. The imports from\n// `./schemaZ` are type-only, so zod never reaches the browser bundle through\n// the isomorphic barrel; at runtime the dependency points the other way\n// (schemaZ imports this module's consts).\n//\n// Field specs are a DISCRIMINATED UNION on `type`: narrow with\n// `field.type === \"enum\"` (etc.) before reading a variant key like `values`,\n// `to`, `formula`, or `of`.\n\nimport type { z } from \"zod\";\nimport type {\n ActionSpecZ,\n CollectionSchemaZ,\n CustomViewZ,\n DataSourceZ,\n DynamicIconRuleZ,\n DynamicIconSourceZ,\n DynamicIconSpecZ,\n EveryFieldDrivenZ,\n EveryLiteralZ,\n EveryZ,\n FieldSpecZ,\n IngestZ,\n SpawnZ,\n SubFieldSpecZ,\n WhenZ,\n} from \"./schemaZ\";\n\n/** Minimal \"this collection is a feed\" descriptor carried on the schema.\n * Deliberately narrow — the canonical collection contract stays\n * independent of the host's feeds subsystem. The host's richer retrieval\n * spec (`IngestSpec` in `feeds/ingestTypes.ts`) is a subtype, so feed code\n * reads the extra fields by typing feed schemas with that subtype;\n * collection rendering only needs these three + the presence check. */\nexport interface CollectionIngest {\n kind: string;\n schedule: string;\n /** Optional time-of-day anchor for `schedule: \"daily\"` — the hour (0–23) to\n * refresh around (the host ticks hourly, so the run lands within that hour).\n * Ignored for non-daily schedules. Absent ⇒ elapsed-based daily (\"≥24 h since\n * the last run\"). NOTE: **UTC**, not local — compared via `getUTCHours()` for\n * an unambiguous, DST-free check (matching the rest of the scheduler), so\n * convert local times before writing (e.g. 07:00 JST → `atHour: 22`). */\n atHour?: number;\n /** Declarative retrievers (`rss`/`atom`/`http-json`) only — the host fetches\n * this URL on the schedule. Absent for `kind: \"agent\"`, where the agent owns\n * retrieval. */\n url?: string;\n /** `kind: \"agent\"` only: role id the scheduled hidden worker runs in. */\n role?: string;\n /** `kind: \"agent\"` only: skill-relative template path (under `templates/`)\n * whose prose tells the worker how to refresh the records. */\n template?: string;\n}\n\n/** Declarative retriever kinds a Feed's `ingest.kind` may declare. The host's\n * feeds engine dispatches on these; they live here (with the schema contract)\n * so the schema validator can enforce them. The host re-exports these from\n * `server/workspace/feeds/ingestTypes.ts`. */\nexport const INGEST_KINDS = [\"rss\", \"atom\", \"http-json\"] as const;\nexport type IngestKind = (typeof INGEST_KINDS)[number];\n\n/** The agent-performed ingest kind. Instead of a declarative fetch, the host\n * dispatches a hidden background chat (origin `system`) in `ingest.role`,\n * seeded with `ingest.template` + a summary of every record, on the\n * `ingest.schedule` cadence; the worker edits records via the collections io\n * layer. Kept separate from {@link INGEST_KINDS} (which the declarative\n * retriever registry keys on) so the schema validator can model `ingest` as a\n * discriminated union without the feeds engine gaining an \"agent\" retriever. */\nexport const AGENT_INGEST_KIND = \"agent\" as const;\nexport type AgentIngestKind = typeof AGENT_INGEST_KIND;\n\n/** Refresh cadences a Feed's `ingest.schedule` may declare. */\nexport const FEED_SCHEDULES = [\"hourly\", \"daily\", \"weekly\", \"on-demand\"] as const;\nexport type FeedSchedule = (typeof FEED_SCHEDULES)[number];\n\n// \"feed\" collections live in the non-skill `<workspace>/feeds/` registry\n// and carry an `ingest` block; they reuse the same storage + rendering\n// as skill-backed collections but are never loaded into the agent prompt.\nexport type CollectionSource = \"user\" | \"project\" | \"feed\";\n\n/** One field of a record — a discriminated union on `type`; see the variant\n * docs in `./schemaZ` (`FieldSpecZ`). */\nexport type CollectionFieldSpec = z.infer<typeof FieldSpecZ>;\n\n/** A `table` field's row sub-schema entry — the field union minus `table` /\n * `derived` / display-only types (see `SubFieldSpecZ`). */\nexport type CollectionSubFieldSpec = z.infer<typeof SubFieldSpecZ>;\n\nexport type CollectionFieldType = CollectionFieldSpec[\"type\"];\n\n/** The computed-boolean variant — a `where` predicate bound to a field\n * name; see `FlagFieldZ`. */\nexport type CollectionFlagField = Extract<CollectionFieldSpec, { type: \"flag\" }>;\n\n/** derived/embed/backlinks/rollup/toggle/flag are host-computed or\n * projected — never written to the record JSON, so required / value\n * checks and edit-draft slots must not apply to them. THE single source\n * for \"computed\" — lives here (zod-free at runtime) so browser code\n * (`./draft`) and the zod record compiler (`./recordZ`, which re-exports\n * it) share one set instead of drifting copies. */\nexport const COMPUTED_TYPES: ReadonlySet<CollectionFieldType> = new Set<CollectionFieldType>([\"derived\", \"embed\", \"backlinks\", \"rollup\", \"toggle\", \"flag\"]);\n\n/** Optional visibility predicate: the target (an action button or a\n * field) renders only when the open record's `field` (stringified) is\n * one of `in`. Generic and domain-free — the host evaluates it against\n * the record with no knowledge of what the field means. Absent ⇒\n * always shown. */\nexport type CollectionWhen = z.infer<typeof WhenZ>;\n\n/** @deprecated Name retained for back-compat; use {@link CollectionWhen}.\n * Both actions and fields share the same predicate shape. No in-repo\n * consumers, but the package is public API (MulmoTerminal). */\n// eslint-disable-next-line sonarjs/redundant-type-aliases -- deliberate deprecated back-compat export\nexport type CollectionActionWhen = CollectionWhen;\n\n/** A schema-declared, per-record action rendered as a button in the\n * read-only detail view. Pure UI/behaviour directive — never stored,\n * never validated against record data. All domain specifics (label,\n * role, template — or the declarative `set`) live in the schema / skill\n * folder, so the host stays generic. A discriminated union on `kind`;\n * see `ActionSpecZ`. */\nexport type CollectionAction = z.infer<typeof ActionSpecZ>;\n\n/** The kind of work an action kicks off: `\"chat\"` (visible LLM chat),\n * `\"agent\"` (hidden LLM worker), or `\"mutate\"` (declarative host write,\n * no LLM). */\nexport type CollectionActionKind = CollectionAction[\"kind\"];\n\n/** The LLM-seeded action variants (`role` + `template`). */\nexport type CollectionSeededAction = Extract<CollectionAction, { kind: \"chat\" | \"agent\" }>;\n\n/** The declarative host-write variant (`set` + optional `require`/`params`). */\nexport type CollectionMutateAction = Extract<CollectionAction, { kind: \"mutate\" }>;\n\n/** A custom (LLM-authored) HTML view for a collection. The host renders\n * `file` in a sandboxed iframe over the collection's records; the view\n * reaches its data only through a slug- and capability-scoped token (see\n * `server/api/auth/viewToken.ts`). Pure data — the host holds no\n * view-specific code; meaning lives in the HTML file + this registration.\n * See `CustomViewZ` for the per-key contracts. */\nexport type CollectionCustomView = z.infer<typeof CustomViewZ>;\n\n/** What a custom view's capability token is allowed to do against the\n * collection's data endpoint. `read` returns enriched records (getItems\n * semantics); `write` validates-and-stores rows (putItems semantics).\n * There is deliberately no `delete` — a view can never do more than the\n * agent's own `manageCollection` tool. */\nexport type CollectionViewCapability = NonNullable<CollectionCustomView[\"capabilities\"]>[number];\n\n/** How a `spawn` advances the source item's `triggerField` date to\n * produce the successor's. All arithmetic is done on the civil\n * (year, month, day) triple — never by adding milliseconds — so month\n * lengths and leap years are handled correctly. */\nexport type CollectionEvery = z.infer<typeof EveryLiteralZ>;\n\n/** Recurrence unit for a `spawn.every` advance. */\nexport type CollectionRecurUnit = CollectionEvery[\"unit\"];\n\n/** Field-driven recurrence: the advance interval is selected PER RECORD by\n * the value of an `enum` field (`fromField`), looked up in `map`. See\n * `EveryFieldDrivenZ`. */\nexport type CollectionEveryFieldDriven = z.infer<typeof EveryFieldDrivenZ>;\n\n/** The `every` of a `spawn`: either a single literal interval applied to\n * every record, or a per-record interval selected by an `enum` field. The\n * literal arm is what `advanceTriggerDate` consumes — the field-driven arm\n * is resolved down to one of its `map` values before the date math runs. */\nexport type CollectionSpawnEvery = z.infer<typeof EveryZ>;\n\n/** Narrowing guard: true when `every` is the field-driven arm. */\nexport function isFieldDrivenEvery(every: CollectionSpawnEvery): every is CollectionEveryFieldDriven {\n return \"fromField\" in every;\n}\n\n/** Host-driven recurrence. See `SpawnZ`. */\nexport type CollectionSpawn = z.infer<typeof SpawnZ>;\n\n/** One rule in a `dynamicIcon.rules` list: when the resolved source\n * record matches `where` (an AND of typed conditions, see `./where`),\n * the collection's effective launcher icon becomes `icon`. Evaluated top\n * to bottom — the first match wins. */\nexport type DynamicIconRule = z.infer<typeof DynamicIconRuleZ>;\n\n/** Where a {@link DynamicIconSpec}'s source record comes from: a (possibly\n * cross-collection) pool of records, optionally narrowed by `where` and\n * reduced to a single record by `from`. */\nexport type DynamicIconSource = z.infer<typeof DynamicIconSourceZ>;\n\n/** Declarative \"data state → icon\" mapping for a collection's launcher\n * shortcut icon (see `CollectionSchema.dynamicIcon`). When absent, the\n * launcher icon is the static `schema.icon`. */\nexport type DynamicIconSpec = z.infer<typeof DynamicIconSpecZ>;\n\n/** The `ingest` block as the schema validator accepts it — a discriminated\n * union on `kind` (declarative retrievers | agent worker). The feeds\n * subsystem's `IngestSpec` is the same union under its historical name. */\nexport type CollectionIngestSpec = z.infer<typeof IngestZ>;\n\n/** The `dataSource` block: this collection's records are the rows of an\n * external read-only data file (v1: CSV). See `DataSourceZ`. */\nexport type CollectionDataSource = z.infer<typeof DataSourceZ>;\n\n/** The whole `schema.json` contract. Key-level docs live on\n * `CollectionSchemaZ` in `./schemaZ`. */\nexport type CollectionSchema = z.infer<typeof CollectionSchemaZ>;\n\n/** True when `schema` declares an external `dataSource` — i.e. the\n * collection is READ-ONLY through every UI/tool write path (updates\n * happen by editing/replacing the data file itself). Isomorphic: both\n * the server write guards and the client's control hiding key off this\n * one predicate. */\nexport function isReadOnlySchema(schema: Pick<CollectionSchema, \"dataSource\">): boolean {\n return schema.dataSource !== undefined;\n}\n\nexport interface CollectionSummary {\n slug: string;\n title: string;\n icon: string;\n source: CollectionSource;\n /** Present (true) when the collection is backed by an external\n * `dataSource` and therefore read-only in every UI/tool write path.\n * Absent-when-writable, matching the other optional summary flags. */\n readonly?: true;\n /** Slugs of the source collection(s) a `dynamicIcon` icon was computed\n * from — present only when `schema.dynamicIcon` is set. Lets a client\n * know which collection change-channel(s) to watch for a live icon\n * update (see `useDynamicShortcutIcons`). */\n iconSources?: string[];\n}\n\nexport interface CollectionDetail extends CollectionSummary {\n schema: CollectionSchema;\n}\n\nexport type CollectionItem = Record<string, unknown>;\n\n/** Resolve an `embed` field's target record id: the fixed `id`, or the value\n * of the sibling `idField` on this record (empty string when neither applies\n * — the caller renders that as \"no record\"). Pure + isomorphic so the server\n * projection (`derive.ts`) and the client preview (`useCollectionRendering`)\n * resolve embeds identically. Non-`embed` fields resolve to \"no record\". */\nexport function embedTargetId(field: CollectionFieldSpec, record: CollectionItem | null): string {\n if (field.type !== \"embed\") return \"\";\n if (field.id) return field.id;\n if (field.idField && record) return String(record[field.idField] ?? \"\");\n return \"\";\n}\n","// Pure slug / record-id character rules. Shared by the isomorphic schema\n// validator (`./schemaZ`) — which must stay node-free — and the server-side\n// path sanitisers (`../server/paths`), which wrap these patterns with the\n// `path.basename` round-trip CodeQL recognises as a `js/path-injection`\n// sanitiser. Both layers MUST gate on the same patterns; importing them from\n// here is what keeps them in sync.\n\n// The ONE slug pattern — `server/workspace/skills/catalog.ts` imports it\n// for its own sanitiser, so there is no second copy to keep in sync.\n// Bounded character classes, no nested quantifiers; ReDoS-safe.\n// eslint-disable-next-line security/detect-unsafe-regex -- non-overlapping character classes, no catastrophic backtracking\nexport const SAFE_SLUG_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9_-]*[a-zA-Z0-9])?$/;\n\n// Record ids are a superset of slugs: they're only ever filename stems\n// (`<id>.json`), never directory names or URL segments, so they may carry\n// dots — natural keys like a Slack ts (`1718900000.123456`), a SemVer\n// (`1.2.3`), or a decimal timestamp. The interior class adds `.` to the slug\n// set; the explicit `..` reject in `isSafeRecordId` keeps a\n// parent-dir-looking segment out while still allowing repeated `-`/`_`\n// (`a--b`, `a__b`). Start/end stay alphanumeric so leading/trailing dots\n// (hidden files, the special `.`/`..` names) and `..`-only ids are all\n// excluded.\n// eslint-disable-next-line security/detect-unsafe-regex -- non-overlapping character classes, no catastrophic backtracking\nexport const SAFE_RECORD_ID_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9_.-]*[a-zA-Z0-9])?$/;\n\n/** True when `value` is a well-formed collection slug (alphanumeric /\n * hyphen / underscore, no path separators). The pattern admits no `/`,\n * `\\`, or `.`, so a passing value is trivially also a safe basename —\n * validation callers need no `path.basename` round-trip (path-building\n * callers use `../server/paths#safeSlugName`, which adds it). */\nexport function isSafeSlug(value: string): boolean {\n return typeof value === \"string\" && SAFE_SLUG_PATTERN.test(value);\n}\n\n/** True when `value` is a well-formed record id (slug charset plus interior\n * dots), with any `..` substring rejected explicitly. Validation-only\n * counterpart of `../server/paths#safeRecordId`. */\nexport function isSafeRecordId(value: string): boolean {\n if (typeof value !== \"string\" || !SAFE_RECORD_ID_PATTERN.test(value)) return false;\n return !value.includes(\"..\");\n}\n"],"mappings":";;;;;AAoEA,IAAa,eAAe;CAAC;CAAO;CAAQ;AAAW;;;;;;;;AAUvD,IAAa,oBAAoB;;AAIjC,IAAa,iBAAiB;CAAC;CAAU;CAAS;CAAU;AAAW;;;;;;;AA4BvE,IAAa,iCAAmD,IAAI,IAAyB;CAAC;CAAW;CAAS;CAAa;CAAU;CAAU;AAAM,CAAC;;AAsE1J,SAAgB,mBAAmB,OAAkE;CACnG,OAAO,eAAe;AACxB;;;;;;AAuCA,SAAgB,iBAAiB,QAAuD;CACtF,OAAO,OAAO,eAAe,KAAA;AAC/B;;;;;;AA6BA,SAAgB,cAAc,OAA4B,QAAuC;CAC/F,IAAI,MAAM,SAAS,SAAS,OAAO;CACnC,IAAI,MAAM,IAAI,OAAO,MAAM;CAC3B,IAAI,MAAM,WAAW,QAAQ,OAAO,OAAO,OAAO,MAAM,YAAY,EAAE;CACtE,OAAO;AACT;;;ACtPA,IAAa,oBAAoB;AAYjC,IAAa,yBAAyB;;;;;;AAOtC,SAAgB,WAAW,OAAwB;CACjD,OAAO,OAAO,UAAU,YAAY,kBAAkB,KAAK,KAAK;AAClE;;;;AAKA,SAAgB,eAAe,OAAwB;CACrD,IAAI,OAAO,UAAU,YAAY,CAAC,uBAAuB,KAAK,KAAK,GAAG,OAAO;CAC7E,OAAO,CAAC,MAAM,SAAS,IAAI;AAC7B"}
@@ -1,4 +1,4 @@
1
- import { R as FEED_SCHEDULES } from "./promptSafety-BLUcImzd.js";
1
+ import { s as FEED_SCHEDULES } from "./ids-D4e10971.js";
2
2
  import "./collection/index.js";
3
3
  //#region src/feeds/ingestTypes.ts
4
4
  var AGENT_INGEST_KIND = "agent";
@@ -12,4 +12,4 @@ var DEFAULT_FEED_MAX_ITEMS = 100;
12
12
  //#endregion
13
13
  export { DEFAULT_FEED_MAX_ITEMS as n, isFeedSchedule as r, AGENT_INGEST_KIND as t };
14
14
 
15
- //# sourceMappingURL=ingestTypes-CFEgTItj.js.map
15
+ //# sourceMappingURL=ingestTypes-BsGOjR2l.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ingestTypes-CFEgTItj.js","names":[],"sources":["../src/feeds/ingestTypes.ts"],"sourcesContent":["// Declarative retrieval config for the \"Feeds\" mechanism. A Feed is a\n// CollectionSchema plus this `ingest` block, registered as data (NOT as\n// a skill) under `<workspace>/feeds/<slug>/schema.json`. The host's\n// retrieval engine reads it to periodically refill the collection's\n// records via the shared collections io layer.\n//\n// The ingest vocab (INGEST_KINDS / FEED_SCHEDULES + their literal-union types)\n// lives in the sibling `../collection` subpath alongside the schema contract, so\n// the package's schema validator can enforce it. Re-exported here so the feeds\n// engine's existing importers resolve them unchanged.\nimport { INGEST_KINDS, FEED_SCHEDULES, type IngestKind, type FeedSchedule } from \"../collection/index.js\";\n// Type-only: keeps zod out of this module's runtime graph (the feeds engine\n// imports it browser-free through `../collection`'s type surface).\nimport type { z } from \"zod\";\nimport type { AgentIngestZ, DeclarativeIngestZ } from \"../collection/core/schemaZ\";\n//\n// Two flavours: the declarative kinds (`rss`/`atom`/`http-json`) fetch-and-map,\n// and `agent` dispatches a hidden worker. The `code` kind (LLM-generated\n// deterministic transform) is still reserved for a future retriever.\n\n// The agent ingest kind. Defined HERE (not imported from `../collection`) on\n// purpose: the engine only needs the literal to branch, and re-importing it as a\n// VALUE keeps this module free of a value dependency on the collection vocab.\n// Core owns its own copy for the schema validator; this matches the same literal.\nexport const AGENT_INGEST_KIND = \"agent\" as const;\nexport type AgentIngestKind = typeof AGENT_INGEST_KIND;\n\nexport { INGEST_KINDS, FEED_SCHEDULES, type IngestKind, type FeedSchedule };\n\nconst FEED_SCHEDULE_SET: ReadonlySet<string> = new Set(FEED_SCHEDULES);\n\nexport function isFeedSchedule(value: unknown): value is FeedSchedule {\n return typeof value === \"string\" && FEED_SCHEDULE_SET.has(value);\n}\n\n/** Default cap on stored records per feed when `ingest.maxItems` is\n * omitted. Keeps high-volume feeds (news / podcasts) bounded. */\nexport const DEFAULT_FEED_MAX_ITEMS = 100;\n\n/** Declarative field map: target collection field name → source path\n * into the raw item (dot/bracket path, e.g. `\"title\"` or\n * `\"data.name\"`). */\nexport type IngestFieldMap = Record<string, string>;\n\n/** Declarative retrieval (`rss`/`atom`/`http-json`): the host fetches `url`\n * and projects each item through `map` (target field → source path; a\n * `http-json` feed may add `itemsAt`, a dot/bracket path to the items\n * array; `idFrom` derives the primaryKey; `maxItems` caps stored records,\n * default {@link DEFAULT_FEED_MAX_ITEMS}, `0` = keep everything). The\n * canonical loose contract stays the minimal `CollectionIngest`; this\n * feeds subtype is derived from the zod source of truth\n * (`collection/core/schemaZ` `DeclarativeIngestZ`), so the engine and the\n * schema validator can never drift. */\nexport type DeclarativeIngestSpec = z.infer<typeof DeclarativeIngestZ>;\n\n/** Agent-performed retrieval (`kind: \"agent\"`). No `url`/`map`: the host seeds\n * a hidden background worker (origin `system`) in `role` with `template` + a\n * summary of every record, and the worker edits the records itself via the\n * collections io layer. Valid on any collection (primarily skill-backed).\n * Derived from `AgentIngestZ` in `collection/core/schemaZ`. */\nexport type AgentIngestSpec = z.infer<typeof AgentIngestZ>;\n\n/** The `ingest` block carried on a `CollectionSchema`, discriminated on `kind`. */\nexport type IngestSpec = DeclarativeIngestSpec | AgentIngestSpec;\n"],"mappings":";;;AAwBA,IAAa,oBAAoB;AAKjC,IAAM,oBAAyC,IAAI,IAAI,cAAc;AAErE,SAAgB,eAAe,OAAuC;CACpE,OAAO,OAAO,UAAU,YAAY,kBAAkB,IAAI,KAAK;AACjE;;;AAIA,IAAa,yBAAyB"}
1
+ {"version":3,"file":"ingestTypes-BsGOjR2l.js","names":[],"sources":["../src/feeds/ingestTypes.ts"],"sourcesContent":["// Declarative retrieval config for the \"Feeds\" mechanism. A Feed is a\n// CollectionSchema plus this `ingest` block, registered as data (NOT as\n// a skill) under `<workspace>/feeds/<slug>/schema.json`. The host's\n// retrieval engine reads it to periodically refill the collection's\n// records via the shared collections io layer.\n//\n// The ingest vocab (INGEST_KINDS / FEED_SCHEDULES + their literal-union types)\n// lives in the sibling `../collection` subpath alongside the schema contract, so\n// the package's schema validator can enforce it. Re-exported here so the feeds\n// engine's existing importers resolve them unchanged.\nimport { INGEST_KINDS, FEED_SCHEDULES, type IngestKind, type FeedSchedule } from \"../collection/index.js\";\n// Type-only: keeps zod out of this module's runtime graph (the feeds engine\n// imports it browser-free through `../collection`'s type surface).\nimport type { z } from \"zod\";\nimport type { AgentIngestZ, DeclarativeIngestZ } from \"../collection/core/schemaZ\";\n//\n// Two flavours: the declarative kinds (`rss`/`atom`/`http-json`) fetch-and-map,\n// and `agent` dispatches a hidden worker. The `code` kind (LLM-generated\n// deterministic transform) is still reserved for a future retriever.\n\n// The agent ingest kind. Defined HERE (not imported from `../collection`) on\n// purpose: the engine only needs the literal to branch, and re-importing it as a\n// VALUE keeps this module free of a value dependency on the collection vocab.\n// Core owns its own copy for the schema validator; this matches the same literal.\nexport const AGENT_INGEST_KIND = \"agent\" as const;\nexport type AgentIngestKind = typeof AGENT_INGEST_KIND;\n\nexport { INGEST_KINDS, FEED_SCHEDULES, type IngestKind, type FeedSchedule };\n\nconst FEED_SCHEDULE_SET: ReadonlySet<string> = new Set(FEED_SCHEDULES);\n\nexport function isFeedSchedule(value: unknown): value is FeedSchedule {\n return typeof value === \"string\" && FEED_SCHEDULE_SET.has(value);\n}\n\n/** Default cap on stored records per feed when `ingest.maxItems` is\n * omitted. Keeps high-volume feeds (news / podcasts) bounded. */\nexport const DEFAULT_FEED_MAX_ITEMS = 100;\n\n/** Declarative field map: target collection field name → source path\n * into the raw item (dot/bracket path, e.g. `\"title\"` or\n * `\"data.name\"`). */\nexport type IngestFieldMap = Record<string, string>;\n\n/** Declarative retrieval (`rss`/`atom`/`http-json`): the host fetches `url`\n * and projects each item through `map` (target field → source path; a\n * `http-json` feed may add `itemsAt`, a dot/bracket path to the items\n * array; `idFrom` derives the primaryKey; `maxItems` caps stored records,\n * default {@link DEFAULT_FEED_MAX_ITEMS}, `0` = keep everything). The\n * canonical loose contract stays the minimal `CollectionIngest`; this\n * feeds subtype is derived from the zod source of truth\n * (`collection/core/schemaZ` `DeclarativeIngestZ`), so the engine and the\n * schema validator can never drift. */\nexport type DeclarativeIngestSpec = z.infer<typeof DeclarativeIngestZ>;\n\n/** Agent-performed retrieval (`kind: \"agent\"`). No `url`/`map`: the host seeds\n * a hidden background worker (origin `system`) in `role` with `template` + a\n * summary of every record, and the worker edits the records itself via the\n * collections io layer. Valid on any collection (primarily skill-backed).\n * Derived from `AgentIngestZ` in `collection/core/schemaZ`. */\nexport type AgentIngestSpec = z.infer<typeof AgentIngestZ>;\n\n/** The `ingest` block carried on a `CollectionSchema`, discriminated on `kind`. */\nexport type IngestSpec = DeclarativeIngestSpec | AgentIngestSpec;\n"],"mappings":";;;AAwBA,IAAa,oBAAoB;AAKjC,IAAM,oBAAyC,IAAI,IAAI,cAAc;AAErE,SAAgB,eAAe,OAAuC;CACpE,OAAO,OAAO,UAAU,YAAY,kBAAkB,IAAI,KAAK;AACjE;;;AAIA,IAAa,yBAAyB"}
@@ -1,8 +1,8 @@
1
- const require_promptSafety = require("./promptSafety-DI_1Rvhk.cjs");
1
+ const require_ids = require("./ids-BR4U1dJD.cjs");
2
2
  require("./collection/index.cjs");
3
3
  //#region src/feeds/ingestTypes.ts
4
4
  var AGENT_INGEST_KIND = "agent";
5
- var FEED_SCHEDULE_SET = new Set(require_promptSafety.FEED_SCHEDULES);
5
+ var FEED_SCHEDULE_SET = new Set(require_ids.FEED_SCHEDULES);
6
6
  function isFeedSchedule(value) {
7
7
  return typeof value === "string" && FEED_SCHEDULE_SET.has(value);
8
8
  }
@@ -29,4 +29,4 @@ Object.defineProperty(exports, "isFeedSchedule", {
29
29
  }
30
30
  });
31
31
 
32
- //# sourceMappingURL=ingestTypes-Stc7iQyA.cjs.map
32
+ //# sourceMappingURL=ingestTypes-CygUXsjf.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"ingestTypes-Stc7iQyA.cjs","names":[],"sources":["../src/feeds/ingestTypes.ts"],"sourcesContent":["// Declarative retrieval config for the \"Feeds\" mechanism. A Feed is a\n// CollectionSchema plus this `ingest` block, registered as data (NOT as\n// a skill) under `<workspace>/feeds/<slug>/schema.json`. The host's\n// retrieval engine reads it to periodically refill the collection's\n// records via the shared collections io layer.\n//\n// The ingest vocab (INGEST_KINDS / FEED_SCHEDULES + their literal-union types)\n// lives in the sibling `../collection` subpath alongside the schema contract, so\n// the package's schema validator can enforce it. Re-exported here so the feeds\n// engine's existing importers resolve them unchanged.\nimport { INGEST_KINDS, FEED_SCHEDULES, type IngestKind, type FeedSchedule } from \"../collection/index.js\";\n// Type-only: keeps zod out of this module's runtime graph (the feeds engine\n// imports it browser-free through `../collection`'s type surface).\nimport type { z } from \"zod\";\nimport type { AgentIngestZ, DeclarativeIngestZ } from \"../collection/core/schemaZ\";\n//\n// Two flavours: the declarative kinds (`rss`/`atom`/`http-json`) fetch-and-map,\n// and `agent` dispatches a hidden worker. The `code` kind (LLM-generated\n// deterministic transform) is still reserved for a future retriever.\n\n// The agent ingest kind. Defined HERE (not imported from `../collection`) on\n// purpose: the engine only needs the literal to branch, and re-importing it as a\n// VALUE keeps this module free of a value dependency on the collection vocab.\n// Core owns its own copy for the schema validator; this matches the same literal.\nexport const AGENT_INGEST_KIND = \"agent\" as const;\nexport type AgentIngestKind = typeof AGENT_INGEST_KIND;\n\nexport { INGEST_KINDS, FEED_SCHEDULES, type IngestKind, type FeedSchedule };\n\nconst FEED_SCHEDULE_SET: ReadonlySet<string> = new Set(FEED_SCHEDULES);\n\nexport function isFeedSchedule(value: unknown): value is FeedSchedule {\n return typeof value === \"string\" && FEED_SCHEDULE_SET.has(value);\n}\n\n/** Default cap on stored records per feed when `ingest.maxItems` is\n * omitted. Keeps high-volume feeds (news / podcasts) bounded. */\nexport const DEFAULT_FEED_MAX_ITEMS = 100;\n\n/** Declarative field map: target collection field name → source path\n * into the raw item (dot/bracket path, e.g. `\"title\"` or\n * `\"data.name\"`). */\nexport type IngestFieldMap = Record<string, string>;\n\n/** Declarative retrieval (`rss`/`atom`/`http-json`): the host fetches `url`\n * and projects each item through `map` (target field → source path; a\n * `http-json` feed may add `itemsAt`, a dot/bracket path to the items\n * array; `idFrom` derives the primaryKey; `maxItems` caps stored records,\n * default {@link DEFAULT_FEED_MAX_ITEMS}, `0` = keep everything). The\n * canonical loose contract stays the minimal `CollectionIngest`; this\n * feeds subtype is derived from the zod source of truth\n * (`collection/core/schemaZ` `DeclarativeIngestZ`), so the engine and the\n * schema validator can never drift. */\nexport type DeclarativeIngestSpec = z.infer<typeof DeclarativeIngestZ>;\n\n/** Agent-performed retrieval (`kind: \"agent\"`). No `url`/`map`: the host seeds\n * a hidden background worker (origin `system`) in `role` with `template` + a\n * summary of every record, and the worker edits the records itself via the\n * collections io layer. Valid on any collection (primarily skill-backed).\n * Derived from `AgentIngestZ` in `collection/core/schemaZ`. */\nexport type AgentIngestSpec = z.infer<typeof AgentIngestZ>;\n\n/** The `ingest` block carried on a `CollectionSchema`, discriminated on `kind`. */\nexport type IngestSpec = DeclarativeIngestSpec | AgentIngestSpec;\n"],"mappings":";;;AAwBA,IAAa,oBAAoB;AAKjC,IAAM,oBAAyC,IAAI,IAAI,qBAAA,cAAc;AAErE,SAAgB,eAAe,OAAuC;CACpE,OAAO,OAAO,UAAU,YAAY,kBAAkB,IAAI,KAAK;AACjE;;;AAIA,IAAa,yBAAyB"}
1
+ {"version":3,"file":"ingestTypes-CygUXsjf.cjs","names":[],"sources":["../src/feeds/ingestTypes.ts"],"sourcesContent":["// Declarative retrieval config for the \"Feeds\" mechanism. A Feed is a\n// CollectionSchema plus this `ingest` block, registered as data (NOT as\n// a skill) under `<workspace>/feeds/<slug>/schema.json`. The host's\n// retrieval engine reads it to periodically refill the collection's\n// records via the shared collections io layer.\n//\n// The ingest vocab (INGEST_KINDS / FEED_SCHEDULES + their literal-union types)\n// lives in the sibling `../collection` subpath alongside the schema contract, so\n// the package's schema validator can enforce it. Re-exported here so the feeds\n// engine's existing importers resolve them unchanged.\nimport { INGEST_KINDS, FEED_SCHEDULES, type IngestKind, type FeedSchedule } from \"../collection/index.js\";\n// Type-only: keeps zod out of this module's runtime graph (the feeds engine\n// imports it browser-free through `../collection`'s type surface).\nimport type { z } from \"zod\";\nimport type { AgentIngestZ, DeclarativeIngestZ } from \"../collection/core/schemaZ\";\n//\n// Two flavours: the declarative kinds (`rss`/`atom`/`http-json`) fetch-and-map,\n// and `agent` dispatches a hidden worker. The `code` kind (LLM-generated\n// deterministic transform) is still reserved for a future retriever.\n\n// The agent ingest kind. Defined HERE (not imported from `../collection`) on\n// purpose: the engine only needs the literal to branch, and re-importing it as a\n// VALUE keeps this module free of a value dependency on the collection vocab.\n// Core owns its own copy for the schema validator; this matches the same literal.\nexport const AGENT_INGEST_KIND = \"agent\" as const;\nexport type AgentIngestKind = typeof AGENT_INGEST_KIND;\n\nexport { INGEST_KINDS, FEED_SCHEDULES, type IngestKind, type FeedSchedule };\n\nconst FEED_SCHEDULE_SET: ReadonlySet<string> = new Set(FEED_SCHEDULES);\n\nexport function isFeedSchedule(value: unknown): value is FeedSchedule {\n return typeof value === \"string\" && FEED_SCHEDULE_SET.has(value);\n}\n\n/** Default cap on stored records per feed when `ingest.maxItems` is\n * omitted. Keeps high-volume feeds (news / podcasts) bounded. */\nexport const DEFAULT_FEED_MAX_ITEMS = 100;\n\n/** Declarative field map: target collection field name → source path\n * into the raw item (dot/bracket path, e.g. `\"title\"` or\n * `\"data.name\"`). */\nexport type IngestFieldMap = Record<string, string>;\n\n/** Declarative retrieval (`rss`/`atom`/`http-json`): the host fetches `url`\n * and projects each item through `map` (target field → source path; a\n * `http-json` feed may add `itemsAt`, a dot/bracket path to the items\n * array; `idFrom` derives the primaryKey; `maxItems` caps stored records,\n * default {@link DEFAULT_FEED_MAX_ITEMS}, `0` = keep everything). The\n * canonical loose contract stays the minimal `CollectionIngest`; this\n * feeds subtype is derived from the zod source of truth\n * (`collection/core/schemaZ` `DeclarativeIngestZ`), so the engine and the\n * schema validator can never drift. */\nexport type DeclarativeIngestSpec = z.infer<typeof DeclarativeIngestZ>;\n\n/** Agent-performed retrieval (`kind: \"agent\"`). No `url`/`map`: the host seeds\n * a hidden background worker (origin `system`) in `role` with `template` + a\n * summary of every record, and the worker edits the records itself via the\n * collections io layer. Valid on any collection (primarily skill-backed).\n * Derived from `AgentIngestZ` in `collection/core/schemaZ`. */\nexport type AgentIngestSpec = z.infer<typeof AgentIngestZ>;\n\n/** The `ingest` block carried on a `CollectionSchema`, discriminated on `kind`. */\nexport type IngestSpec = DeclarativeIngestSpec | AgentIngestSpec;\n"],"mappings":";;;AAwBA,IAAa,oBAAoB;AAKjC,IAAM,oBAAyC,IAAI,IAAI,YAAA,cAAc;AAErE,SAAgB,eAAe,OAAuC;CACpE,OAAO,OAAO,UAAU,YAAY,kBAAkB,IAAI,KAAK;AACjE;;;AAIA,IAAa,yBAAyB"}
@@ -1,85 +1,3 @@
1
- //#region src/collection/core/schema.ts
2
- /** Declarative retriever kinds a Feed's `ingest.kind` may declare. The host's
3
- * feeds engine dispatches on these; they live here (with the schema contract)
4
- * so the schema validator can enforce them. The host re-exports these from
5
- * `server/workspace/feeds/ingestTypes.ts`. */
6
- var INGEST_KINDS = [
7
- "rss",
8
- "atom",
9
- "http-json"
10
- ];
11
- /** The agent-performed ingest kind. Instead of a declarative fetch, the host
12
- * dispatches a hidden background chat (origin `system`) in `ingest.role`,
13
- * seeded with `ingest.template` + a summary of every record, on the
14
- * `ingest.schedule` cadence; the worker edits records via the collections io
15
- * layer. Kept separate from {@link INGEST_KINDS} (which the declarative
16
- * retriever registry keys on) so the schema validator can model `ingest` as a
17
- * discriminated union without the feeds engine gaining an "agent" retriever. */
18
- var AGENT_INGEST_KIND = "agent";
19
- /** Refresh cadences a Feed's `ingest.schedule` may declare. */
20
- var FEED_SCHEDULES = [
21
- "hourly",
22
- "daily",
23
- "weekly",
24
- "on-demand"
25
- ];
26
- /** derived/embed/backlinks/rollup/toggle/flag are host-computed or
27
- * projected — never written to the record JSON, so required / value
28
- * checks and edit-draft slots must not apply to them. THE single source
29
- * for "computed" — lives here (zod-free at runtime) so browser code
30
- * (`./draft`) and the zod record compiler (`./recordZ`, which re-exports
31
- * it) share one set instead of drifting copies. */
32
- var COMPUTED_TYPES = /* @__PURE__ */ new Set([
33
- "derived",
34
- "embed",
35
- "backlinks",
36
- "rollup",
37
- "toggle",
38
- "flag"
39
- ]);
40
- /** Narrowing guard: true when `every` is the field-driven arm. */
41
- function isFieldDrivenEvery(every) {
42
- return "fromField" in every;
43
- }
44
- /** True when `schema` declares an external `dataSource` — i.e. the
45
- * collection is READ-ONLY through every UI/tool write path (updates
46
- * happen by editing/replacing the data file itself). Isomorphic: both
47
- * the server write guards and the client's control hiding key off this
48
- * one predicate. */
49
- function isReadOnlySchema(schema) {
50
- return schema.dataSource !== void 0;
51
- }
52
- /** Resolve an `embed` field's target record id: the fixed `id`, or the value
53
- * of the sibling `idField` on this record (empty string when neither applies
54
- * — the caller renders that as "no record"). Pure + isomorphic so the server
55
- * projection (`derive.ts`) and the client preview (`useCollectionRendering`)
56
- * resolve embeds identically. Non-`embed` fields resolve to "no record". */
57
- function embedTargetId(field, record) {
58
- if (field.type !== "embed") return "";
59
- if (field.id) return field.id;
60
- if (field.idField && record) return String(record[field.idField] ?? "");
61
- return "";
62
- }
63
- //#endregion
64
- //#region src/collection/core/ids.ts
65
- var SAFE_SLUG_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9_-]*[a-zA-Z0-9])?$/;
66
- var SAFE_RECORD_ID_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9_.-]*[a-zA-Z0-9])?$/;
67
- /** True when `value` is a well-formed collection slug (alphanumeric /
68
- * hyphen / underscore, no path separators). The pattern admits no `/`,
69
- * `\`, or `.`, so a passing value is trivially also a safe basename —
70
- * validation callers need no `path.basename` round-trip (path-building
71
- * callers use `../server/paths#safeSlugName`, which adds it). */
72
- function isSafeSlug(value) {
73
- return typeof value === "string" && SAFE_SLUG_PATTERN.test(value);
74
- }
75
- /** True when `value` is a well-formed record id (slug charset plus interior
76
- * dots), with any `..` substring rejected explicitly. Validation-only
77
- * counterpart of `../server/paths#safeRecordId`. */
78
- function isSafeRecordId(value) {
79
- if (typeof value !== "string" || !SAFE_RECORD_ID_PATTERN.test(value)) return false;
80
- return !value.includes("..");
81
- }
82
- //#endregion
83
1
  //#region src/collection/core/actionVisible.ts
84
2
  /** Core matcher:
85
3
  * - no `when` ⇒ always true (visible);
@@ -933,6 +851,6 @@ function defangForPrompt(value) {
933
851
  return value.replace(/[<>]/g, "").replace(/`/g, "'").replace(/\$\{/g, "$ {").replace(/\s+/g, " ").slice(0, DEFANG_MAX_LEN);
934
852
  }
935
853
  //#endregion
936
- export { fieldVisible as A, embedTargetId as B, matchesWhere as C, rollupValue as D, projectBacklinkRow as E, isSafeSlug as F, isReadOnlySchema as H, AGENT_INGEST_KIND as I, COMPUTED_TYPES as L, SAFE_RECORD_ID_PATTERN as M, SAFE_SLUG_PATTERN as N, actionVisible as O, isSafeRecordId as P, FEED_SCHEDULES as R, itemIsDone as S, coerceNumeric as T, isFieldDrivenEvery as V, resolveRowRefs as _, buildMonthGrid as a, resolveIcon as b, daySlice as c, parseIsoDateTime as d, parseTimeRange as f, deriveAll as g, ymdKey as h, bucketRecords as i, whenMatches as j, agentActionRunKey as k, monthAnchorDate as l, spanCoversDay as m, MINUTES_PER_DAY as n, compareYmd as o, recordSpan as p, assignLanes as r, dateOf as s, defangForPrompt as t, parseIsoDate as u, evaluateDerived as v, backlinkRows as w, selectDynamicRecord as x, firstDateField as y, INGEST_KINDS as z };
854
+ export { fieldVisible as A, matchesWhere as C, rollupValue as D, projectBacklinkRow as E, actionVisible as O, itemIsDone as S, coerceNumeric as T, resolveRowRefs as _, buildMonthGrid as a, resolveIcon as b, daySlice as c, parseIsoDateTime as d, parseTimeRange as f, deriveAll as g, ymdKey as h, bucketRecords as i, whenMatches as j, agentActionRunKey as k, monthAnchorDate as l, spanCoversDay as m, MINUTES_PER_DAY as n, compareYmd as o, recordSpan as p, assignLanes as r, dateOf as s, defangForPrompt as t, parseIsoDate as u, evaluateDerived as v, backlinkRows as w, selectDynamicRecord as x, firstDateField as y };
937
855
 
938
- //# sourceMappingURL=promptSafety-BLUcImzd.js.map
856
+ //# sourceMappingURL=promptSafety-Boz5qOs-.js.map