@mulmoclaude/core 0.26.0 → 0.27.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/error-recovery.md +55 -0
- package/dist/collection/core/errorMessage.d.ts +1 -4
- package/dist/collection/core/ontologyGraph.d.ts +58 -0
- package/dist/collection/core/uiTypes.d.ts +7 -0
- package/dist/collection/index.cjs +81 -10
- package/dist/collection/index.cjs.map +1 -1
- package/dist/collection/index.d.ts +1 -0
- package/dist/collection/index.js +74 -4
- package/dist/collection/index.js.map +1 -1
- package/dist/collection/registry/server/index.cjs +8 -7
- package/dist/collection/registry/server/index.cjs.map +1 -1
- package/dist/collection/registry/server/index.js +3 -2
- package/dist/collection/registry/server/index.js.map +1 -1
- package/dist/collection/server/index.cjs +3 -3
- package/dist/collection/server/index.js +3 -3
- package/dist/collection/server/ontology.d.ts +2 -24
- package/dist/collection/server/util.d.ts +1 -2
- package/dist/collection-watchers/index.cjs +5 -4
- package/dist/collection-watchers/index.cjs.map +1 -1
- package/dist/collection-watchers/index.js +4 -3
- package/dist/collection-watchers/index.js.map +1 -1
- package/dist/{discovery-QUi_7aNG.js → discovery-B9Vfojyy.js} +2 -2
- package/dist/{discovery-QUi_7aNG.js.map → discovery-B9Vfojyy.js.map} +1 -1
- package/dist/{discovery-cwtfyDxl.cjs → discovery-BxbJy2VN.cjs} +2 -2
- package/dist/{discovery-cwtfyDxl.cjs.map → discovery-BxbJy2VN.cjs.map} +1 -1
- package/dist/errors-7P5eMOSX.cjs +30 -0
- package/dist/errors-7P5eMOSX.cjs.map +1 -0
- package/dist/errors-eid6Mes3.js +19 -0
- package/dist/errors-eid6Mes3.js.map +1 -0
- package/dist/feeds/index.cjs +2 -2
- package/dist/feeds/index.js +2 -2
- package/dist/feeds/server/index.cjs +4 -4
- package/dist/feeds/server/index.js +4 -4
- package/dist/google/index.cjs +8 -18
- package/dist/google/index.cjs.map +1 -1
- package/dist/google/index.js +3 -13
- package/dist/google/index.js.map +1 -1
- package/dist/google/util.d.ts +2 -4
- package/dist/{ids-P2T89Gou.js → ids-D1M1T6KJ.js} +26 -3
- package/dist/ids-D1M1T6KJ.js.map +1 -0
- package/dist/{ids-Dl9Ij63K.cjs → ids-DWmHjm17.cjs} +37 -2
- package/dist/ids-DWmHjm17.cjs.map +1 -0
- package/dist/{ingestTypes-C-IQB4nw.js → ingestTypes-Ci4eOgv-.js} +2 -2
- package/dist/{ingestTypes-C-IQB4nw.js.map → ingestTypes-Ci4eOgv-.js.map} +1 -1
- package/dist/{ingestTypes-DZpAC3N_.cjs → ingestTypes-DbuQNuK6.cjs} +2 -2
- package/dist/{ingestTypes-DZpAC3N_.cjs.map → ingestTypes-DbuQNuK6.cjs.map} +1 -1
- package/dist/{promptSafety-BOc1B3im.cjs → promptSafety-0ZKHX-6J.cjs} +5 -39
- package/dist/promptSafety-0ZKHX-6J.cjs.map +1 -0
- package/dist/{promptSafety-B5KPkaNY.js → promptSafety-RE1SPxBN.js} +3 -25
- package/dist/promptSafety-RE1SPxBN.js.map +1 -0
- package/dist/remote-host/server/index.cjs +4 -4
- package/dist/remote-host/server/index.cjs.map +1 -1
- package/dist/remote-host/server/index.js +1 -1
- package/dist/scheduler/index.cjs +2 -4
- package/dist/scheduler/index.cjs.map +1 -1
- package/dist/scheduler/index.js +1 -3
- package/dist/scheduler/index.js.map +1 -1
- package/dist/{server-B7mtx4Cd.cjs → server-DOGKfUuD.cjs} +9 -17
- package/dist/server-DOGKfUuD.cjs.map +1 -0
- package/dist/{server-CNLZZONa.js → server-sGNe-msA.js} +9 -11
- package/dist/server-sGNe-msA.js.map +1 -0
- package/dist/text-CaAh9ZSO.cjs +27 -0
- package/dist/text-CaAh9ZSO.cjs.map +1 -0
- package/dist/text-CjDbP2O1.js +22 -0
- package/dist/text-CjDbP2O1.js.map +1 -0
- package/dist/utils/errors.d.ts +2 -0
- package/dist/utils/index.cjs +6 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/text.d.ts +12 -0
- package/dist/whisper/index.cjs +7 -9
- package/dist/whisper/index.cjs.map +1 -1
- package/dist/whisper/index.js +1 -3
- package/dist/whisper/index.js.map +1 -1
- package/dist/whisper/internal.d.ts +1 -1
- package/dist/workspace-setup/index.js +12 -3
- package/dist/workspace-setup/index.js.map +1 -1
- package/package.json +7 -1
- package/dist/errorMessage--LvXe_Jx.cjs +0 -16
- package/dist/errorMessage--LvXe_Jx.cjs.map +0 -1
- package/dist/errorMessage-DIdhheHk.js +0 -11
- package/dist/errorMessage-DIdhheHk.js.map +0 -1
- package/dist/ids-Dl9Ij63K.cjs.map +0 -1
- package/dist/ids-P2T89Gou.js.map +0 -1
- package/dist/promptSafety-B5KPkaNY.js.map +0 -1
- package/dist/promptSafety-BOc1B3im.cjs.map +0 -1
- package/dist/server-B7mtx4Cd.cjs.map +0 -1
- package/dist/server-CNLZZONa.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-sGNe-msA.js","names":[],"sources":["../src/collection/server/jsonlQuery.ts","../src/collection/server/derive.ts","../src/collection/server/queryRunner.ts","../src/collection/core/recordZ.ts","../src/collection/server/validate.ts","../src/collection/server/mutate.ts","../src/collection/server/ontology.ts","../src/collection/server/dynamicIcon.ts","../src/collection/server/util.ts","../src/collection/server/spawn.ts","../src/collection/server/delete.ts","../src/collection/server/views.ts","../src/workspace-setup/slug.ts","../src/collection/server/manageTool.ts"],"sourcesContent":["// Aggregation-query executor for FILE-BACKED collections: the same DSL\n// (`core/queryZ.ts`) and the same DuckDB engine as the CSV store, run\n// over a temp JSONL of the collection's rows. Deliberately takes\n// ALREADY-LOADED rows rather than pointing DuckDB at the record files,\n// for two correctness reasons:\n//\n// 1. **Computed fields.** Stored record files never contain `derived` /\n// `rollup` / `toggle` values — the caller passes ENRICHED rows\n// (`listItems` → `enrichItems`), so \"sum of invoice totals\" works\n// even when `total` is a formula.\n// 2. **The symlink defense.** A `read_json('<dataDir>/*.json')` glob\n// would follow a symlinked record file (the exact file-disclosure\n// hole `listItems`' lstat check closes) — DuckDB only ever sees\n// content that already passed the guarded reader.\n//\n// The temp JSONL lives in the same 0700 cache dir as CSV decode copies,\n// is written 0600 (shared OS tmpdir), and is unlinked when the query\n// finishes. Caller wiring lives in `queryRunner.ts` (NOT `store.ts` —\n// enrichment lives in `derive.ts`, which itself consumes `storeFor`, so\n// wiring it into the store would create an import cycle).\n\nimport { mkdir, open, unlink } from \"node:fs/promises\";\nimport { randomBytes } from \"node:crypto\";\nimport path from \"node:path\";\nimport type { CollectionItem } from \"../core/schema\";\nimport type { CollectionQuery, CollectionQueryAggregate } from \"../core/queryZ\";\nimport { compileJsonlQuery } from \"./csvQuery\";\nimport { cacheDir, normalizeCsvValue, queryCsv } from \"./csvStore\";\nimport { log } from \"./host\";\n\n/** SQL semantics for an aggregate-only query over ZERO rows: one scalar\n * row (`count` = 0, everything else NULL) — the same shape the CSV path\n * produces for a header-only file, so callers reading `rows[0]` never\n * break on storage kind or emptiness. A grouped query over zero rows is\n * zero groups (`[]`), also matching SQL. Synthesized here because DuckDB\n * has no empty file to infer a schema from. */\nfunction emptyCollectionResult(query: CollectionQuery): Record<string, unknown>[] {\n if ((query.groupBy?.length ?? 0) > 0) return [];\n const aggregates = Object.entries(query.aggregates ?? {});\n return [Object.fromEntries(aggregates.map(([alias, aggregate]: [string, CollectionQueryAggregate]) => [alias, aggregate.op === \"count\" ? 0 : null]))];\n}\n\n/** The SOURCE columns a query reads: groupBy columns, aggregate input\n * columns, and where fields. (orderBy resolves to groupBy columns or\n * aggregate ALIASES — never to a new source column — so it adds none.) */\nfunction referencedSourceColumns(query: CollectionQuery): string[] {\n const columns = new Set<string>(query.groupBy ?? []);\n for (const aggregate of Object.values(query.aggregates ?? {})) {\n if (aggregate.column !== undefined) columns.add(aggregate.column);\n }\n for (const cond of query.where ?? []) columns.add(cond.field);\n return [...columns];\n}\n\n/** Referenced columns that appear in NO row — a freshly-added optional\n * field, or a derived field whose inputs no record has yet. DuckDB only\n * infers columns from keys that occur in the JSONL, so without help a\n * valid query on such a field binder-errors here while the CSV path\n * (whose header always declares the column) returns NULLs. Padding the\n * FIRST line with `col: null` makes the column exist (full-scan\n * inference unions keys across lines) with matching NULL semantics. */\nfunction absentReferencedColumns(rows: CollectionItem[], query: CollectionQuery): string[] {\n const present = new Set<string>();\n for (const row of rows) for (const key of Object.keys(row)) present.add(key);\n return referencedSourceColumns(query).filter((column) => !present.has(column));\n}\n\n/** Run a validated query over enriched collection rows. */\nexport async function runQueryOverRows(rows: CollectionItem[], query: CollectionQuery): Promise<Record<string, unknown>[]> {\n if (rows.length === 0) return emptyCollectionResult(query);\n await mkdir(cacheDir(), { recursive: true, mode: 0o700 });\n const jsonlPath = path.join(cacheDir(), `q-${randomBytes(8).toString(\"hex\")}.jsonl`);\n try {\n // Stream row-by-row — a map+join would duplicate an uncapped\n // collection as one giant string before writing it. The first line\n // carries null placeholders for referenced-but-absent columns (see\n // `absentReferencedColumns`); a row's own values always win.\n const nullPads = Object.fromEntries(absentReferencedColumns(rows, query).map((column) => [column, null]));\n const handle = await open(jsonlPath, \"wx\", 0o600);\n try {\n let first = true;\n for (const row of rows) {\n await handle.write(`${JSON.stringify(first ? { ...nullPads, ...row } : row)}\\n`);\n first = false;\n }\n } finally {\n await handle.close();\n }\n const { sql, params } = compileJsonlQuery(query);\n const result = await queryCsv(sql, [jsonlPath, ...params]);\n return result.map((row) => Object.fromEntries(Object.entries(row).map(([key, value]) => [key, normalizeCsvValue(value)])));\n } finally {\n // The finally covers the WRITE too — a partially-written file from a\n // rejected write must not linger in the shared tmpdir. A failed unlink\n // (other than \"never created\") is only logged: throwing here would\n // mask the query's own error.\n await unlink(jsonlPath).catch((err: unknown) => {\n if ((err as { code?: string }).code !== \"ENOENT\") log.warn(\"collections\", \"temp JSONL cleanup failed\", { path: jsonlPath, error: String(err) });\n });\n }\n}\n","// Server-side computed-field enrichment for collection records: the\n// host evaluates `derived` formulas (via the SHARED saturation loop in\n// src/utils/collections/deriveAll.ts — never a server reimplementation),\n// projects `toggle` fields off their enum, and resolves `embed` fields\n// to their fixed target record. The client does the same at render\n// time; this module gives server consumers (manageCollection getItems)\n// the same numbers the user sees on screen.\n\nimport { backlinkRows, projectBacklinkRow, rollupValue } from \"../core/backlinks\";\nimport { deriveAll, type DeriveRefRecords } from \"../core/deriveAll\";\nimport { loadCollection, type DiscoveryOptions } from \"./discovery\";\nimport type { LoadedCollection } from \"./discoveredCollection\";\nimport { storeFor } from \"./store\";\nimport { fieldText } from \"../core/fieldText\";\nimport { embedTargetId } from \"../core/schema\";\nimport type { CollectionFieldSpec, CollectionItem, CollectionSchema } from \"../core/schema\";\n\n/** Slugs of every collection referenced by a `ref` field — top-level\n * and one level into `table` sub-fields (nested tables are\n * schema-rejected). Mirrors the client's `uniqueRefTargets`. */\nfunction uniqueRefTargets(schema: CollectionSchema): string[] {\n const targets = new Set<string>();\n const walk = (fields: Record<string, CollectionFieldSpec>): void => {\n for (const field of Object.values(fields)) {\n if (field.type === \"ref\" && typeof field.to === \"string\" && field.to.length > 0) targets.add(field.to);\n if (field.type === \"table\" && field.of) walk(field.of);\n }\n };\n walk(schema.fields);\n return [...targets];\n}\n\n/** Slugs of every collection referenced by an `embed` field (top-level\n * only, like the client). */\nfunction uniqueEmbedTargets(schema: CollectionSchema): string[] {\n const targets = new Set<string>();\n for (const field of Object.values(schema.fields)) {\n if (field.type === \"embed\" && typeof field.to === \"string\" && field.to.length > 0) targets.add(field.to);\n }\n return [...targets];\n}\n\n/** Slugs of every SOURCE collection a `backlinks` or `rollup` field\n * reverses over — loaded exactly like ref/embed targets (whole\n * collection, once; the two field kinds share one load). */\nfunction uniqueBacklinkSources(schema: CollectionSchema): string[] {\n const sources = new Set<string>();\n for (const field of Object.values(schema.fields)) {\n if ((field.type === \"backlinks\" || field.type === \"rollup\") && field.from.length > 0) sources.add(field.from);\n }\n return [...sources];\n}\n\ninterface LinkedTarget {\n schema: CollectionSchema;\n /** primary-key slug → record (ref targets store the DERIVED record,\n * mirroring the client's `buildRefRecordMap`, so a formula deref\n * like `ticker.price` can read the target's own derived columns). */\n byId: Record<string, CollectionItem>;\n}\n\nasync function loadTarget(slug: string, opts: DiscoveryOptions): Promise<LinkedTarget | null> {\n const target = await loadCollection(slug, opts);\n if (!target) return null;\n const items = await storeFor(target, { workspaceRoot: opts.workspaceRoot }).list();\n const byId: Record<string, CollectionItem> = {};\n for (const item of items) {\n const itemId = item[target.schema.primaryKey];\n if (typeof itemId === \"string\" && itemId.length > 0) byId[itemId] = deriveAll(target.schema, item, {});\n }\n return { schema: target.schema, byId };\n}\n\n/** Load every ref/embed target and backlink source collection once.\n * Unknown / unloadable targets are simply absent — downstream derefs\n * resolve to null (em-dash) and backlinks to an empty row set, the\n * same fail-soft the UI renders. */\nasync function loadLinkedTargets(schema: CollectionSchema, opts: DiscoveryOptions): Promise<Record<string, LinkedTarget>> {\n const slugs = [...new Set([...uniqueRefTargets(schema), ...uniqueEmbedTargets(schema), ...uniqueBacklinkSources(schema)])];\n const loaded: Record<string, LinkedTarget> = {};\n for (const slug of slugs) {\n const target = await loadTarget(slug, opts);\n if (target) loaded[slug] = target;\n }\n return loaded;\n}\n\nfunction toRefRecords(linked: Record<string, LinkedTarget>): DeriveRefRecords {\n return Object.fromEntries(Object.entries(linked).map(([slug, target]) => [slug, target.byId]));\n}\n\n/** The matching source rows for one `backlinks` field, projected to the\n * source primaryKey + `display` columns — so getItems on a\n * heavily-referenced record stays a summary, not a dump of the source\n * collection. Missing source ⇒ [] (fail-soft). The rows come from the\n * DERIVED source records (`byId`), so `display`/`filter` on a derived\n * source column (an invoice `total`) works. */\nfunction projectBacklinks(\n field: Extract<CollectionFieldSpec, { type: \"backlinks\" }>,\n schema: CollectionSchema,\n enriched: CollectionItem,\n linked: Record<string, LinkedTarget>,\n): CollectionItem[] {\n const source = linked[field.from];\n if (!source) return [];\n const selfId = String(enriched[schema.primaryKey] ?? \"\");\n return backlinkRows(field, selfId, Object.values(source.byId)).map((row) => projectBacklinkRow(row, field.display, source.schema.primaryKey));\n}\n\n/** Project the computed (never-stored) field kinds onto one derived\n * record: `toggle` → boolean off its enum, `embed` → the target record\n * (fixed `id` or per-record `idField`), or null when missing,\n * `backlinks` → the matching source rows (see `projectBacklinks`). */\nfunction projectComputed(schema: CollectionSchema, enriched: CollectionItem, linked: Record<string, LinkedTarget>): CollectionItem {\n for (const [key, field] of Object.entries(schema.fields)) {\n if (field.type === \"toggle\" && field.field) {\n enriched[key] = fieldText(enriched[field.field]) === field.onValue;\n }\n if (field.type === \"embed\" && field.to) {\n const targetId = embedTargetId(field, enriched);\n enriched[key] = (targetId && linked[field.to]?.byId[targetId]) || null;\n }\n if (field.type === \"backlinks\") {\n enriched[key] = projectBacklinks(field, schema, enriched, linked);\n }\n }\n return enriched;\n}\n\n/** Resolve every rollup field onto a COPY of the record, BEFORE the\n * formula pass — a `derived` formula may reference rollup values as\n * plain identifiers (`played = homePlayed + awayPlayed`). Same reverse\n * machinery as backlinks, collapsed to a number: an unresolvable source\n * is null (a formula reading it fails soft to em-dash); an empty match\n * set is a real 0. Returns `record` unchanged when the schema declares\n * no rollups. */\nfunction projectRollups(schema: CollectionSchema, record: CollectionItem, linked: Record<string, LinkedTarget>): CollectionItem {\n let out = record;\n for (const [key, field] of Object.entries(schema.fields)) {\n if (field.type !== \"rollup\") continue;\n if (out === record) out = { ...record };\n const source = linked[field.from];\n const selfId = String(record[schema.primaryKey] ?? \"\");\n out[key] = source ? rollupValue(field, selfId, Object.values(source.byId)) : null;\n }\n return out;\n}\n\n/** Enrich records with every host-computed field: derived formulas\n * evaluated (cross-collection derefs included), toggles projected,\n * embeds resolved. Loads each linked collection ONCE per call. Input\n * records are not mutated. */\nexport async function enrichItems(collection: LoadedCollection, items: CollectionItem[], opts: DiscoveryOptions = {}): Promise<CollectionItem[]> {\n const { schema } = collection;\n const linked = await loadLinkedTargets(schema, opts);\n const refRecords = toRefRecords(linked);\n // Rollups FIRST (formulas may read them), then the formula pass, then\n // the remaining projections — the client mirrors this order exactly\n // (`deriveRecord` / `evaluateDerived` in collection-plugin).\n return items.map((item) => projectComputed(schema, deriveAll(schema, projectRollups(schema, item, linked), refRecords), linked));\n}\n","// Engine-level aggregation runner: ONE entry point for the structured\n// query DSL (`core/queryZ.ts`) over any collection, regardless of its\n// storage backend. Native when the store has a query engine (the CSV\n// store scans the whole file in DuckDB); otherwise the shared fallback —\n// enrich the full record set (computed fields become real columns) and\n// run the same compiled SQL over a temp JSONL (`jsonlQuery.ts`).\n//\n// Callers (manageCollection's queryItems, which also backs the desktop\n// custom view's `/query` route) go through here so the two engines can\n// never drift — and so a future backend gets aggregation for free by\n// either declaring `nativeQuery` or doing nothing.\n//\n// Lives at the derive layer (above the store), which is what resolves the\n// old wiring note in jsonlQuery.ts: enrichment consumes `storeFor`, so\n// this could never live inside `store.ts` without an import cycle.\n\nimport type { CollectionQuery } from \"../core/queryZ\";\nimport type { LoadedCollection } from \"./discoveredCollection\";\nimport type { DiscoveryOptions } from \"./discovery\";\nimport { enrichItems } from \"./derive\";\nimport { runQueryOverRows } from \"./jsonlQuery\";\nimport { storeFor } from \"./store\";\n\nexport async function runCollectionQuery(\n collection: LoadedCollection,\n query: CollectionQuery,\n opts: DiscoveryOptions = {},\n): Promise<Record<string, unknown>[]> {\n const store = storeFor(collection, { workspaceRoot: opts.workspaceRoot });\n if (store.query) return store.query(query);\n // File-backed: load through the guarded reader (symlink defenses) and\n // enrich BEFORE DuckDB sees anything — a raw read_json over the record\n // files would both follow symlinks and miss every computed field.\n const enriched = await enrichItems(collection, await store.list(), opts);\n return runQueryOverRows(enriched, query);\n}\n","// Compiled per-collection zod RECORD validators (plan step ⓪ Phase B of\n// plans/done/collection-ontology.md): `compileRecordZ(schema, tier)` turns a\n// CollectionSchema's `fields` into a zod object validator for the stored\n// record JSON, so every consumer of record validation — the putItems write\n// gate, the post-hoc file scan, and (future) `mutate` action `params` forms —\n// derives its checks from the same compiler instead of growing parallel\n// hand-rolled loops.\n//\n// Two tiers, per the lint-not-lock principle:\n//\n// - `\"enforced\"` — the write-gate tier. Reproduces EXACTLY the historical\n// three checks (required fields non-empty, enum values in the closed set;\n// the primaryKey↔filename identity check stays in `validateRecordObject`,\n// which owns the id). Message strings are part of the contract: they are\n// the `problem` feedback the authoring LLM acts on, and the file scan and\n// the write gate must report enforced-tier violations IDENTICALLY.\n// - `\"strict\"` — the report-only tier: enforced plus per-type checks\n// (number/money hold numerics, booleans are booleans, dates parse, table\n// rows conform to their sub-schema). Consumed by `validateCollectionRecords`\n// (the scan surfaced through presentCollection / the detail response) so\n// legacy records written under the loose rules get REPORTED, never\n// rejected. Promote a strict check into the enforced tier only once the\n// lint runs clean across real workspaces.\n//\n// Isomorphic (zod + pure logic, no node built-ins) but — like `./schemaZ` —\n// deliberately NOT exported through the browser barrel; the server surface\n// re-exports it via `../server/validate`.\n\nimport { z } from \"zod\";\nimport { coerceNumeric } from \"./backlinks\";\nimport { parseIsoDate, parseIsoDateTime } from \"./calendarGrid\";\nimport { COMPUTED_TYPES } from \"./schema\";\nimport type { CollectionFieldSpec, CollectionItem, CollectionSchema, CollectionSubFieldSpec } from \"./schema\";\n\n// COMPUTED_TYPES moved to `./schema` (zod-free, so `./draft` can share\n// it); re-exported here for the existing server-surface importers.\nexport { COMPUTED_TYPES };\n\nexport type RecordCheckTier = \"enforced\" | \"strict\";\n\ntype AnyFieldSpec = CollectionFieldSpec | CollectionSubFieldSpec;\n\n/** The emptiness rule shared by `required` and the \"only check present\n * values\" gate. NOT a truthiness check — `0` and `false` are filled. */\nconst isEmptyValue = (value: unknown): boolean => value === undefined || value === null || value === \"\";\n\n/** The historical write-gate checks, verbatim: required non-empty, enum\n * membership (compared as strings, so a numeric `5` satisfies `\"5\"`). */\nfunction enforcedProblem(key: string, spec: AnyFieldSpec, value: unknown): string | null {\n const empty = isEmptyValue(value);\n if (spec.required && empty) return `missing required field '${key}'`;\n if (!empty && spec.type === \"enum\" && !spec.values.includes(String(value))) {\n return `'${key}' = '${String(value)}' is not one of [${spec.values.join(\", \")}]`;\n }\n return null;\n}\n\n// Numeric coercion for the strict `number`/`money` check — moved to\n// `./backlinks` so rollup sums share the same definition of \"numeric\".\n\n/** Report-only per-type checks on a PRESENT value. Date / datetime reuse the\n * calendar's STRICT civil parsers (`parseIsoDate` / `parseIsoDateTime`), so\n * the lint flags exactly the values the calendar / trigger / spawn code\n * would silently drop — impossible days like `2026-02-30`, and datetimes\n * outside the canonical `YYYY-MM-DDTHH:MM[:SS]` shape (e.g. a `Z` suffix,\n * which the day view can't place). `string`-backed types accept anything\n * stringifiable; `ref` existence is out of scope. */\nfunction strictTypeProblem(key: string, spec: AnyFieldSpec, value: unknown): string | null {\n switch (spec.type) {\n case \"number\":\n case \"money\":\n return Number.isFinite(coerceNumeric(value)) ? null : `'${key}' = '${String(value)}' is not numeric (a '${spec.type}' field stores a plain number)`;\n case \"boolean\":\n return value === true || value === false ? null : `'${key}' = '${String(value)}' is not a boolean (store true or false, unquoted)`;\n case \"date\":\n return parseIsoDate(value) !== null ? null : `'${key}' = '${String(value)}' is not a real YYYY-MM-DD date`;\n case \"datetime\":\n return parseIsoDateTime(value) !== null\n ? null\n : `'${key}' = '${String(value)}' is not a YYYY-MM-DDTHH:MM datetime (seconds optional, no timezone suffix — the shape the calendar parses)`;\n default:\n return null;\n }\n}\n\n/** Strict check for a PRESENT `table` value: an array of row objects, each\n * row conforming to the sub-schema (required / enum / typed sub-values).\n * First row problem wins, prefixed with the row number so the fix is\n * locatable. */\nfunction strictTableProblem(key: string, spec: Extract<CollectionFieldSpec, { type: \"table\" }>, value: unknown): string | null {\n if (!Array.isArray(value)) return `'${key}' = '${String(value)}' is not an array of rows (a 'table' field stores an array of row objects)`;\n for (let index = 0; index < value.length; index++) {\n const row: unknown = value[index];\n if (!row || typeof row !== \"object\" || Array.isArray(row)) return `'${key}' row ${index + 1} is not an object`;\n for (const [subKey, subSpec] of Object.entries(spec.of)) {\n const subValue = (row as Record<string, unknown>)[subKey];\n // Typed checks apply to PRESENT sub-values only — an empty optional\n // cell is fine, mirroring the top-level gate in `recordFieldProblem`.\n const problem = enforcedProblem(subKey, subSpec, subValue) ?? (isEmptyValue(subValue) ? null : strictTypeProblem(subKey, subSpec, subValue));\n if (problem) return `'${key}' row ${index + 1}: ${problem}`;\n }\n }\n return null;\n}\n\n/** First problem for one field's stored value under `tier`, or null.\n * Enforced checks always run (and their messages never vary by tier — the\n * scan and the write gate must agree on them); strict adds the per-type\n * layer on present values only. */\nexport function recordFieldProblem(key: string, spec: AnyFieldSpec, value: unknown, tier: RecordCheckTier): string | null {\n const enforced = enforcedProblem(key, spec, value);\n if (enforced || tier === \"enforced\") return enforced;\n if (isEmptyValue(value)) return null;\n if (spec.type === \"table\") return strictTableProblem(key, spec, value);\n return strictTypeProblem(key, spec, value);\n}\n\n// Compiled validators cached per schema OBJECT (a putItems batch validates\n// many rows against the same LoadedCollection.schema instance; a re-loaded\n// collection gets a fresh schema object and naturally re-compiles).\nconst compiled = new WeakMap<CollectionSchema, Partial<Record<RecordCheckTier, z.ZodType>>>();\n\n/** Compile `schema.fields` into a zod validator for a stored record.\n * Loose object: unknown keys are allowed and any declared key may be\n * absent (records are user files, not parse-and-rewrite targets —\n * callers validate, they never persist the parse output). The checks run\n * as ONE object-level refine iterating fields in declaration order —\n * per-key shape schemas can't express \"key may be absent BUT its absence\n * must still reach the required check\", and the single loop keeps the\n * first reported issue identical to the historical first-problem-wins\n * contract. */\nexport function compileRecordZ(schema: CollectionSchema, tier: RecordCheckTier): z.ZodType {\n const cached = compiled.get(schema)?.[tier];\n if (cached) return cached;\n const stored = Object.entries(schema.fields).filter(([, spec]) => !COMPUTED_TYPES.has(spec.type));\n const validator = z.looseObject({}).superRefine((record, ctx) => {\n for (const [key, spec] of stored) {\n const problem = recordFieldProblem(key, spec, record[key], tier);\n if (problem) ctx.addIssue({ code: \"custom\", message: problem, path: [key] });\n }\n });\n const entry = compiled.get(schema) ?? {};\n entry[tier] = validator;\n compiled.set(schema, entry);\n return validator;\n}\n\n/** First schema problem on an in-memory record under `tier`, or null. One\n * issue per record keeps the report short and the fix obvious (the\n * historical contract of `validateRecordObject`). */\nexport function firstRecordProblem(record: CollectionItem, schema: CollectionSchema, tier: RecordCheckTier): string | null {\n const result = compileRecordZ(schema, tier).safeParse(record);\n if (result.success) return null;\n return result.error.issues[0]?.message ?? \"record failed schema validation\";\n}\n","// Validate a collection's record files and report problems back to the\n// authoring LLM. The host SILENTLY skips unparseable records at read time\n// (see `listItems`), so without this a single malformed file looks like\n// \"records vanished.\" `presentCollection` — which the LLM is told to call\n// after every write — runs this and surfaces the problems in its result,\n// closing the loop so the model fixes the file instead of a human noticing\n// missing rows much later.\n\nimport { readdir, readFile, lstat } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { getWorkspaceRoot, log } from \"./host\";\nimport { isContainedInRoot } from \"./paths\";\nimport { storeFor } from \"./store\";\nimport { firstRecordProblem, type RecordCheckTier } from \"../core/recordZ\";\nimport type { LoadedCollection } from \"./discoveredCollection\";\nimport type { CollectionItem, CollectionSchema } from \"../core/schema\";\n\n// The compiled record validators (and COMPUTED_TYPES, which moved next to\n// them) live in the isomorphic ../core/recordZ; re-exported here so the\n// existing `collection/server` importers resolve unchanged and future\n// consumers (the reserved `mutate` action's `params` form) share the\n// same compiler.\nexport { COMPUTED_TYPES, compileRecordZ, recordFieldProblem, type RecordCheckTier } from \"../core/recordZ\";\n\nexport interface RecordIssue {\n /** Record filename, e.g. `lesson-003.json`. */\n file: string;\n /** Human-readable problem, written to be actionable by the LLM. */\n problem: string;\n}\n\n// Don't flood the result; the first batch is enough to act on.\nconst MAX_ISSUES = 25;\n\n/** Read every `<id>.json` under the collection's dataDir and report the\n * ones that won't load or violate the schema. An empty list means every\n * record is fine. */\n/** List entries under the data dir, guarding realpath containment (against a\n * symlinked dir swapped in after discovery, like `listItems`) and treating a\n * missing dir as empty while surfacing real I/O faults. */\nasync function listRecordFilenames(dataDir: string, workspaceRoot: string): Promise<string[]> {\n if (!isContainedInRoot(dataDir, workspaceRoot)) {\n log.warn(\"collections\", \"validate refused: dataDir escapes workspace via symlink\", { dataDir });\n return [];\n }\n try {\n return await readdir(dataDir);\n } catch (err) {\n const error = err as { code?: string };\n if (error.code === \"ENOENT\") return []; // no dir yet = no records\n throw err; // surface permission / I/O faults instead of silently passing\n }\n}\n\nexport async function validateCollectionRecords(collection: LoadedCollection, opts: { workspaceRoot?: string } = {}): Promise<RecordIssue[]> {\n // A `dataSource` collection has no record FILES to validate — its rows\n // come from the external data file (type mismatches there surface as\n // raw values in the views, not as repairable record files).\n if (collection.schema.dataSource !== undefined) return [];\n // A `storage` collection's records live in its database, not as files —\n // validate what the store serves instead of scanning the phantom dataDir.\n if (collection.schema.storage !== undefined) return validateStoreRecords(collection, opts);\n const workspaceRoot = opts.workspaceRoot ?? getWorkspaceRoot();\n const entries = await listRecordFilenames(collection.dataDir, workspaceRoot);\n const issues: RecordIssue[] = [];\n for (const name of entries.sort()) {\n if (!name.endsWith(\".json\") || name.startsWith(\".\")) continue;\n if (issues.length >= MAX_ISSUES) break;\n const issue = await inspectRecord(path.join(collection.dataDir, name), name, collection.schema);\n if (issue) issues.push(issue);\n }\n return issues;\n}\n\n/** Store-backed twin of the file scan: list every record through the\n * collection's store and lint it with the same \"strict\" report-only tier.\n * A row the store can't even parse is invisible here (the store skips\n * it), so the read/parse classifications of the file scan don't apply —\n * schema violations are what this catches. `file` carries the record id\n * (there is no per-record filename). */\nasync function validateStoreRecords(collection: LoadedCollection, opts: { workspaceRoot?: string }): Promise<RecordIssue[]> {\n let items: CollectionItem[];\n try {\n items = await storeFor(collection, { workspaceRoot: opts.workspaceRoot }).list();\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n return [{ file: \"(store)\", problem: `records could not be read from the storage backend: ${reason}` }];\n }\n const issues: RecordIssue[] = [];\n for (const item of items) {\n if (issues.length >= MAX_ISSUES) break;\n const itemId = String(item[collection.schema.primaryKey] ?? \"\");\n const problem = validateRecordObject(item, itemId, collection.schema, \"strict\");\n if (problem) issues.push({ file: itemId, problem });\n }\n return issues;\n}\n\n// Read a record file's text, or classify why it can't be read (missing /\n// symlink / unreadable). Split out to keep `inspectRecord` under the line limit.\nasync function readRecordText(fullPath: string, name: string): Promise<{ raw: string } | RecordIssue> {\n try {\n const stat = await lstat(fullPath);\n if (!stat.isFile()) return { file: name, problem: \"not a regular file (symlink?) — skipped, won't appear\" };\n return { raw: await readFile(fullPath, \"utf-8\") };\n } catch {\n return { file: name, problem: \"could not be read — skipped, won't appear\" };\n }\n}\n\n/** Classify a single record file: unreadable / unparseable / non-object /\n * schema violation, or null when it's fine. */\nasync function inspectRecord(fullPath: string, name: string, schema: CollectionSchema): Promise<RecordIssue | null> {\n const read = await readRecordText(fullPath, name);\n if (\"problem\" in read) return read;\n let parsed: unknown;\n try {\n parsed = JSON.parse(read.raw);\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n return {\n file: name,\n problem: `invalid JSON (${reason}) — SKIPPED, won't appear. Usual cause: an unescaped \" inside a string value; use 「」/『』 or write \\\\\" instead.`,\n };\n }\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n return { file: name, problem: \"not a JSON object — skipped, won't appear\" };\n }\n // \"strict\" tier: the file scan is REPORT-ONLY (surfaced through\n // presentCollection / the detail response), so it lints the per-type\n // rules the write gate does not yet enforce — legacy records written\n // under the loose rules get reported here, never rejected on write.\n const problem = validateRecordObject(parsed as CollectionItem, name.replace(/\\.json$/, \"\"), schema, \"strict\");\n return problem ? { file: name, problem } : null;\n}\n\n/** First schema problem on an in-memory record (primaryKey↔id mismatch,\n * then the compiled per-field checks — see `../core/recordZ` for the two\n * tiers), or null when it's fine. One issue per record keeps the report\n * short and the fix obvious. Pure + exported so write paths\n * (manageCollection putItems) can gate on the SAME enforced rules the\n * post-hoc file scan reports — `itemId` is the id the record is (or\n * would be) stored under. The default `\"enforced\"` tier keeps every\n * write gate on the historical three checks; only pass `\"strict\"` from\n * report-only surfaces. */\nexport function validateRecordObject(record: CollectionItem, itemId: string, schema: CollectionSchema, tier: RecordCheckTier = \"enforced\"): string | null {\n const idValue = record[schema.primaryKey];\n if (typeof idValue !== \"string\" || idValue !== itemId) {\n return `'${schema.primaryKey}' is '${String(idValue ?? \"\")}' but must equal the filename ('${itemId}'), or the record can't be opened`;\n }\n return firstRecordProblem(record, schema, tier);\n}\n","// Execute a `kind: \"mutate\"` action (plan step ④ of\n// plans/done/collection-ontology.md): the HOST applies a declarative,\n// merge-semantics write to one record — no LLM, no tokens. The pipeline\n// deliberately reuses the governed write path's pieces, never a third\n// mechanism:\n//\n// 1. params — the mini-form is validated per spec by the SAME\n// compiled record checks `putItems` uses (`recordFieldProblem`,\n// strict tier: required/enum plus typed values, since a form\n// rejects rather than lints);\n// 2. merge — the resolved `set` merges over the stored record with\n// computed keys stripped (healing stale copies, exactly like\n// manageCollection's `mode: \"merge\"`);\n// 3. gate — `validateRecordObject`, the same write gate every\n// governed path runs;\n// 4. write — `writeItem` (atomic, change-publishing).\n//\n// The `require` state gate is enforced HERE, against the exact snapshot\n// the merge runs on — the route's earlier `actionVisible` pre-check is a\n// fast 409 on a stale button, but a concurrent write between the route's\n// read and ours could otherwise slip a mutation past a gate that no\n// longer holds (Codex on PR #2105). Visibility is the authorization\n// rule, shared with the chat/agent kinds.\n\nimport { actionVisible } from \"../core/actionVisible\";\nimport { COMPUTED_TYPES, recordFieldProblem } from \"../core/recordZ\";\nimport { resolveMutateSet } from \"../core/mutateAction\";\nimport type { IoOptions } from \"./io\";\nimport { storeFor } from \"./store\";\nimport { validateRecordObject } from \"./validate\";\nimport type { LoadedCollection } from \"./discoveredCollection\";\nimport type { CollectionItem, CollectionMutateAction } from \"../core/schema\";\n\nexport type MutateActionOutcome =\n | { ok: true; item: CollectionItem }\n /** `status` picks the HTTP mapping: bad params / a write-gate reject are\n * the caller's 400s, a missing record its 404, an unmet `require` its\n * 409, a refused write its 500. */\n | { ok: false; status: \"invalid-params\" | \"invalid-record\" | \"not-found\" | \"require-unmet\" | \"write-refused\"; problem: string };\n\n/** First problem with the submitted params, or null. Every declared param\n * is checked by the shared record-field validator; keys the action never\n * declared are rejected outright — a stray key would otherwise ride the\n * resolved `set` semantics silently. */\nexport function firstMutateParamProblem(action: CollectionMutateAction, params: Record<string, unknown>): string | null {\n const declared = action.params ?? {};\n for (const key of Object.keys(params)) {\n if (declared[key] === undefined) return `unknown param '${key}' — not declared by action '${action.id}'`;\n }\n for (const [key, spec] of Object.entries(declared)) {\n const problem = recordFieldProblem(key, spec, params[key], \"strict\");\n if (problem) return problem;\n }\n return null;\n}\n\n/** Apply one mutate action to one record. Never throws for data-shaped\n * failures — the outcome's `problem` doubles as agent feedback when an\n * LLM (not the user) pressed the button. */\nexport async function applyMutateAction(\n collection: LoadedCollection,\n action: CollectionMutateAction,\n itemId: string,\n params: Record<string, unknown>,\n opts: IoOptions = {},\n): Promise<MutateActionOutcome> {\n const paramProblem = firstMutateParamProblem(action, params);\n if (paramProblem) return { ok: false, status: \"invalid-params\", problem: paramProblem };\n\n // Discovery statically rejects mutate actions on read-only (dataSource)\n // schemas, so a missing `write` is defense in depth, not a live path.\n const store = storeFor(collection, opts);\n if (!store.write) return { ok: false, status: \"write-refused\", problem: \"write refused (read-only collection)\" };\n\n const existing = await store.read(itemId);\n if (!existing) return { ok: false, status: \"not-found\", problem: `item '${itemId}' not found` };\n\n // Re-check `require` against THIS read — the snapshot the merge below\n // actually uses — so a write that landed after the route's pre-check\n // can't let the mutation run with its gate no longer satisfied.\n if (!actionVisible(action, existing)) {\n return { ok: false, status: \"require-unmet\", problem: `action '${action.id}' is not available for item '${itemId}' in its current state` };\n }\n\n // Merge over the stored record with computed keys stripped — a legacy /\n // raw-written record can carry a stale derived/embed value, and\n // re-writing it would perpetuate a forged host-computed value.\n const stored = Object.entries(existing).filter(([key]) => {\n const spec = collection.schema.fields[key];\n return !spec || !COMPUTED_TYPES.has(spec.type);\n });\n const merged: CollectionItem = { ...Object.fromEntries(stored), ...resolveMutateSet(action.set, params) };\n\n const invalid = validateRecordObject(merged, itemId, collection.schema);\n if (invalid) return { ok: false, status: \"invalid-record\", problem: invalid };\n\n const result = await store.write(itemId, merged);\n if (result.kind !== \"ok\") return { ok: false, status: \"write-refused\", problem: `write refused (${result.kind})` };\n return { ok: true, item: merged };\n}\n","// The machine-readable workspace ontology (plan step ① of\n// plans/done/collection-ontology.md): one entry per discovered collection with\n// the identity + relationship facts an agent needs to reason ACROSS\n// collections — which slugs exist, how they point at each other, how big\n// they are — without re-reading every schema.json. Derived on demand from\n// the schemas (no authoring, no storage), so it can never go stale.\n// Deliberately NOT a unified schema: collections stay bounded contexts;\n// semantic joining happens at read time in the caller.\n\nimport { readdir } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { discoverCollections, type DiscoveryOptions } from \"./discovery\";\nimport { storeFor } from \"./store\";\nimport { storageKindFor } from \"../core/schema\";\nimport { isRegularFile } from \"./io\";\nimport { isContainedInRoot } from \"./paths\";\nimport { getWorkspaceRoot } from \"./host\";\nimport type { LoadedCollection } from \"./discoveredCollection\";\nimport type { CollectionSchema } from \"../core/schema\";\nimport type { CollectionOntologyEntry, OntologyRelation } from \"../core/ontologyGraph\";\n\n// The entry/relation types live browser-safe in core/ontologyGraph.ts\n// (the Map panel builds the graph client-side from the raw entries);\n// re-exported here so the server subpath's surface is unchanged.\nexport type { CollectionOntologyEntry, OntologyRelation } from \"../core/ontologyGraph\";\n\n/** Extract the relations a schema declares, in field declaration order:\n * top-level `ref` / `embed` / `backlinks` fields plus `ref` sub-fields\n * inside `table` columns. Pure — exported so the phase-2 graph panel\n * can reuse it on already-loaded schemas. */\nexport function schemaRelations(schema: CollectionSchema): OntologyRelation[] {\n const relations: OntologyRelation[] = [];\n for (const [key, spec] of Object.entries(schema.fields)) {\n if (spec.type === \"ref\" || spec.type === \"embed\") relations.push({ field: key, kind: spec.type, to: spec.to });\n if (spec.type === \"backlinks\" || spec.type === \"rollup\") relations.push({ field: key, kind: spec.type, to: spec.from, via: spec.via });\n if (spec.type !== \"table\") continue;\n for (const [subKey, subSpec] of Object.entries(spec.of)) {\n if (subSpec.type === \"ref\") relations.push({ field: `${key}.${subKey}`, kind: \"ref\", to: subSpec.to });\n }\n }\n return relations;\n}\n\n/** Count the record files in a collection's data dir — the same\n * `<id>.json` entries `listItems` considers, WITHOUT parsing them (the\n * ontology is a summary; a malformed record is still a record). Each\n * candidate is classified by the SHARED `isRegularFile` lstat helper —\n * the exact file-disclosure defense `listItems` applies — so a\n * symlinked record never counts and the count can't diverge from the\n * readable set on any filesystem (Codex review on PR #2099, twice).\n * Fail-soft: a missing dir or a dataDir escaping the workspace via\n * symlink counts 0. */\nasync function countRecordFiles(dataDir: string, workspaceRoot: string): Promise<number> {\n if (!isContainedInRoot(dataDir, workspaceRoot)) return 0;\n try {\n const names = (await readdir(dataDir)).filter((name) => name.endsWith(\".json\") && !name.startsWith(\".\"));\n const checks = await Promise.all(names.map((name) => isRegularFile(path.join(dataDir, name))));\n return checks.filter(Boolean).length;\n } catch {\n return 0;\n }\n}\n\n/** A collection's record count for the ontology. File-backed: the cheap\n * readdir count. Every other backend (`dataSource` CSV, `storage`\n * sqlite): the store's page total — the dataDir is a phantom there, so\n * the readdir count would always misreport 0. Fail-soft to 0 like\n * `countRecordFiles` (an unreadable file / missing engine must not\n * break the whole ontology). */\nasync function countRecords(collection: LoadedCollection, workspaceRoot: string): Promise<number> {\n if (storageKindFor(collection.schema) !== \"file\") {\n try {\n return (await storeFor(collection, { workspaceRoot }).page({ limit: 0 })).total;\n } catch {\n return 0;\n }\n }\n return countRecordFiles(collection.dataDir, workspaceRoot);\n}\n\nasync function toOntologyEntry(collection: LoadedCollection, workspaceRoot: string): Promise<CollectionOntologyEntry> {\n const { schema } = collection;\n return {\n slug: collection.slug,\n title: schema.title,\n icon: schema.icon,\n primaryKey: schema.primaryKey,\n displayField: schema.displayField ?? schema.primaryKey,\n recordCount: await countRecords(collection, workspaceRoot),\n relations: schemaRelations(schema),\n };\n}\n\n/** Build the workspace ontology: every discovered collection (slug-sorted,\n * discovery's order), each with its outbound relations and a\n * readdir-cheap record count — fine to call on demand. */\nexport async function buildWorkspaceOntology(opts: DiscoveryOptions = {}): Promise<CollectionOntologyEntry[]> {\n const workspaceRoot = opts.workspaceRoot ?? getWorkspaceRoot();\n const collections = await discoverCollections(opts);\n return Promise.all(collections.map((collection) => toOntologyEntry(collection, workspaceRoot)));\n}\n","// Server-side compute for a collection's dynamic launcher-shortcut icon\n// (see `CollectionSchema.dynamicIcon`). Wraps the pure resolver in\n// `../core/dynamicIcon` with the one bit of I/O it needs: loading the\n// source collection's raw stored records.\n\nimport { firstDateField, resolveIcon, selectDynamicRecord } from \"../core/dynamicIcon\";\nimport { loadCollection, type DiscoveryOptions } from \"./discovery\";\nimport { storeFor } from \"./store\";\nimport { log } from \"./host\";\nimport type { LoadedCollection } from \"./discoveredCollection\";\nimport type { CollectionItem } from \"../core/schema\";\n\n/** Index `items` by their `primaryKey` value — the `recordsById` map a\n * `valueFrom` reference (e.g. `_config.defaultCity`) resolves against.\n * Items whose primary key isn't a string (shouldn't happen for a valid\n * schema, but records are untyped storage) are skipped rather than\n * coerced, so a broken key never silently shadows a real one. */\nfunction buildRecordsById(items: CollectionItem[], primaryKey: string): Record<string, CollectionItem> {\n const entries = items.filter((item) => typeof item[primaryKey] === \"string\").map((item): [string, CollectionItem] => [String(item[primaryKey]), item]);\n return Object.fromEntries(entries);\n}\n\n/** Order records by their `primaryKey` so record selection is deterministic:\n * `listItems` returns filesystem `readdir` order (arbitrary across machines),\n * which would let `from: \"first\"`, the no-`orderBy` `latest`, and `orderBy`\n * ties pick a different record — and thus a different icon — between\n * reconciles. A stable id sort pins one answer. */\nfunction sortByPrimaryKey(items: CollectionItem[], primaryKey: string): CollectionItem[] {\n return [...items].sort((left, right) => String(left[primaryKey] ?? \"\").localeCompare(String(right[primaryKey] ?? \"\")));\n}\n\n/** Compute the effective launcher icon for `collection`: its static\n * `schema.icon` when it declares no `dynamicIcon`, else the icon\n * resolved from `dynamicIcon.source`'s RAW stored records (no\n * derive/enrich — the icon rules match against stored values) via the\n * pure resolver. Fails soft on any read/discovery error (missing source\n * collection, filesystem error): falls back to `dynamicIcon.fallback ??\n * schema.icon` rather than surfacing to the collections list. */\nexport async function computeCollectionIcon(collection: LoadedCollection, opts: DiscoveryOptions = {}): Promise<string> {\n const { schema } = collection;\n const spec = schema.dynamicIcon;\n if (!spec) return schema.icon;\n try {\n const source = await loadCollection(spec.source.collection, opts);\n if (!source) return spec.fallback ?? schema.icon;\n const items = await storeFor(source, { workspaceRoot: opts.workspaceRoot }).list();\n const ordered = sortByPrimaryKey(items, source.schema.primaryKey);\n const orderBy = spec.source.orderBy ?? firstDateField(source.schema);\n const recordsById = buildRecordsById(ordered, source.schema.primaryKey);\n const record = selectDynamicRecord(ordered, spec.source, orderBy, recordsById);\n return resolveIcon(record, spec, schema.icon, recordsById);\n } catch (err) {\n log.warn(\"collections\", \"dynamic icon compute failed, falling back\", {\n slug: collection.slug,\n source: spec.source.collection,\n error: String(err),\n });\n return spec.fallback ?? schema.icon;\n }\n}\n","// Time constants kept local so the engine carries no host dependency.\n// `errorMessage` now comes from the canonical core helper (#2217) — the local\n// copy silently printed \"[object Object]\" for gRPC-shaped errors.\nexport { errorMessage } from \"../../utils/errors.js\";\n\nexport const ONE_SECOND_MS = 1_000;\nexport const ONE_DAY_MS = 24 * 60 * 60 * 1000;\n","// Host-driven recurrence for schema-driven collections.\n//\n// When a record satisfies its schema's `spawn.when` predicate (default:\n// the item is \"done\" per completionField/completionDoneValues), the host\n// creates the NEXT record with a forward-advanced `triggerField` date.\n//\n// The mechanism is CONVERGENT, not event-driven: we reconcile on a\n// predicate (\"matches `when` AND its successor doesn't exist yet\"), and\n// the successor's id + contents are a pure function of (source record,\n// rule). Creation is create-if-absent (`writeItem`'s `refuseOverwrite`),\n// so observing the predicate N times writes the successor exactly once —\n// the successor record's own existence is the \"already spawned?\" flag.\n// No stored side-state, so fs.watch coalescing / boot re-reads / the\n// wall-clock tick can all re-run this freely.\n//\n// All date math operates on the civil (year, month, day) triple — never\n// by adding milliseconds — so month lengths and leap years are handled\n// correctly. The day-of-month anchor is read from the RULE, never\n// re-derived from the prior concrete date, so \"31st of every month\"\n// never drifts (it clamps per-month at compute time, not stored\n// clamped). See `advanceTriggerDate`.\n\nimport { log } from \"./host\";\nimport { errorMessage, ONE_DAY_MS } from \"./util\";\nimport type { IoOptions } from \"./io\";\nimport { storeFor } from \"./store\";\nimport type { LoadedCollection } from \"./discoveredCollection\";\nimport { isFieldDrivenEvery } from \"../core/schema\";\nimport { itemIsDone } from \"../core/completion\";\nimport type { CollectionEvery, CollectionItem, CollectionSchema, CollectionSpawnEvery, CollectionWhen } from \"../core/schema\";\n\n/** A timezone-free calendar date. `m` is 1-12. */\nexport interface CivilDate {\n y: number;\n m: number;\n d: number;\n}\n\nconst YMD_PATTERN = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\n\nfunction pad2(value: number): string {\n return String(value).padStart(2, \"0\");\n}\n\nfunction pad4(value: number): string {\n return String(value).padStart(4, \"0\");\n}\n\n/** Days in `month` (1-12) of `year`, leap-year-aware. `new Date(y, m, 0)`\n * is day 0 of the *next* month = the last day of `month`; `.getDate()`\n * reads the civil day regardless of timezone. */\nexport function daysInMonth(year: number, month: number): number {\n return new Date(year, month, 0).getDate();\n}\n\n/** Parse a `YYYY-MM-DD` string into a CivilDate, or null when the value\n * isn't a well-formed in-range calendar date. */\nexport function parseCivil(raw: unknown): CivilDate | null {\n if (typeof raw !== \"string\") return null;\n const match = YMD_PATTERN.exec(raw.trim());\n if (!match) return null;\n const year = Number(match[1]);\n const month = Number(match[2]);\n const day = Number(match[3]);\n if (month < 1 || month > 12) return null;\n if (day < 1 || day > daysInMonth(year, month)) return null;\n return { y: year, m: month, d: day };\n}\n\n/** `YYYY-MM-DD` for storage in a `date` field. */\nexport function formatCivil(date: CivilDate): string {\n return `${pad4(date.y)}-${pad2(date.m)}-${pad2(date.d)}`;\n}\n\n/** A monotonic integer key for a civil date (YYYYMMDD), for ordering /\n * equality without timezone concerns. */\nfunction ordinal(date: CivilDate): number {\n return date.y * 10000 + date.m * 100 + date.d;\n}\n\n/** Add `n` whole days to a civil date. Uses UTC epoch arithmetic so it\n * is DST-immune (we only ever read back the civil Y/M/D). */\nfunction addDays(date: CivilDate, days: number): CivilDate {\n const shifted = new Date(Date.UTC(date.y, date.m - 1, date.d) + days * ONE_DAY_MS);\n return { y: shifted.getUTCFullYear(), m: shifted.getUTCMonth() + 1, d: shifted.getUTCDate() };\n}\n\n/** Advance a civil date by one `every` step. Month/year units preserve\n * the rule's day-of-month anchor, clamped to the target month's length\n * (no drift); day/week units do civil day arithmetic. */\nexport function advanceTriggerDate(source: CivilDate, every: CollectionEvery): CivilDate {\n const { unit, interval } = every;\n if (unit === \"day\") return addDays(source, interval);\n if (unit === \"week\") return addDays(source, interval * 7);\n // month / year\n const monthsToAdd = interval * (unit === \"year\" ? 12 : 1);\n const total = source.y * 12 + (source.m - 1) + monthsToAdd;\n const nextYear = Math.floor(total / 12);\n const nextMonth = (total % 12) + 1;\n const dim = daysInMonth(nextYear, nextMonth);\n const anchor = every.dayOfMonth === \"last\" ? dim : (every.dayOfMonth ?? source.d);\n return { y: nextYear, m: nextMonth, d: Math.min(anchor, dim) };\n}\n\n/** True iff `now`'s civil date (local timezone) has reached the fire date\n * for `triggerRaw` — i.e. the trigger date minus `leadDays` (so a 10-day\n * lead fires 10 days early). Returns null when `triggerRaw` isn't a\n * parseable date — callers treat that as \"don't fire\" and warn. */\nexport function isTriggerDue(triggerRaw: unknown, now: Date, leadDays = 0): boolean | null {\n const due = parseCivil(triggerRaw);\n if (due === null) return null;\n const fireDate = leadDays > 0 ? addDays(due, -leadDays) : due;\n const today: CivilDate = { y: now.getFullYear(), m: now.getMonth() + 1, d: now.getDate() };\n return ordinal(today) >= ordinal(fireDate);\n}\n\nconst DATE_SUFFIX_PATTERN = /-\\d{8}$/;\n\n/** Deterministic successor id: `<stem>-<YYYYMMDD>`, where `<stem>` is the\n * source id with a trailing `-YYYYMMDD` stripped if present. So a chain\n * shares one stem and each instance is dated:\n * `rent` → `rent-20260610`\n * `rent-20260610` → `rent-20260710`\n * Slug-safe (alphanumeric + hyphen) and a pure function of the inputs,\n * which is what makes create-if-absent idempotent. */\nexport function successorId(sourceId: string, next: CivilDate): string {\n const stem = sourceId.replace(DATE_SUFFIX_PATTERN, \"\");\n return `${stem}-${pad4(next.y)}${pad2(next.m)}${pad2(next.d)}`;\n}\n\n/** True iff `item` satisfies the spawn predicate. With an explicit\n * `when`, matches `String(item[when.field]) ∈ when.in`. Without one,\n * defaults to the completion-done condition — the shared, flag-aware\n * `itemIsDone` (core/completion; downhill import, so the module graph\n * stays acyclic). */\nfunction matchesWhen(when: CollectionWhen | undefined, schema: CollectionSchema, item: CollectionItem): boolean {\n if (when) {\n const raw = item[when.field];\n return raw !== undefined && raw !== null && when.in.includes(String(raw));\n }\n return itemIsDone(schema, item);\n}\n\n/** Resolve the literal `every` that applies to `sourceItem`. Literal-arm\n * `spawn.every` passes through unchanged. Field-driven `spawn.every` reads\n * `sourceItem[fromField]` and looks it up in `map`; an empty field or a\n * value with no map entry yields null (caller skips + logs — see plan §5).\n * Discovery rejects a map that doesn't cover the enum's values, so null\n * here means a record that predates a map/enum edit. */\nexport function resolveEvery(every: CollectionSpawnEvery, sourceItem: CollectionItem): CollectionEvery | null {\n if (!isFieldDrivenEvery(every)) return every;\n const raw = sourceItem[every.fromField];\n if (raw === undefined || raw === null || raw === \"\") return null;\n return every.map[String(raw)] ?? null;\n}\n\nexport interface ComputedSuccessor {\n id: string;\n record: CollectionItem;\n}\n\n/** Build the successor record purely from (schema, source record, source\n * id). Returns null when the schema has no spawn/triggerField or the\n * source's trigger date is unparseable. */\nexport function computeSuccessor(schema: CollectionSchema, sourceItem: CollectionItem, sourceId: string): ComputedSuccessor | null {\n const { spawn, triggerField } = schema;\n if (!spawn || !triggerField) return null;\n const srcDate = parseCivil(sourceItem[triggerField]);\n if (srcDate === null) return null;\n const every = resolveEvery(spawn.every, sourceItem);\n if (every === null) return null;\n const next = advanceTriggerDate(srcDate, every);\n const nextId = successorId(sourceId, next);\n const record: CollectionItem = {};\n for (const field of spawn.carry ?? []) {\n if (Object.prototype.hasOwnProperty.call(sourceItem, field)) record[field] = sourceItem[field];\n }\n Object.assign(record, spawn.set ?? {});\n record[triggerField] = formatCivil(next);\n record[schema.primaryKey] = nextId;\n return { id: nextId, record };\n}\n\n/** Warn precisely about which of `computeSuccessor`'s two null causes fired\n * (plan §5): an unparseable source trigger date (the original cause), or a\n * field-driven `every` whose record value has no `map` entry (a record that\n * predates a map/enum edit — discovery rejects this statically otherwise). */\nfunction logSpawnSkip(slug: string, triggerField: string, every: CollectionSpawnEvery, sourceItem: CollectionItem, sourceId: string): void {\n if (parseCivil(sourceItem[triggerField]) === null) {\n log.warn(\"collections\", \"spawn skipped: source trigger date unparseable\", { slug, sourceId, triggerField });\n return;\n }\n const fromField = isFieldDrivenEvery(every) ? every.fromField : undefined;\n log.warn(\"collections\", \"spawn skipped: no `every` mapping for frequency value\", {\n slug,\n sourceId,\n fromField,\n value: fromField === undefined ? undefined : sourceItem[fromField],\n });\n}\n\n/** Idempotently create the successor for `sourceItem` when it matches the\n * spawn predicate. No-op when the schema declares no spawn, the\n * predicate doesn't match, the trigger date is unparseable, or the\n * successor already exists (create-if-absent). Never overwrites an\n * existing successor — protects any edits the user made to it. */\nexport async function maybeSpawnSuccessor(collection: LoadedCollection, sourceItem: CollectionItem, sourceId: string, ioOpts: IoOptions = {}): Promise<void> {\n const { slug, schema } = collection;\n const { spawn } = schema;\n if (!spawn || !schema.triggerField) return;\n if (!matchesWhen(spawn.when, schema, sourceItem)) return;\n const computed = computeSuccessor(schema, sourceItem, sourceId);\n if (computed === null) {\n logSpawnSkip(slug, schema.triggerField, spawn.every, sourceItem, sourceId);\n return;\n }\n // Runaway guard: a successor born already matching its own `when` would\n // re-spawn on its first reconcile (and so on) — an unbounded chain. A\n // well-formed schema sets the successor to a non-matching state (e.g.\n // `set: { status: \"pending\" }`); refuse + warn if it doesn't. Discovery\n // also rejects this statically, so this is belt-and-suspenders.\n if (matchesWhen(spawn.when, schema, computed.record)) {\n log.warn(\"collections\", \"spawn skipped: successor would be born matching its own predicate (unbounded respawn)\", {\n slug,\n sourceId,\n successorId: computed.id,\n });\n return;\n }\n try {\n // Store-mediated write: works for any writable backend (file, sqlite).\n // A read-only store can't spawn — schema validation forbids `spawn` on\n // dataSource collections, so an absent `write` is defense in depth.\n const { write } = storeFor(collection, ioOpts);\n if (!write) {\n log.warn(\"collections\", \"spawn skipped: collection store is read-only\", { slug, sourceId });\n return;\n }\n const result = await write(computed.id, computed.record, { refuseOverwrite: true });\n if (result.kind === \"ok\") {\n log.info(\"collections\", \"spawned successor\", { slug, sourceId, successorId: computed.id });\n } else if (result.kind !== \"conflict\") {\n // \"conflict\" = successor already exists = idempotent no-op (expected).\n log.warn(\"collections\", \"spawn write failed\", { slug, sourceId, successorId: computed.id, kind: result.kind });\n }\n } catch (err) {\n log.warn(\"collections\", \"spawn write threw\", { slug, sourceId, successorId: computed.id, error: errorMessage(err) });\n }\n}\n","// Delete a user-authored collection, archiving a full restorable copy\n// first. A collection spans three on-disk locations (see\n// docs/papers/collections-architecture.md \"Deleting a collection\"):\n//\n// 1. data/skills/<slug>/ staging — the canonical skill source\n// 2. .claude/skills/<slug>/ active mirror — what discovery scans\n// 3. <schema.dataPath>/ the records (one <id>.json per record)\n//\n// Locations 1 and 2 are a source→mirror pair maintained by the\n// skill-bridge hook, but that hook only fires on the agent's own tool\n// calls — a server-side delete must remove BOTH explicitly. Before\n// anything is removed we write a single skill copy (from the canonical\n// staging dir), the records, and an LLM-runnable RESTORE.md to\n// `archive/<date>-<uuid>/`.\n//\n// Only project-scope, non-preset collections are deletable: user-scope\n// skills (`~/.claude/skills/`) are read-only from MulmoClaude, and a\n// preset (`mc-*`) re-seeds on next boot so deleting it is futile.\n\nimport { cp, mkdir, rm, rmdir, stat, writeFile } from \"node:fs/promises\";\nimport { randomUUID } from \"node:crypto\";\nimport path from \"node:path\";\nimport { log, getWorkspaceRoot, isPresetSlug, skillsStagingDir, archiveDir as archiveRelDir } from \"./host\";\nimport { isContainedInRoot } from \"./paths\";\nimport { checkpointSqliteDatabase } from \"./sqliteStore\";\nimport type { LoadedCollection } from \"./discoveredCollection\";\n\nexport type DeleteCollectionResult =\n | { kind: \"ok\"; slug: string; archivePath: string }\n | { kind: \"user-scope\"; slug: string }\n | { kind: \"preset\"; slug: string }\n | { kind: \"unsafe-data-path\"; slug: string }\n | { kind: \"path-escape\"; slug: string };\n\ntype DeleteRefusal = Exclude<DeleteCollectionResult, { kind: \"ok\" }>;\n\n/** Human-readable reason for a non-`ok` delete result. Exported so the\n * route maps `kind` → message without inlining the switch (keeps the\n * handler short and the mapping unit-testable). The `Record` is\n * exhaustive — a new refusal kind won't compile until it's added. */\nexport function deleteCollectionRefusalMessage(result: DeleteRefusal): string {\n const { slug } = result;\n const messages: Record<DeleteRefusal[\"kind\"], string> = {\n \"user-scope\": `collection '${slug}' is user-scope (~/.claude/skills/) and is read-only from MulmoClaude`,\n preset: `collection '${slug}' is a preset (mc-*) and re-seeds on restart; unstar it from the catalog instead`,\n \"unsafe-data-path\": `collection '${slug}' declares a dataPath outside its own data/${slug}/ subtree; refusing to delete`,\n \"path-escape\": `a directory for collection '${slug}' escapes the workspace`,\n };\n return messages[result.kind];\n}\n\nexport interface DeleteCollectionOptions {\n /** Override the workspace root for containment checks + archive\n * placement. Default: the live `workspacePath`. Tests point this at\n * a `mkdtempSync` tree (same pattern as the IO helpers). */\n workspaceRoot?: string;\n /** Override the `<date>` half of the archive folder name. Tests pass\n * a fixed stamp so the asserted path is deterministic; production\n * leaves it unset and the current UTC date (YYYY-MM-DD) is used. */\n dateStamp?: string;\n}\n\n/** The canonical staging dir for a slug: `data/skills/<slug>`. */\nfunction stagingSkillDir(workspaceRoot: string, slug: string): string {\n return path.join(skillsStagingDir(workspaceRoot), slug);\n}\n\nasync function pathExists(target: string): Promise<boolean> {\n try {\n await stat(target);\n return true;\n } catch {\n return false;\n }\n}\n\n/** UTC `YYYY-MM-DD` — keeps the archive folder human-sortable. */\nfunction todayStamp(): string {\n return new Date().toISOString().slice(0, 10);\n}\n\n/** Every directory the delete will touch must resolve under the\n * workspace root — guards against a symlinked ancestor escaping it. */\nfunction deleteTargets(collection: LoadedCollection, workspaceRoot: string): string[] {\n return [\n stagingSkillDir(workspaceRoot, collection.slug),\n collection.skillDir,\n collection.dataDir,\n ...(collection.storageFile !== undefined ? [collection.storageFile] : []),\n ];\n}\n\n/** The records directory the delete recursively archives + removes\n * (`collection.dataDir`) must live in this collection's OWN per-slug\n * subtree. `dataDir` is normally derived from `schema.dataPath`, but\n * `deleteCollection` accepts a `LoadedCollection` whose fields could be\n * inconsistent — so we validate the RESOLVED target the destructive ops\n * actually touch, not the schema string. A shared root like `data` or\n * `data/skills` would otherwise turn the recursive removal into a\n * workspace-wide wipe whose archive captures only this collection.\n * `path.resolve` collapses any `..` before the prefix test (symlink\n * escapes are caught separately by the realpath containment check in\n * `deleteTargets`).\n *\n * TWO acceptable per-slug subtrees:\n * - `data/<slug>/...` — the convention authored collections default to.\n * - `data/collections/<slug>/...` — what `normalizedDataPath` in the\n * registry-import path stamps onto every imported schema (so imported\n * collections never collide with `data/<slug>/`-shaped authored ones).\n * Both are equally per-collection and equally safe; a delete that targets\n * anything else (e.g. raw `data/`, `data/wiki/`, a sibling slug) is\n * refused. */\nfunction isDataDirSafe(dataDir: string, slug: string, workspaceRoot: string): boolean {\n const acceptableRoots = [path.resolve(workspaceRoot, \"data\", slug), path.resolve(workspaceRoot, \"data\", \"collections\", slug)];\n const resolved = path.resolve(dataDir);\n return acceptableRoots.some((root) => resolved === root || resolved.startsWith(root + path.sep));\n}\n\n/** Step 2 of the restore doc — how to bring the records back. A\n * `dataSource` collection has no record files to copy (its rows live in\n * the external data file, which the delete never touches). */\nfunction restoreRecordsStep(schema: LoadedCollection[\"schema\"]): string {\n if (schema.storage !== undefined) {\n return `2. Records: copy the archived database file\n \\`${path.basename(schema.storage.path)}\\` (next to this document) back to\n \\`${schema.storage.path}\\` (workspace-relative, \\`cp\\`). It holds every\n record. If \\`-wal\\`/\\`-journal\\` sidecar files were archived alongside\n it, copy them back too.`;\n }\n if (schema.dataPath === undefined) {\n return `2. Records: nothing to copy. This is a \\`dataSource\\` collection —\n its records are the rows of \\`${schema.dataSource?.path}\\`, which the\n delete never touched.`;\n }\n return `2. Copy the item data: \\`cp\\` every file under \\`records/\\` into\n \\`${schema.dataPath}/\\`. The records are part of the collection and\n must be restored. They are plain data files (NOT bridged), so use\n \\`cp\\` — the Write-tool rule in step 1 applies ONLY to the skill\n files, not to these records (there may be many; copy them, do not\n Write them one by one).`;\n}\n\nfunction buildRestoreDoc(collection: LoadedCollection): string {\n const { slug, schema } = collection;\n return `# Restore \"${schema.title}\" (collection \\`${slug}\\`)\n\nThis folder is an automatic backup made when the collection was deleted.\nFollow these steps to restore it.\n\n1. Recreate the skill files in \\`data/skills/${slug}/\\` using the **Write\n tool**: read each file under \\`skill/\\` and Write it to the matching\n path — \\`SKILL.md\\`, \\`schema.json\\`, and any \\`templates/*\\`.\n\n IMPORTANT — use the Write tool, NOT \\`cp\\` / \\`mv\\` / a shell redirect.\n The skill-bridge hook mirrors \\`data/skills/${slug}/\\` into\n \\`.claude/skills/${slug}/\\`, and that mirror is what actually registers\n the collection. The hook only fires on Write/Edit tool calls, so a\n \\`cp\\` would leave the files in staging with no \\`.claude/skills/\\`\n mirror — the collection would stay invisible. (Writing\n \\`.claude/skills/\\` directly is not an option either: that path is\n permission-gated.)\n\n${restoreRecordsStep(schema)}\n\n3. Confirm the collection reappears at \\`/collections/${slug}\\`.\n\n- slug: \\`${slug}\\`\n- title: ${schema.title}\n- dataPath: \\`${schema.dataPath ?? (schema.storage !== undefined ? `(storage) ${schema.storage.path}` : `(dataSource) ${schema.dataSource?.path}`)}\\`\n`;\n}\n\n/** Copy one skill copy + the records + RESTORE.md into `archiveDir`. */\nasync function writeArchive(collection: LoadedCollection, archiveDir: string, workspaceRoot: string): Promise<void> {\n // Prefer the canonical staging dir; fall back to the active mirror\n // for a project collection that was created without the bridge.\n const staging = stagingSkillDir(workspaceRoot, collection.slug);\n const skillSrc = (await pathExists(staging)) ? staging : collection.skillDir;\n await cp(skillSrc, path.join(archiveDir, \"skill\"), { recursive: true });\n if (await pathExists(collection.dataDir)) {\n await cp(collection.dataDir, path.join(archiveDir, \"records\"), { recursive: true });\n }\n // A `storage` collection's records live in its database file (the\n // dataDir is a phantom) — archive it under its own basename so RESTORE\n // can point `storage.path` back at it. Checkpoint first so the main file\n // alone is a complete snapshot (in WAL mode, committed rows can live\n // only in `<db>-wal`); if the checkpoint fails (older Node, locked db),\n // archive the sidecars too — either way no committed data is lost.\n // (`dataSourceFile` is deliberately NOT archived or removed: an external\n // dataSource file is user-owned.)\n if (collection.storageFile !== undefined && (await pathExists(collection.storageFile))) {\n const checkpointed = await checkpointSqliteDatabase(collection.storageFile);\n await cp(collection.storageFile, path.join(archiveDir, path.basename(collection.storageFile)));\n if (!checkpointed) {\n for (const suffix of [\"-wal\", \"-journal\", \"-shm\"]) {\n const sidecar = `${collection.storageFile}${suffix}`;\n if (await pathExists(sidecar)) await cp(sidecar, path.join(archiveDir, path.basename(sidecar)));\n }\n }\n }\n await writeFile(path.join(archiveDir, \"RESTORE.md\"), buildRestoreDoc(collection), \"utf-8\");\n}\n\n/** Remove all three locations. `rm -rf`-style (force) so a missing dir\n * is a no-op; the now-empty data parent (`data/<slug>/` after its\n * `items/` is gone) is swept too, but only when empty. */\nasync function removeLocations(collection: LoadedCollection, workspaceRoot: string): Promise<void> {\n await rm(stagingSkillDir(workspaceRoot, collection.slug), { recursive: true, force: true });\n await rm(collection.skillDir, { recursive: true, force: true });\n await rm(collection.dataDir, { recursive: true, force: true });\n await rmdir(path.dirname(collection.dataDir)).catch(() => undefined);\n // The archived copy above is the backup; remove the live db (+ sqlite\n // sidecars) so a deleted collection doesn't leave orphaned data behind.\n if (collection.storageFile !== undefined) {\n await rm(collection.storageFile, { force: true });\n await rm(`${collection.storageFile}-wal`, { force: true });\n await rm(`${collection.storageFile}-journal`, { force: true });\n await rm(`${collection.storageFile}-shm`, { force: true });\n }\n}\n\nexport async function deleteCollection(collection: LoadedCollection, opts: DeleteCollectionOptions = {}): Promise<DeleteCollectionResult> {\n const { slug } = collection;\n const workspaceRoot = opts.workspaceRoot ?? getWorkspaceRoot();\n if (collection.source === \"user\") return { kind: \"user-scope\", slug };\n if (isPresetSlug(slug)) return { kind: \"preset\", slug };\n if (!isDataDirSafe(collection.dataDir, slug, workspaceRoot)) {\n log.warn(\"collections\", \"deleteCollection refused: dataDir is not under the per-collection root\", { slug, dataDir: collection.dataDir });\n return { kind: \"unsafe-data-path\", slug };\n }\n if (deleteTargets(collection, workspaceRoot).some((target) => !isContainedInRoot(target, workspaceRoot))) {\n log.warn(\"collections\", \"deleteCollection refused: a target escapes the workspace\", { slug });\n return { kind: \"path-escape\", slug };\n }\n const archiveRel = path.join(archiveRelDir(), `${opts.dateStamp ?? todayStamp()}-${randomUUID()}`);\n const archiveDir = path.join(workspaceRoot, archiveRel);\n await mkdir(archiveDir, { recursive: true });\n await writeArchive(collection, archiveDir, workspaceRoot);\n await removeLocations(collection, workspaceRoot);\n log.info(\"collections\", \"collection deleted + archived\", { slug, archive: archiveRel });\n return { kind: \"ok\", slug, archivePath: archiveRel };\n}\n","// Server-side custom-view management: removing a collection's custom view.\n//\n// A custom view spans two on-disk facts that must be removed together:\n// 1. an entry in the collection's schema.json `views[]` array\n// 2. its HTML file at `<base>/views/<file>` (the entry's `file` field)\n//\n// The base dir is source-aware, mirroring `readCustomViewHtml`. A PROJECT\n// collection authored in-place keeps schema + view HTML in the staging tree\n// (`data/skills/<slug>/`) AND mirrors schema into the active dir\n// (`.claude/skills/<slug>/`, i.e. `collection.skillDir`). A PROJECT collection\n// IMPORTED via the discover panel (rename-on-conflict) lives entirely under\n// the active dir — no staging mirror is created. We pick the canonical base\n// (and the schema-write set) by *what's actually on disk*, so both layouts\n// delete cleanly without ENOENT. The skill-bridge hook that normally keeps\n// staging+active in sync only fires on the agent's own tool calls, never from\n// an API route — exactly as `deleteCollection` reasons. A FEED / USER\n// collection is a single tree at `collection.skillDir`.\n//\n// Custom-view HTML is staging-only for project collections (never mirrored —\n// rendering is host-side), so only the canonical base's copy is unlinked.\n\nimport { readFile, stat, unlink } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { writeFileAtomic } from \"./atomic\";\nimport { getWorkspaceRoot, isPresetSlug, skillsStagingDir } from \"./host\";\nimport { resolveTemplatePath, safeSlugName, SCHEMA_FILE } from \"./paths\";\nimport type { IoOptions } from \"./io\";\nimport type { LoadedCollection } from \"./discoveredCollection\";\n\nexport type DeleteViewResult =\n | { kind: \"ok\"; viewId: string }\n | { kind: \"not-found\"; viewId: string }\n | { kind: \"user-scope\" }\n | { kind: \"preset\" }\n | { kind: \"unsafe-path\"; viewId: string };\n\nasync function fileExists(target: string): Promise<boolean> {\n try {\n await stat(target);\n return true;\n } catch (err) {\n const { code } = err as { code?: string };\n if (code === \"ENOENT\" || code === \"ENOTDIR\") return false;\n throw err;\n }\n}\n\n/** The authoritative base dir for a collection's schema.json + view HTML.\n * For a project collection, prefer the staging tree when its schema.json is\n * present (authoring layout); otherwise fall back to the active skill dir\n * (imported layout — staging never materialised). For feed / user, it's\n * always the discovered skillDir. Matches `readCustomViewHtml` so reads and\n * deletes agree on both layouts. */\nasync function canonicalBase(collection: Pick<LoadedCollection, \"source\" | \"skillDir\">, workspaceRoot: string, safeSlug: string): Promise<string> {\n if (collection.source !== \"project\") return collection.skillDir;\n const staging = path.join(skillsStagingDir(workspaceRoot), safeSlug);\n if (await fileExists(path.join(staging, SCHEMA_FILE))) return staging;\n return collection.skillDir;\n}\n\n/** Every on-disk schema.json that must reflect the removal. The active\n * `<skillDir>/schema.json` is the discovery anchor and is always present.\n * The staging copy is included only when it actually exists, so an imported\n * project collection (no staging mirror) doesn't have an empty staging tree\n * materialised by a side effect of the delete. */\nasync function schemaWriteTargets(collection: Pick<LoadedCollection, \"source\" | \"skillDir\">, workspaceRoot: string, safeSlug: string): Promise<string[]> {\n const active = path.join(collection.skillDir, SCHEMA_FILE);\n if (collection.source !== \"project\") return [active];\n const stagingSchema = path.join(skillsStagingDir(workspaceRoot), safeSlug, SCHEMA_FILE);\n const targets: string[] = [];\n if (await fileExists(stagingSchema)) targets.push(stagingSchema);\n targets.push(active);\n return targets;\n}\n\n/** Idempotent unlink — a missing file is fine (the schema entry still gets\n * cleaned), but a real error (permissions, etc.) propagates. */\nasync function unlinkIfPresent(target: string): Promise<void> {\n try {\n await unlink(target);\n } catch (err) {\n if ((err as { code?: string }).code !== \"ENOENT\") throw err;\n }\n}\n\n/** Re-read the canonical schema.json, drop the `views[]` entry, and write the\n * result back to every on-disk copy so staging + active stay identical. Reads\n * raw (not `collection.schema`) so fields the typed schema doesn't model are\n * preserved verbatim. */\nasync function removeViewFromSchemas(collection: LoadedCollection, viewId: string, workspaceRoot: string, safeSlug: string): Promise<void> {\n const base = await canonicalBase(collection, workspaceRoot, safeSlug);\n const canonical = path.join(base, SCHEMA_FILE);\n const parsed = JSON.parse(await readFile(canonical, \"utf-8\")) as { views?: { id?: unknown }[] };\n if (Array.isArray(parsed.views)) parsed.views = parsed.views.filter((entry) => entry?.id !== viewId);\n const serialized = `${JSON.stringify(parsed, null, 2)}\\n`;\n for (const target of await schemaWriteTargets(collection, workspaceRoot, safeSlug)) {\n await writeFileAtomic(target, serialized);\n }\n}\n\n/** Delete one custom view from `collection`: unlink its HTML file and drop it\n * from every schema.json copy. User-scope and preset (mc-*) collections are\n * refused (read-only / re-seeded on boot), consistent with `deleteCollection`. */\nexport async function deleteCustomView(collection: LoadedCollection, viewId: string, opts: IoOptions = {}): Promise<DeleteViewResult> {\n if (collection.source === \"user\") return { kind: \"user-scope\" };\n if (isPresetSlug(collection.slug)) return { kind: \"preset\" };\n const safeSlug = safeSlugName(collection.slug);\n if (safeSlug === null) return { kind: \"unsafe-path\", viewId };\n const views = collection.schema.views ?? [];\n const view = views.find((entry) => entry.id === viewId);\n if (!view) return { kind: \"not-found\", viewId };\n const workspaceRoot = opts.workspaceRoot ?? getWorkspaceRoot();\n const htmlPath = resolveTemplatePath(await canonicalBase(collection, workspaceRoot, safeSlug), view.file);\n if (htmlPath === null) return { kind: \"unsafe-path\", viewId };\n // Rewrite the schema BEFORE unlinking: if the write fails the request errors\n // out, but the HTML stays put and the still-registered view keeps working —\n // an orphaned `views[]` entry pointing at a deleted file would 404 forever.\n await removeViewFromSchemas(collection, viewId, workspaceRoot, safeSlug);\n // Distinct ids may point at the same `file` (unique ids are enforced, unique\n // files are not), so only unlink when no remaining view still references it.\n const stillReferenced = views.some((entry) => entry.id !== viewId && entry.file === view.file);\n if (!stillReferenced) await unlinkIfPresent(htmlPath);\n return { kind: \"ok\", viewId };\n}\n","// Preset-skill namespace — the ONE definition of \"is this a launcher-managed preset\n// skill\". Browser-safe (no node imports) so the Vue UI can import it via the package's\n// `./slug` entry while the server-only sync logic (`.` entry, uses node:fs) imports it\n// here too. Shared by MulmoClaude and MulmoTerminal so the `mc-` convention can't drift.\n\n/** Launcher preset namespace. Preset skills (and only those) are `mc-*`. */\nexport const PRESET_SLUG_PREFIX = \"mc-\";\n\n/** True for a launcher-managed preset slug (`mc-<something>`). The sync logic uses\n * this to bound which active skills it may refresh/prune, so user-authored skills are\n * never touched. */\nexport function isPresetSlug(slug: string): boolean {\n return slug.startsWith(PRESET_SLUG_PREFIX) && slug.length > PRESET_SLUG_PREFIX.length;\n}\n","// `manageCollection` is the agent's data plane for schema-driven\n// collections — the paved road over the same record files that raw\n// Read/Write/Edit reach (the workspace stays the database; this tool is\n// a convenience + gate, not a second store):\n//\n// - getItems: records WITH the host-computed fields the stored JSON\n// never contains — `derived` formulas evaluated (cross-collection\n// derefs included), `toggle` projected, `embed` resolved — i.e. the\n// same numbers the user sees rendered. One call instead of N file\n// Reads plus a mental join.\n// - putItems: rows validated against the schema BEFORE the write\n// (primaryKey↔id, required fields, enum membership, no computed\n// keys), written atomically, with per-row accept/reject results the\n// model can fix and retry — instead of writing a broken file and\n// meeting it later in the presentCollection repair loop.\n// - deleteItems: removal by id, so a collection whose records aren't\n// files (or whose caller shouldn't be handed raw unlink) still has a\n// delete. A missing id rejects rather than reporting success.\n// - getOntology: the machine-readable workspace ontology — every\n// collection with its record count and outbound ref/embed relations,\n// so a cross-collection question starts from the map instead of\n// re-reading every schema.\n//\n// It is also the paved road for a collection's STRUCTURE — so an edit\n// gets the same authoring reference + validation a create does:\n//\n// - schemaDocs: the collection-authoring reference (`collection-skills.md`)\n// delivered as a method, so the agent never needs to know the help\n// file's path or that it exists — the gap that made schema EDITS fail\n// (create-time prompts point at the doc; edit-time had no pointer).\n// - getSchema / putSchema: read the raw schema.json, and validate it\n// against `CollectionSchemaZ` BEFORE writing the canonical staging\n// copy + mirroring it active (an internal write skips the skill-bridge\n// hook, so the mirror is explicit). Edit-only; creation stays the\n// normal \"write SKILL.md + schema.json under data/skills/\" flow.\n//\n// Shared by both hosts (MulmoClaude's mcp-tools shim + MulmoTerminal):\n// everything host-specific rides in `ManageCollectionDeps` — the\n// workspace root falls back to the configured collection host\n// (`getWorkspaceRoot`), the post-putSchema UI refresh and the\n// evaluation-only validation ablation are injected, and tests point the\n// whole tool at a tmpdir workspace via the same deps.\n\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { COMPUTED_TYPES } from \"../core/schema\";\nimport type { CollectionItem, CollectionSchema } from \"../core/schema\";\nimport { CollectionSchemaZ } from \"../core/schemaZ\";\nimport { CollectionQueryZ } from \"../core/queryZ\";\nimport { defangForPrompt } from \"../core/promptSafety\";\nimport { loadCollection, type DiscoveryOptions } from \"./discovery\";\nimport type { LoadedCollection } from \"./discoveredCollection\";\nimport { resolveCreateItemId } from \"./io\";\nimport { readOnlyRefusal, storeFor, type CollectionStore } from \"./store\";\nimport { runCollectionQuery } from \"./queryRunner\";\nimport { enrichItems } from \"./derive\";\nimport { validateCollectionRecords, validateRecordObject } from \"./validate\";\nimport { buildWorkspaceOntology } from \"./ontology\";\nimport { resolveDataDir } from \"./paths\";\nimport { getWorkspaceRoot } from \"./host\";\nimport { writeFileAtomic } from \"./atomic\";\nimport { dataSkillDir, mirrorSkillWrite } from \"../../skill-bridge/index.js\";\n// NOTE: only the browser-safe `slug` module — workspace-setup's assets.ts uses\n// `import.meta.url` and is ESM-only (build pass 2), while this entry builds\n// dual ESM+CJS. The bundled-docs dir is injected instead (`bundledHelpsDir`).\nimport { isPresetSlug } from \"../../workspace-setup/slug\";\n\n/** Refuse an unselective getItems beyond this many records — a silent\n * truncation would read as \"covered everything\", and an unbounded dump\n * of a large collection is a token bomb. `ids` or `fields` lifts it. */\nexport const MAX_UNSELECTIVE_ITEMS = 200;\n\n/** schema.json basename under a skill dir (canonical staging + active mirror). */\nconst SCHEMA_FILE = \"schema.json\";\n/** The collection-authoring reference, served by the `schemaDocs` action. */\nconst SCHEMA_DOCS_FILE = \"collection-skills.md\";\n/** Cap the rejected-schema issue list so a deeply-broken schema can't flood the result. */\nexport const MAX_SCHEMA_ISSUES = 20;\n/** The workspace help-docs dir both hosts seed (`@mulmoclaude/core/workspace-setup`\n * syncs the bundled assets here) — the user-editable copy schemaDocs prefers. */\nconst HELPS_DIR = \"config/helps\";\n\n/** Workspace-targeting overrides, threaded to every collections call.\n * Production: `{}` (the configured collection host's workspace).\n * Tests: a tmpdir + empty user skills dir. `refreshAfterWrite` is the\n * best-effort UI-refresh fired after a `putSchema` write — hosts with\n * schema-driven side state (MulmoClaude's scheduled skills / user\n * tasks) inject their refreshers; omitted, no refresh runs (discovery\n * re-reads schema.json on every call, so only a live UI update is\n * delayed, never the data). */\nexport type ManageCollectionDeps = DiscoveryOptions & {\n refreshAfterWrite?: () => Promise<void>;\n /** Evaluation-only: skip pre-write record validation in putItems and\n * the getItems record-issue scan. MulmoClaude's production singleton\n * binds this from its ablation env; leave unset everywhere else. */\n ablateValidation?: boolean;\n /** The host's bundled help-docs dir (workspace-setup's `helpsAssetDir()`)\n * — the `schemaDocs` fallback when the workspace has no `config/helps`\n * copy. Injected because that module is ESM-only (`import.meta.url`)\n * while this entry builds dual ESM+CJS. Omitted, only the workspace\n * copy is tried. */\n bundledHelpsDir?: () => string;\n};\n\n/** Resolve the workspace root the same way every collections call does:\n * the injected override (tests) or the configured collection host. */\nfunction resolveBase(deps: ManageCollectionDeps): string {\n return deps.workspaceRoot ?? getWorkspaceRoot();\n}\n\n/** Shared \"unknown collection\" message — its schema.json is missing or\n * failed validation, so discovery skipped it. */\nfunction unknownCollection(slug: string): string {\n return `manageCollection: unknown collection '${defangForPrompt(slug)}' — its schema.json is missing or failed validation.`;\n}\n\ninterface GetItemsArgs {\n slug: string;\n ids?: string[];\n fields?: string[];\n}\n\ntype PutMode = \"upsert\" | \"create\" | \"merge\";\n\ninterface PutItemsArgs {\n slug: string;\n items: CollectionItem[];\n mode: PutMode;\n}\n\nfunction optionalStringArray(value: unknown, name: string): { ok: true; value?: string[] } | { ok: false; error: string } {\n if (value === undefined) return { ok: true };\n if (!Array.isArray(value) || !value.every((entry) => typeof entry === \"string\" && entry.length > 0)) {\n return { ok: false, error: `manageCollection: \\`${name}\\` must be an array of non-empty strings when present.` };\n }\n return { ok: true, value: value as string[] };\n}\n\n/** Project a record down to the requested fields. The primary key is\n * always kept so every returned record stays addressable for a\n * follow-up getItems/putItems. */\nfunction projectFields(record: CollectionItem, fields: string[], primaryKey: string): CollectionItem {\n const keys = fields.includes(primaryKey) ? fields : [primaryKey, ...fields];\n return Object.fromEntries(keys.filter((key) => key in record).map((key) => [key, record[key]]));\n}\n\n/** The validation warning appended to a getItems result when stored\n * record files are malformed (they're silently skipped at read time,\n * so without this they'd just look missing). Issue strings quote\n * record-controlled text → defanged, mirroring the presentCollection\n * dispatch. The record VALUES in `items` ride verbatim, like a raw\n * file Read — only host-composed report strings are defanged. */\nasync function recordIssuesWarning(collection: LoadedCollection, deps: ManageCollectionDeps): Promise<string | undefined> {\n if (deps.ablateValidation) return undefined;\n const issues = await validateCollectionRecords(collection, { workspaceRoot: deps.workspaceRoot });\n if (issues.length === 0) return undefined;\n const lines = issues.map((issue) => `- ${defangForPrompt(issue.file)}: ${defangForPrompt(issue.problem)}`).join(\"\\n\");\n return `${issues.length} record file(s) have data problems and are missing from this result. Fix each (Read → correct → Write):\\n${lines}`;\n}\n\nasync function loadRequestedItems(\n collection: LoadedCollection,\n ids: string[] | undefined,\n deps: ManageCollectionDeps,\n): Promise<{ items: CollectionItem[]; missing: string[] }> {\n const store = storeFor(collection, { workspaceRoot: deps.workspaceRoot });\n if (!ids) return { items: await store.list(), missing: [] };\n const items: CollectionItem[] = [];\n const missing: string[] = [];\n for (const recordId of ids) {\n // The file store's read THROWS on a malformed record file (only ENOENT\n // is null) — for the tool that's a `missing` entry, not a failed call:\n // the warning scan that runs whenever something is missing then names\n // the broken file and how to fix it.\n const item = await store.read(recordId).catch(() => null);\n if (item) items.push(item);\n else missing.push(recordId);\n }\n return { items, missing };\n}\n\nasync function handleGetItems(collection: LoadedCollection, args: GetItemsArgs, deps: ManageCollectionDeps): Promise<string> {\n const { items, missing } = await loadRequestedItems(collection, args.ids, deps);\n if (!args.ids && !args.fields && items.length > MAX_UNSELECTIVE_ITEMS) {\n return `manageCollection: refused — '${collection.slug}' has ${items.length} records, over the unselective limit of ${MAX_UNSELECTIVE_ITEMS}. Pass \\`ids\\` for specific records or \\`fields\\` to project only the columns you need.`;\n }\n const enriched = await enrichItems(collection, items, deps);\n const projected = args.fields ? enriched.map((item) => projectFields(item, args.fields as string[], collection.schema.primaryKey)) : enriched;\n // The warning scan reads every record file, so don't pay it on a\n // selective read that found everything it asked for — only a full\n // listing (where a malformed file silently looks absent) or a missing\n // requested id (where the scan explains WHY it's missing) needs it.\n const warning = !args.ids || missing.length > 0 ? await recordIssuesWarning(collection, deps) : undefined;\n return JSON.stringify({\n collection: collection.slug,\n count: projected.length,\n items: projected,\n ...(missing.length > 0 ? { missing: missing.map((recordId) => defangForPrompt(recordId)) } : {}),\n ...(warning ? { warning } : {}),\n });\n}\n\n/** Reject writes that set host-computed keys, with a pointer at the\n * writable source of truth (the toggle's enum) where one exists. */\nfunction computedKeyProblem(record: CollectionItem, schema: CollectionSchema): string | null {\n for (const key of Object.keys(record)) {\n const spec = schema.fields[key];\n if (!spec || !COMPUTED_TYPES.has(spec.type)) continue;\n if (spec.type === \"toggle\" && spec.field) return `'${key}' is a toggle projection — write the enum field '${spec.field}' instead`;\n const kindLabel: Record<string, string> = { derived: \"derived\", embed: \"an embed\", backlinks: \"a backlinks view\", rollup: \"a rollup\" };\n return `'${key}' is ${kindLabel[spec.type] ?? \"computed\"} — computed by the host, remove it from the record`;\n }\n return null;\n}\n\ninterface RejectedRow {\n id: string;\n problem: string;\n}\n\n/** `mode: \"merge\"` resolves the row against the EXISTING record —\n * a partial row updates just the fields it carries, instead of a\n * whole-record upsert silently erasing the optional fields it omits\n * (an upsert of `{id, status}` would pass validation yet drop\n * `notes`/`lesson`/…). Merge is a partial UPDATE by definition, so a\n * missing record is a reject, not an implicit create — a merged-over-\n * nothing partial record is exactly the data shape this mode exists\n * to prevent.\n *\n * Computed keys found in the STORED record are stripped before the\n * merge: the caller's own row was already computed-key-rejected, but a\n * raw-written / legacy record can carry a stale `derived`/`embed`/\n * `toggle` value, and re-writing it would perpetuate a forged\n * host-computed value. A merge heals the record instead.\n *\n * the store read THROWS on a malformed stored file (only ENOENT is null) —\n * downgraded to a per-row rejection here, like loadRequestedItems'\n * `missing`, so one broken file can't abort the whole putItems batch. */\nasync function mergeWithExisting(\n collection: LoadedCollection,\n store: CollectionStore,\n record: CollectionItem,\n itemId: string,\n): Promise<CollectionItem | string> {\n let existing: CollectionItem | null;\n try {\n existing = await store.read(itemId);\n } catch {\n 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\"`;\n }\n if (!existing) return `'${itemId}' not found — mode \"merge\" updates an existing record; use \"upsert\" or \"create\" to add it`;\n const stored = Object.entries(existing).filter(([key]) => {\n const spec = collection.schema.fields[key];\n return !spec || !COMPUTED_TYPES.has(spec.type);\n });\n return { ...Object.fromEntries(stored), ...record };\n}\n\nasync function putOneItem(\n collection: LoadedCollection,\n store: CollectionStore,\n write: NonNullable<CollectionStore[\"write\"]>,\n record: CollectionItem,\n mode: PutMode,\n deps: ManageCollectionDeps,\n): Promise<{ written?: string; rejected?: RejectedRow }> {\n const { schema } = collection;\n const itemId = resolveCreateItemId(schema, record);\n const reject = (about: string, problem: string): { rejected: RejectedRow } => ({\n rejected: { id: defangForPrompt(about), problem: defangForPrompt(problem) },\n });\n if (itemId === null) return reject(\"(no id)\", `record has no '${schema.primaryKey}' value — set it (it doubles as the filename)`);\n const computed = computedKeyProblem(record, schema);\n if (computed) return reject(itemId, computed);\n let toWrite = record;\n if (mode === \"merge\") {\n const merged = await mergeWithExisting(collection, store, record, itemId);\n if (typeof merged === \"string\") return reject(itemId, merged);\n toWrite = merged;\n }\n if (!deps.ablateValidation) {\n const invalid = validateRecordObject(toWrite, itemId, schema);\n if (invalid) return reject(itemId, invalid);\n }\n const result = await write(itemId, toWrite, { refuseOverwrite: mode === \"create\" });\n if (result.kind === \"ok\") return { written: result.itemId };\n if (result.kind === \"invalid-id\")\n return reject(itemId, `'${itemId}' is not a valid record id (letters/digits at the ends; -, _, or . inside; no '..' or path characters)`);\n if (result.kind === \"conflict\") return reject(itemId, `'${itemId}' already exists — mode \"create\" refuses overwrite; use \"upsert\" to update it`);\n return reject(itemId, \"write refused: the collection's data dir escapes the workspace\");\n}\n\n/** Aggregation over a collection via the structured query DSL\n * (`core/queryZ.ts`) — the paved road for counts / sums / group-bys\n * that a row listing can't answer honestly. The engine choice (native\n * store query vs enrich-then-JSONL fallback) lives in\n * `runCollectionQuery`, shared with the desktop custom view's `/query`\n * route so the two surfaces can never drift. */\nasync function handleQueryItems(collection: LoadedCollection, queryArg: unknown, deps: ManageCollectionDeps): Promise<string> {\n const parsed = CollectionQueryZ.safeParse(queryArg);\n if (!parsed.success) {\n const lines = parsed.error.issues\n .slice(0, MAX_SCHEMA_ISSUES)\n .map((issue) => `- ${issue.path.map(String).join(\".\") || \"(root)\"}: ${defangForPrompt(issue.message)}`);\n return `manageCollection: \\`query\\` rejected — fix and retry:\\n${lines.join(\"\\n\")}`;\n }\n const rows = await runCollectionQuery(collection, parsed.data, { workspaceRoot: deps.workspaceRoot });\n return JSON.stringify({ collection: collection.slug, count: rows.length, rows });\n}\n\nasync function handlePutItems(collection: LoadedCollection, args: PutItemsArgs, deps: ManageCollectionDeps): Promise<string> {\n // Server-enforced read-only: a `dataSource` collection's rows live in\n // the external data file — point the agent at the real update path\n // instead of writing phantom record files. The store encodes this as an\n // absent `write` method.\n const store = storeFor(collection, { workspaceRoot: deps.workspaceRoot });\n const { write } = store;\n if (!write) {\n return `manageCollection: ${readOnlyRefusal(collection.slug)} (its records are the rows of '${collection.schema.dataSource?.path}'; edit that file to change the data).`;\n }\n const written: string[] = [];\n const rejected: RejectedRow[] = [];\n for (const record of args.items) {\n const outcome = await putOneItem(collection, store, write, record, args.mode, deps);\n if (outcome.written) written.push(outcome.written);\n if (outcome.rejected) rejected.push(outcome.rejected);\n }\n return JSON.stringify({ collection: collection.slug, written, rejected });\n}\n\n/** Delete records by id, through the store — so it works on any writable\n * backend, not just file records. Same read-only refusal as putItems (an\n * absent `delete` IS the refusal), and the same per-id result shape so a\n * partially-bad batch reports per id instead of failing whole. */\nasync function handleDeleteItems(collection: LoadedCollection, ids: string[], deps: ManageCollectionDeps): Promise<string> {\n const { delete: removeItem } = storeFor(collection, { workspaceRoot: deps.workspaceRoot });\n if (!removeItem) {\n return `manageCollection: ${readOnlyRefusal(collection.slug)} (its records are the rows of '${collection.schema.dataSource?.path}'; edit that file to change the data).`;\n }\n const deleted: string[] = [];\n const rejected: RejectedRow[] = [];\n for (const itemId of ids) {\n const outcome = await deleteOneItem(removeItem, itemId);\n if (outcome.deleted) deleted.push(outcome.deleted);\n if (outcome.rejected) rejected.push(outcome.rejected);\n }\n return JSON.stringify({ collection: collection.slug, deleted, rejected });\n}\n\n/** A missing id is a rejection, not a silent success: deleting by a typo'd\n * id would otherwise report \"done\" while the real record survives. */\nasync function deleteOneItem(removeItem: NonNullable<CollectionStore[\"delete\"]>, itemId: string): Promise<{ deleted?: string; rejected?: RejectedRow }> {\n const result = await removeItem(itemId);\n if (result.kind === \"ok\") return { deleted: result.itemId };\n const reject = (problem: string): { rejected: RejectedRow } => ({ rejected: { id: defangForPrompt(itemId), problem: defangForPrompt(problem) } });\n if (result.kind === \"invalid-id\")\n return reject(`'${itemId}' is not a valid record id (letters/digits at the ends; -, _, or . inside; no '..' or path characters)`);\n if (result.kind === \"not-found\") return reject(`'${itemId}' not found — nothing was deleted; confirm the id with getItems`);\n return reject(\"delete refused: the collection's data dir escapes the workspace\");\n}\n\nfunction parseDeleteIds(args: Record<string, unknown>): string[] | string {\n const { ids } = args;\n const valid = Array.isArray(ids) && ids.length > 0 && ids.every((entry) => typeof entry === \"string\" && entry.trim().length > 0);\n if (!valid) return \"manageCollection: `ids` is required for deleteItems — a non-empty array of record ids.\";\n return ids as string[];\n}\n\nfunction parsePutItems(args: Record<string, unknown>, slug: string): PutItemsArgs | string {\n const { items, mode } = args;\n const validItems = Array.isArray(items) && items.length > 0 && items.every((entry) => Boolean(entry) && typeof entry === \"object\" && !Array.isArray(entry));\n if (!validItems) return \"manageCollection: `items` is required for putItems — a non-empty array of record objects.\";\n if (mode !== undefined && mode !== \"upsert\" && mode !== \"create\" && mode !== \"merge\") {\n return 'manageCollection: `mode` must be \"upsert\" (default), \"create\", or \"merge\".';\n }\n return { slug, items: items as CollectionItem[], mode: (mode as PutItemsArgs[\"mode\"] | undefined) ?? \"upsert\" };\n}\n\n/** The machine-readable workspace ontology: every collection with its\n * identity, record count, and outbound `ref`/`embed` relations. Slugs\n * are discovery-sanitized; titles/labels are workspace-authored schema\n * text and ride verbatim — the same trust class as the record values\n * getItems returns. */\nasync function handleGetOntology(deps: ManageCollectionDeps): Promise<string> {\n const collections = await buildWorkspaceOntology(deps);\n return JSON.stringify({ count: collections.length, collections });\n}\n\n/** Return the collection-authoring reference (`collection-skills.md`).\n * Workspace copy first (reflects user edits), bundled asset as the\n * always-present fallback. Both reads guarded; if neither resolves the\n * agent still gets an actionable message instead of a thrown call. */\nasync function handleSchemaDocs(deps: ManageCollectionDeps): Promise<string> {\n const candidates = [\n path.join(resolveBase(deps), HELPS_DIR, SCHEMA_DOCS_FILE),\n ...(deps.bundledHelpsDir ? [path.join(deps.bundledHelpsDir(), SCHEMA_DOCS_FILE)] : []),\n ];\n for (const candidate of candidates) {\n try {\n return await readFile(candidate, \"utf-8\");\n } catch {\n // try the next source\n }\n }\n return `manageCollection: could not read the collection-authoring reference (${SCHEMA_DOCS_FILE}).`;\n}\n\n/** Return the raw schema.json of an existing collection, for editing.\n * Staging (the canonical writable copy) first, the active mirror as a\n * fallback for user-scope skills that have no staging copy. Raw text —\n * not the parsed schema — so the agent edits the true on-disk source. */\nasync function handleGetSchema(slug: string, deps: ManageCollectionDeps): Promise<string> {\n const collection = await loadCollection(slug, deps);\n if (!collection) return unknownCollection(slug);\n // Path from the discovered (sanitized) slug, never the raw arg.\n const candidates = [path.join(dataSkillDir(resolveBase(deps), collection.slug), SCHEMA_FILE), path.join(collection.skillDir, SCHEMA_FILE)];\n for (const candidate of candidates) {\n try {\n return await readFile(candidate, \"utf-8\");\n } catch {\n // fall through to the next location\n }\n }\n return `manageCollection: '${defangForPrompt(slug)}' has no readable ${SCHEMA_FILE}.`;\n}\n\n/** Refuse a schema edit the host can't honour: user-scope/feed collections\n * are read-only, and presets (mc-*) re-seed on restart so an edit is lost. */\nfunction schemaEditRefusal(collection: LoadedCollection, slug: string): string | null {\n if (collection.source !== \"project\") {\n return `manageCollection: '${defangForPrompt(slug)}' is ${collection.source}-scope and read-only here — only project collections (data/skills/) can be edited.`;\n }\n if (isPresetSlug(slug)) {\n return `manageCollection: '${defangForPrompt(slug)}' is a preset (mc-*) and re-seeds on restart — copy it under a different slug to customise.`;\n }\n return null;\n}\n\n/** Turn a CollectionSchemaZ failure into a short, actionable list the\n * agent can fix, pointing back at the field reference. */\nfunction formatSchemaIssues(issues: readonly { path: PropertyKey[]; message: string }[]): string {\n const shown = issues.slice(0, MAX_SCHEMA_ISSUES);\n const lines = shown.map((issue) => `- ${issue.path.map(String).join(\".\") || \"(root)\"}: ${defangForPrompt(issue.message)}`).join(\"\\n\");\n const omitted = issues.length - shown.length;\n const more = omitted > 0 ? `\\n- …and ${omitted} more issue(s); fix these first and retry.` : \"\";\n return `manageCollection: schema rejected — fix and retry (call schemaDocs for the field reference):\\n${lines}${more}`;\n}\n\n/** Write the validated schema to the canonical staging copy, then mirror\n * it into the active `.claude/skills/` tree discovery reads — an internal\n * write doesn't fire the skill-bridge hook, so we mirror explicitly. */\nasync function writeAndMirrorSchema(slug: string, schema: unknown, deps: ManageCollectionDeps): Promise<void> {\n const base = resolveBase(deps);\n await writeFileAtomic(path.join(dataSkillDir(base, slug), SCHEMA_FILE), `${JSON.stringify(schema, null, 2)}\\n`);\n mirrorSkillWrite(base, { slug, relSegments: [SCHEMA_FILE] });\n try {\n await deps.refreshAfterWrite?.();\n } catch {\n // best-effort — see ManageCollectionDeps.refreshAfterWrite\n }\n}\n\n/** The post-Zod acceptance gates discovery applies before a parsed schema\n * becomes a live collection. Mirrors discovery's checks (`loadOneCollection`)\n * so a write can't pass here yet be silently skipped on the next load.\n * putSchema only runs for project-scope collections, so the feed-`ingest`\n * gate doesn't apply. Returns a one-line reason, or null when the schema\n * would be accepted. */\nfunction schemaDiscoveryGate(schema: CollectionSchema, base: string): string | null {\n const primaryField = schema.fields[schema.primaryKey];\n if (!primaryField) return `primaryKey '${schema.primaryKey}' is not one of the declared fields`;\n if (primaryField.primary !== true) return `the primaryKey field '${schema.primaryKey}' must be flagged \\`primary: true\\``;\n if (schema.dataPath !== undefined && resolveDataDir(schema.dataPath, base) === null) return `dataPath '${schema.dataPath}' escapes the workspace`;\n if (schema.dataSource !== undefined && resolveDataDir(schema.dataSource.path, base) === null) {\n return `dataSource.path '${schema.dataSource.path}' escapes the workspace`;\n }\n return null;\n}\n\n/** Validate a schema against CollectionSchemaZ and, on success, persist it.\n * Edit-only: a new collection is created by writing SKILL.md + schema.json\n * under data/skills/<slug>/ (the normal create flow), not through here. */\nasync function handlePutSchema(slug: string, schemaArg: unknown, deps: ManageCollectionDeps): Promise<string> {\n if (!schemaArg || typeof schemaArg !== \"object\" || Array.isArray(schemaArg)) {\n return \"manageCollection: `schema` is required for putSchema — the full collection schema object.\";\n }\n const collection = await loadCollection(slug, deps);\n if (!collection) {\n return `manageCollection: unknown collection '${defangForPrompt(slug)}' — create it by writing SKILL.md + ${SCHEMA_FILE} under data/skills/${defangForPrompt(slug)}/, then edit it here.`;\n }\n const refusal = schemaEditRefusal(collection, slug);\n if (refusal) return refusal;\n const parsed = CollectionSchemaZ.safeParse(schemaArg);\n if (!parsed.success) return formatSchemaIssues(parsed.error.issues);\n // Run the SAME post-Zod gates discovery applies, so a write can't pass\n // here yet be silently skipped on the next load (hiding the collection).\n const gate = schemaDiscoveryGate(parsed.data, resolveBase(deps));\n if (gate) {\n return `manageCollection: schema rejected — ${gate} (call schemaDocs for the field reference). It passes basic validation but discovery would skip it, hiding the collection.`;\n }\n // Path from the discovered (sanitized) slug, never the raw arg.\n await writeAndMirrorSchema(collection.slug, parsed.data, deps);\n return JSON.stringify({ collection: collection.slug, written: true });\n}\n\nconst MANAGE_COLLECTION_PROMPT =\n \"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). \" +\n \"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. \" +\n \"`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. \" +\n '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. ' +\n \"`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. \" +\n '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. ' +\n \"`deleteItems` removes records by id and returns `{ deleted, rejected }`; an id that doesn't exist comes back rejected rather than counted as deleted, so check `rejected` before reporting a deletion as done. \" +\n \"Answer aggregation questions (counts, sums, averages, group-bys) with `queryItems` on ANY collection — on a dataSource (CSV) collection it scans the whole file (getItems is row-capped, so aggregates computed from its output can be silently wrong on large files); on a file-backed collection it aggregates the enriched records, so computed fields (derived/rollup/toggle) are queryable columns.\";\n\n/** Validate getItems' optional `ids`/`fields` args, then delegate. */\nasync function dispatchGetItems(collection: LoadedCollection, args: Record<string, unknown>, deps: ManageCollectionDeps): Promise<string> {\n const ids = optionalStringArray(args.ids, \"ids\");\n if (!ids.ok) return ids.error;\n const fields = optionalStringArray(args.fields, \"fields\");\n if (!fields.ok) return fields.error;\n return handleGetItems(collection, { slug: collection.slug, ids: ids.value, fields: fields.value }, deps);\n}\n\n/** Actions that operate on a collection's RECORDS — i.e. the ones that\n * need the collection loaded first. Schema/workspace actions don't. */\nconst RECORD_ACTIONS = new Set([\"getItems\", \"putItems\", \"deleteItems\", \"queryItems\"]);\n\n/** Record-action dispatch, split from `manageCollectionHandler` to keep\n * both within the cognitive-complexity budget. */\nasync function dispatchRecordAction(action: string, collection: LoadedCollection, args: Record<string, unknown>, deps: ManageCollectionDeps): Promise<string> {\n if (action === \"getItems\") return dispatchGetItems(collection, args, deps);\n if (action === \"queryItems\") return handleQueryItems(collection, args.query, deps);\n if (action === \"deleteItems\") {\n const ids = parseDeleteIds(args);\n return typeof ids === \"string\" ? ids : handleDeleteItems(collection, ids, deps);\n }\n const parsed = parsePutItems(args, collection.slug);\n return typeof parsed === \"string\" ? parsed : handlePutItems(collection, parsed, deps);\n}\n\n// The tool's action dispatch. Extracted from the factory's returned object so\n// `makeManageCollectionTool` stays under the max-lines threshold; each branch\n// already delegates to a handler.\nasync function manageCollectionHandler(deps: ManageCollectionDeps, args: Record<string, unknown>): Promise<string> {\n const action = typeof args.action === \"string\" ? args.action : \"\";\n if (action === \"schemaDocs\") return handleSchemaDocs(deps);\n if (action === \"getOntology\") return handleGetOntology(deps);\n const slug = typeof args.slug === \"string\" ? args.slug.trim() : \"\";\n if (!slug) return \"manageCollection: `slug` is required (the collection's slug).\";\n if (action === \"getSchema\") return handleGetSchema(slug, deps);\n if (action === \"putSchema\") return handlePutSchema(slug, args.schema, deps);\n if (!RECORD_ACTIONS.has(action)) {\n return 'manageCollection: `action` must be \"getItems\", \"putItems\", \"deleteItems\", \"queryItems\", \"getOntology\", \"schemaDocs\", \"getSchema\", or \"putSchema\".';\n }\n const collection = await loadCollection(slug, deps);\n if (!collection) return unknownCollection(slug);\n return dispatchRecordAction(action, collection, args, deps);\n}\n\n// Static tool definition, hoisted out of the factory so the function body\n// stays within the line budget (the schema only ever grows).\nconst MANAGE_COLLECTION_DEFINITION = {\n name: \"manageCollection\",\n description:\n \"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; deleteItems removes records by id. 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.\",\n inputSchema: {\n type: \"object\",\n properties: {\n action: {\n type: \"string\",\n enum: [\"getItems\", \"putItems\", \"deleteItems\", \"queryItems\", \"getOntology\", \"schemaDocs\", \"getSchema\", \"putSchema\"],\n description: \"What to do.\",\n },\n slug: {\n type: \"string\",\n description: \"The collection's slug (its directory name, e.g. `stock-quotes`). Required for everything except schemaDocs and getOntology.\",\n },\n ids: {\n type: \"array\",\n items: { type: \"string\" },\n description:\n \"getItems: only these record ids (primary-key values); omit for all records. deleteItems: the record ids to delete — required, and never optional there.\",\n },\n fields: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"getItems: only these fields per record (the primary key is always included). Omit for all fields. Use on large collections.\",\n },\n items: {\n type: \"array\",\n items: { type: \"object\" },\n description: \"putItems: the record objects to store. Each must carry the schema's primaryKey value (it doubles as the filename).\",\n },\n mode: {\n type: \"string\",\n enum: [\"upsert\", \"create\", \"merge\"],\n description:\n '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.',\n },\n schema: {\n type: \"object\",\n description:\n \"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.\",\n },\n query: {\n type: \"object\",\n description:\n 'queryItems: a structured aggregation query — `{ groupBy?: [\"col\"], aggregates?: { alias: { op: \"count\"|\"sum\"|\"avg\"|\"min\"|\"max\", column? } }, where?: [{ field, op, value }], orderBy?: [{ field, dir? }], limit? }`. At least one of groupBy/aggregates. On a dataSource (CSV) collection it scans the WHOLE file uncapped (unlike getItems); on a file-backed collection it aggregates the ENRICHED records, so computed fields (derived/rollup/toggle) are queryable columns. Use it for counts / sums / averages / group-bys instead of arithmetic over getItems output.',\n },\n },\n required: [\"action\"],\n },\n};\n\nexport function makeManageCollectionTool(deps: ManageCollectionDeps = {}) {\n return {\n definition: MANAGE_COLLECTION_DEFINITION,\n\n // Collections are workspace data every role can already reach via\n // raw Read/Write/Edit — gating the SAFER path per-role would only\n // push unlisted roles back onto unvalidated file I/O.\n alwaysActive: true,\n\n prompt: MANAGE_COLLECTION_PROMPT,\n\n handler: (args: Record<string, unknown>): Promise<string> => manageCollectionHandler(deps, args),\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/utils/text.ts
|
|
2
|
+
/** Clip a string to at most `max` chars. The ellipsis is part of the budget,
|
|
3
|
+
* so the result never exceeds `max`.
|
|
4
|
+
*
|
|
5
|
+
* Edge cases:
|
|
6
|
+
* - `text.length <= max` → `text` unchanged.
|
|
7
|
+
* - `max <= 0` → empty string (callers asking for "no output" should get no
|
|
8
|
+
* output, not a stray ellipsis).
|
|
9
|
+
* - `ellipsis.length >= max` → the ellipsis itself is clipped to `max` rather
|
|
10
|
+
* than throwing. Dropping this guard is what let the pre-#2217 core copy
|
|
11
|
+
* return 3 chars for `truncate("hello", 2, "...")` while promising in its
|
|
12
|
+
* own docblock that output never exceeds `max`. */
|
|
13
|
+
function truncate(text, max, ellipsis = "…") {
|
|
14
|
+
if (max <= 0) return "";
|
|
15
|
+
if (text.length <= max) return text;
|
|
16
|
+
if (ellipsis.length >= max) return ellipsis.slice(0, max);
|
|
17
|
+
return text.slice(0, max - ellipsis.length) + ellipsis;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
Object.defineProperty(exports, "truncate", {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
get: function() {
|
|
23
|
+
return truncate;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=text-CaAh9ZSO.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-CaAh9ZSO.cjs","names":[],"sources":["../src/utils/text.ts"],"sourcesContent":["// Canonical text helpers. Browser-safe: pure string work, no node imports.\n\n/** Clip a string to at most `max` chars. The ellipsis is part of the budget,\n * so the result never exceeds `max`.\n *\n * Edge cases:\n * - `text.length <= max` → `text` unchanged.\n * - `max <= 0` → empty string (callers asking for \"no output\" should get no\n * output, not a stray ellipsis).\n * - `ellipsis.length >= max` → the ellipsis itself is clipped to `max` rather\n * than throwing. Dropping this guard is what let the pre-#2217 core copy\n * return 3 chars for `truncate(\"hello\", 2, \"...\")` while promising in its\n * own docblock that output never exceeds `max`. */\nexport function truncate(text: string, max: number, ellipsis = \"…\"): string {\n if (max <= 0) return \"\";\n if (text.length <= max) return text;\n if (ellipsis.length >= max) return ellipsis.slice(0, max);\n return text.slice(0, max - ellipsis.length) + ellipsis;\n}\n"],"mappings":";;;;;;;;;;;;AAaA,SAAgB,SAAS,MAAc,KAAa,WAAW,KAAa;CAC1E,IAAI,OAAO,GAAG,OAAO;CACrB,IAAI,KAAK,UAAU,KAAK,OAAO;CAC/B,IAAI,SAAS,UAAU,KAAK,OAAO,SAAS,MAAM,GAAG,GAAG;CACxD,OAAO,KAAK,MAAM,GAAG,MAAM,SAAS,MAAM,IAAI;AAChD"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/utils/text.ts
|
|
2
|
+
/** Clip a string to at most `max` chars. The ellipsis is part of the budget,
|
|
3
|
+
* so the result never exceeds `max`.
|
|
4
|
+
*
|
|
5
|
+
* Edge cases:
|
|
6
|
+
* - `text.length <= max` → `text` unchanged.
|
|
7
|
+
* - `max <= 0` → empty string (callers asking for "no output" should get no
|
|
8
|
+
* output, not a stray ellipsis).
|
|
9
|
+
* - `ellipsis.length >= max` → the ellipsis itself is clipped to `max` rather
|
|
10
|
+
* than throwing. Dropping this guard is what let the pre-#2217 core copy
|
|
11
|
+
* return 3 chars for `truncate("hello", 2, "...")` while promising in its
|
|
12
|
+
* own docblock that output never exceeds `max`. */
|
|
13
|
+
function truncate(text, max, ellipsis = "…") {
|
|
14
|
+
if (max <= 0) return "";
|
|
15
|
+
if (text.length <= max) return text;
|
|
16
|
+
if (ellipsis.length >= max) return ellipsis.slice(0, max);
|
|
17
|
+
return text.slice(0, max - ellipsis.length) + ellipsis;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { truncate as t };
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=text-CjDbP2O1.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-CjDbP2O1.js","names":[],"sources":["../src/utils/text.ts"],"sourcesContent":["// Canonical text helpers. Browser-safe: pure string work, no node imports.\n\n/** Clip a string to at most `max` chars. The ellipsis is part of the budget,\n * so the result never exceeds `max`.\n *\n * Edge cases:\n * - `text.length <= max` → `text` unchanged.\n * - `max <= 0` → empty string (callers asking for \"no output\" should get no\n * output, not a stray ellipsis).\n * - `ellipsis.length >= max` → the ellipsis itself is clipped to `max` rather\n * than throwing. Dropping this guard is what let the pre-#2217 core copy\n * return 3 chars for `truncate(\"hello\", 2, \"...\")` while promising in its\n * own docblock that output never exceeds `max`. */\nexport function truncate(text: string, max: number, ellipsis = \"…\"): string {\n if (max <= 0) return \"\";\n if (text.length <= max) return text;\n if (ellipsis.length >= max) return ellipsis.slice(0, max);\n return text.slice(0, max - ellipsis.length) + ellipsis;\n}\n"],"mappings":";;;;;;;;;;;;AAaA,SAAgB,SAAS,MAAc,KAAa,WAAW,KAAa;CAC1E,IAAI,OAAO,GAAG,OAAO;CACrB,IAAI,KAAK,UAAU,KAAK,OAAO;CAC/B,IAAI,SAAS,UAAU,KAAK,OAAO,SAAS,MAAM,GAAG,GAAG;CACxD,OAAO,KAAK,MAAM,GAAG,MAAM,SAAS,MAAM,IAAI;AAChD"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_errors = require("../errors-7P5eMOSX.cjs");
|
|
3
|
+
const require_text = require("../text-CaAh9ZSO.cjs");
|
|
4
|
+
exports.errorMessage = require_errors.errorMessage;
|
|
5
|
+
exports.toError = require_errors.toError;
|
|
6
|
+
exports.truncate = require_text.truncate;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Clip a string to at most `max` chars. The ellipsis is part of the budget,
|
|
2
|
+
* so the result never exceeds `max`.
|
|
3
|
+
*
|
|
4
|
+
* Edge cases:
|
|
5
|
+
* - `text.length <= max` → `text` unchanged.
|
|
6
|
+
* - `max <= 0` → empty string (callers asking for "no output" should get no
|
|
7
|
+
* output, not a stray ellipsis).
|
|
8
|
+
* - `ellipsis.length >= max` → the ellipsis itself is clipped to `max` rather
|
|
9
|
+
* than throwing. Dropping this guard is what let the pre-#2217 core copy
|
|
10
|
+
* return 3 chars for `truncate("hello", 2, "...")` while promising in its
|
|
11
|
+
* own docblock that output never exceeds `max`. */
|
|
12
|
+
export declare function truncate(text: string, max: number, ellipsis?: string): string;
|
package/dist/whisper/index.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_rolldown_runtime = require("../rolldown-runtime-D6vf50IK.cjs");
|
|
3
|
+
const require_errors = require("../errors-7P5eMOSX.cjs");
|
|
3
4
|
let node_path = require("node:path");
|
|
4
5
|
node_path = require_rolldown_runtime.__toESM(node_path, 1);
|
|
5
6
|
let node_fs = require("node:fs");
|
|
@@ -13,9 +14,6 @@ let node_timers_promises = require("node:timers/promises");
|
|
|
13
14
|
//#region src/whisper/internal.ts
|
|
14
15
|
var ONE_SECOND_MS = 1e3;
|
|
15
16
|
var ONE_MINUTE_MS = 6e4;
|
|
16
|
-
function errorMessage(err) {
|
|
17
|
-
return err instanceof Error ? err.message : String(err);
|
|
18
|
-
}
|
|
19
17
|
var NOOP = () => void 0;
|
|
20
18
|
var NOOP_LOGGER = {
|
|
21
19
|
info: NOOP,
|
|
@@ -178,7 +176,7 @@ function createModelDownloader(modelsDir, logger = NOOP_LOGGER) {
|
|
|
178
176
|
downloadStatus.set(name, { state: "ready" });
|
|
179
177
|
logger.info("model download: ok", { model: name });
|
|
180
178
|
} catch (err) {
|
|
181
|
-
const error = errorMessage(err);
|
|
179
|
+
const error = require_errors.errorMessage(err);
|
|
182
180
|
downloadStatus.set(name, {
|
|
183
181
|
state: "error",
|
|
184
182
|
error
|
|
@@ -268,7 +266,7 @@ function waitForReadyOrFailure(proc, port) {
|
|
|
268
266
|
};
|
|
269
267
|
onError = (err) => {
|
|
270
268
|
cleanup();
|
|
271
|
-
reject(/* @__PURE__ */ new Error(`spawn failed: ${errorMessage(err)}`));
|
|
269
|
+
reject(/* @__PURE__ */ new Error(`spawn failed: ${require_errors.errorMessage(err)}`));
|
|
272
270
|
};
|
|
273
271
|
onExit = (code) => {
|
|
274
272
|
cleanup();
|
|
@@ -299,7 +297,7 @@ async function postInference(port, wavPath, language) {
|
|
|
299
297
|
signal: AbortSignal.timeout(INFERENCE_TIMEOUT_MS)
|
|
300
298
|
});
|
|
301
299
|
} catch (err) {
|
|
302
|
-
throw new Error(`whisper-server request failed: ${errorMessage(err)}`);
|
|
300
|
+
throw new Error(`whisper-server request failed: ${require_errors.errorMessage(err)}`);
|
|
303
301
|
}
|
|
304
302
|
if (!res.ok) throw new Error(`whisper-server returned HTTP ${res.status}`);
|
|
305
303
|
return parseInferenceText(await res.json());
|
|
@@ -323,7 +321,7 @@ function instrumentChildProcess(proc, model, logger, onExit) {
|
|
|
323
321
|
drainStderr(proc, stderrTail);
|
|
324
322
|
proc.on("error", (err) => logger.warn("sidecar: process error", {
|
|
325
323
|
model,
|
|
326
|
-
error: errorMessage(err)
|
|
324
|
+
error: require_errors.errorMessage(err)
|
|
327
325
|
}));
|
|
328
326
|
proc.on("exit", (code) => {
|
|
329
327
|
logger.warn("sidecar: exited", {
|
|
@@ -340,7 +338,7 @@ async function awaitReadyOrThrow(proc, port, stderrTail, waitReady) {
|
|
|
340
338
|
await waitReady(proc, port);
|
|
341
339
|
} catch (err) {
|
|
342
340
|
proc.kill();
|
|
343
|
-
throw new Error(`whisper-server failed to start: ${errorMessage(err)} — stderr: ${stderrTail.text.slice(-500)}`);
|
|
341
|
+
throw new Error(`whisper-server failed to start: ${require_errors.errorMessage(err)} — stderr: ${stderrTail.text.slice(-500)}`);
|
|
344
342
|
}
|
|
345
343
|
}
|
|
346
344
|
function throwIfStartCancelled(startedToken, currentToken, proc) {
|
|
@@ -423,7 +421,7 @@ function createSidecar(modelsDir, serverBinary = "whisper-server", logger = NOOP
|
|
|
423
421
|
} catch (err) {
|
|
424
422
|
logger.warn("sidecar: warmup failed", {
|
|
425
423
|
model,
|
|
426
|
-
error: errorMessage(err)
|
|
424
|
+
error: require_errors.errorMessage(err)
|
|
427
425
|
});
|
|
428
426
|
}
|
|
429
427
|
}
|