@mulmoclaude/core 3.2.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/assets/helps/error-recovery.md +71 -0
  2. package/assets/helps/google-calendar-collection.md +10 -0
  3. package/dist/collection/registry/server/index.cjs +3 -3
  4. package/dist/collection/registry/server/index.cjs.map +1 -1
  5. package/dist/collection/registry/server/index.js +3 -3
  6. package/dist/collection/registry/server/index.js.map +1 -1
  7. package/dist/collection/server/discovery.d.ts +10 -2
  8. package/dist/collection/server/host.d.ts +27 -3
  9. package/dist/collection/server/index.cjs +2 -2
  10. package/dist/collection/server/index.js +2 -2
  11. package/dist/collection-watchers/index.cjs +2 -2
  12. package/dist/collection-watchers/index.js +2 -2
  13. package/dist/{discovery-CDOjfctD.cjs → discovery-CrA_EqUJ.cjs} +19 -7
  14. package/dist/discovery-CrA_EqUJ.cjs.map +1 -0
  15. package/dist/{discovery-CC7kuwHf.js → discovery-D7LCN8gJ.js} +19 -7
  16. package/dist/discovery-D7LCN8gJ.js.map +1 -0
  17. package/dist/feeds/server/index.cjs +2 -2
  18. package/dist/feeds/server/index.js +2 -2
  19. package/dist/google/calendar.d.ts +15 -0
  20. package/dist/google/calendarBackfillState.d.ts +19 -0
  21. package/dist/google/calendarSyncStore.d.ts +15 -0
  22. package/dist/google/collectionSync.d.ts +78 -11
  23. package/dist/google/index.cjs +207 -35
  24. package/dist/google/index.cjs.map +1 -1
  25. package/dist/google/index.d.ts +4 -3
  26. package/dist/google/index.js +197 -35
  27. package/dist/google/index.js.map +1 -1
  28. package/dist/{server-WU35UYlF.cjs → server-CorfPQVQ.cjs} +2 -2
  29. package/dist/{server-WU35UYlF.cjs.map → server-CorfPQVQ.cjs.map} +1 -1
  30. package/dist/{server-AA2kfDfk.js → server-D6QY853r.js} +2 -2
  31. package/dist/{server-AA2kfDfk.js.map → server-D6QY853r.js.map} +1 -1
  32. package/package.json +1 -1
  33. package/dist/discovery-CC7kuwHf.js.map +0 -1
  34. package/dist/discovery-CDOjfctD.cjs.map +0 -1
@@ -572,6 +572,77 @@ workspace is already on a real filesystem and a conflict still will not
572
572
  clear, that is a new bug — report it with the calendar id and the
573
573
  record.
574
574
 
575
+ ## A calendar collection only ever holds a handful of records
576
+
577
+ ### Symptoms
578
+
579
+ A `googleCalendar` collection sits at a small number of records (0, 1,
580
+ a couple of dozen) instead of the calendar's history. Sync reports
581
+ success with **no error**, and pressing it again adds nothing. Only
582
+ events created or edited AFTER the collection existed keep arriving.
583
+
584
+ ### Cause
585
+
586
+ A Google sync token is keyed by `calendarId` alone, so it is shared by
587
+ every consumer of that calendar — the `google` tool's `calendarSync`,
588
+ and every collection bound to it. Before `#2850` a fresh collection
589
+ resumed from whatever cursor was already stored, so its "first sync"
590
+ was a DELTA of a window it had never received. Two ways in: the user
591
+ (or you) ran `google` `kind: "calendarSync"` first, or another
592
+ collection on the same calendar had already synced.
593
+
594
+ Fixed in `@mulmoclaude/core` 3.4.0: a collection now records its own
595
+ backfill beside its records (`<dataPath>/.calendar-sync.json`) and the
596
+ sync walks the whole calendar while any collection still lacks one. The
597
+ `google` tool keeps a separate cursor.
598
+
599
+ ### Fix
600
+
601
+ On a host with the fix, delete `<dataPath>/.calendar-sync.json` (or the
602
+ records themselves) and press Sync — the walk starts over. On an older
603
+ host, delete `<workspace>/data/calendar/.sync-state.json` to force a
604
+ full re-walk for every collection on that calendar.
605
+
606
+ Do NOT tell the user to recreate the collection: deleting the skill
607
+ files by hand does not clear the shared cursor, which is why the
608
+ original reporter hit this six times in a row.
609
+
610
+ ## Sync says only part of the calendar was copied
611
+
612
+ ### Symptoms
613
+
614
+ Sync reports `Google returned more pages of events than one sync pass
615
+ walks`. The collection holds real records, but not all of them, and the
616
+ message returns on every attempt.
617
+
618
+ ### Cause
619
+
620
+ One sync pass walks a bounded number of pages, and Google returned more
621
+ than that. Any calendar big enough in Google's own paging terms reaches
622
+ it — Google states a page may hold fewer events than asked for, "or none
623
+ at all", so page count does not track event count.
624
+
625
+ The common cause by far is recurring expansion: with `singleEvents=true`
626
+ and no date window, a recurring event with **no end date** is expanded
627
+ decades into the future, and one such series can fill the entire walk
628
+ before any other event is reached. Check that first, but do not assume
629
+ it is the only way in.
630
+
631
+ Before `#2850` this was silent — the partial copy read as a completed
632
+ sync.
633
+
634
+ ### Fix
635
+
636
+ Give every recurring event with no end date a finite end date in Google
637
+ Calendar, then Sync again. Ask the user to check for "repeats forever"
638
+ series; a single one is enough to cause this. If there are none and the
639
+ message persists, the calendar is simply larger than one pass — report
640
+ it, since raising the guard is a code change.
641
+
642
+ Nothing already synced is lost while it persists: the records that did
643
+ arrive are written and keep their baseline, and the sync withholds the
644
+ backfill marker rather than claiming the calendar is fully copied.
645
+
575
646
  ## `proceeding without the calendar state lock` in the logs
576
647
 
577
648
  ### Symptoms
@@ -93,6 +93,10 @@ the calendar day view then draws each record as a proportional time block.
93
93
  - **On creation** — the first sync starts as soon as the schema lands, so the
94
94
  collection is not empty while the user waits for the schedule. The same
95
95
  applies when you add a `googleCalendar` block to an existing collection.
96
+ Each collection gets its OWN full walk: a calendar that another collection
97
+ (or the `google` tool's `calendarSync`) has already read is still walked in
98
+ full for the new one, because a sync cursor says how far the *calendar* has
99
+ been read, not what *these records* hold (#2850).
96
100
  - **Hourly** after that, in the background.
97
101
  - **On demand** — the collection view has a Sync button. Tell the user about it
98
102
  if they want the calendar refreshed right now.
@@ -110,6 +114,12 @@ the calendar day view then draws each record as a proportional time block.
110
114
  this covers **all** dates — Google does not allow a date window together with
111
115
  incremental sync — so a calendar with years of history produces a lot of
112
116
  records on that first pass.
117
+ - Because it covers all dates, a recurring event with **no end date** is
118
+ expanded decades into the future, and one such series can produce hundreds of
119
+ records. If a first sync reports that only part of the calendar was copied,
120
+ that is the common cause — ask the user to give those series a finite end
121
+ date. The report itself means the walk ran out of pages, which a big enough
122
+ calendar can do on its own.
113
123
 
114
124
  Records are ordinary collection records: the user can open, filter, and view
115
125
  them like any other.
@@ -4,9 +4,9 @@ const require_dist = require("../../../dist-CsgSfWwR.cjs");
4
4
  const require_root = require("../../../root-rPH6FGDT.cjs");
5
5
  const require_relPath = require("../../../relPath-CTAjGdCL.cjs");
6
6
  const require_utils_fetch = require("../../../utils/fetch.cjs");
7
- const require_discovery = require("../../../discovery-CDOjfctD.cjs");
7
+ const require_discovery = require("../../../discovery-CrA_EqUJ.cjs");
8
8
  const require_templatePath = require("../../../templatePath-27vUZowm.cjs");
9
- const require_server = require("../../../server-WU35UYlF.cjs");
9
+ const require_server = require("../../../server-CorfPQVQ.cjs");
10
10
  const require_skill_bridge_index = require("../../../skill-bridge/index.cjs");
11
11
  const require_types = require("../../../types-CNqkLT4M.cjs");
12
12
  let node_fs = require("node:fs");
@@ -835,7 +835,7 @@ async function copyBundle(skillDir, outDir) {
835
835
  async function exportSeed(dataDir, outDir) {
836
836
  let names;
837
837
  try {
838
- names = (await (0, node_fs_promises.readdir)(dataDir)).filter((name) => name.endsWith(".json"));
838
+ names = (await (0, node_fs_promises.readdir)(dataDir)).filter((name) => name.endsWith(".json") && !name.startsWith("."));
839
839
  } catch {
840
840
  return {
841
841
  count: 0,
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../../../../src/collection/registry/server/registriesConfig.ts","../../../../src/collection/registry/server/client.ts","../../../../src/collection/registry/server/collectionFiles.ts","../../../../src/collection/registry/server/importCollection.ts","../../../../src/collection/registry/server/importWriter.ts","../../../../src/collection/registry/server/exportCollection.ts","../../../../src/collection/registry/server/skillDescription.ts","../../../../src/collection/registry/server/performExport.ts","../../../../src/collection/registry/server/index.ts"],"sourcesContent":["// User-supplied list of extra collection registries (`config/collections-registries.json`).\n// The official receptron/mulmoclaude-collections registry is always loaded; this\n// file adds more on top so a community / org / private registry shows up in the\n// same Discover catalog.\n//\n// Shape on disk:\n// [\n// { \"name\": \"myorg\", \"indexUrl\": \"https://...\", \"rawBaseUrl\": \"https://...\" }\n// ]\n//\n// Validation rules (a bad entry is dropped + logged; the rest still load):\n// - `name` matches `[A-Za-z0-9][A-Za-z0-9_-]{0,31}` and is NOT the reserved\n// `official` (which is always synthesized by client.ts)\n// - Both URLs are HTTPS, parseable, and have no userinfo/credentials\n// - Names are unique (later duplicates are dropped, first wins)\n//\n// Pure read + parse — no network. Caller invalidates by re-reading the file.\n\nimport { readFileSync } from \"node:fs\";\n\nimport { isRecord } from \"../guards.js\";\nimport { OFFICIAL_REGISTRY_NAME, type RegistryConfigEntry } from \"../types.js\";\nimport { collectionsRegistriesConfigPath, getWorkspaceRoot, log } from \"../../server/host.js\";\n\n/** Which workspace a registry read is scoped to. Omitted ⇒ the host's\n * configured root (and, under an explicit-root binding, a loud throw rather\n * than a silent read of another project's config). */\nexport interface RegistryScope {\n workspaceRoot?: string | undefined;\n}\n\nexport { OFFICIAL_REGISTRY_NAME, type RegistryConfigEntry };\n\nconst NAME_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$/;\n\nfunction isValidHttpsUrl(value: unknown): value is string {\n if (typeof value !== \"string\" || value.length === 0) return false;\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return false;\n }\n if (url.protocol !== \"https:\") return false;\n if (url.username !== \"\" || url.password !== \"\") return false;\n return true;\n}\n\nfunction parseEntry(value: unknown, index: number): RegistryConfigEntry | string {\n if (!isRecord(value)) return `entry[${index}] is not an object`;\n const { name, indexUrl, rawBaseUrl } = value;\n if (typeof name !== \"string\" || !NAME_RE.test(name)) {\n return `entry[${index}].name must match ${NAME_RE.source}`;\n }\n if (name === OFFICIAL_REGISTRY_NAME) {\n return `entry[${index}].name \"${OFFICIAL_REGISTRY_NAME}\" is reserved`;\n }\n if (!isValidHttpsUrl(indexUrl)) return `entry[${index}].indexUrl must be a valid HTTPS URL (no credentials)`;\n if (!isValidHttpsUrl(rawBaseUrl)) return `entry[${index}].rawBaseUrl must be a valid HTTPS URL (no credentials)`;\n // `rawBaseUrl` gets joined as `${rawBaseUrl}/<path>` downstream — a query or\n // fragment would land in the middle of the composed URL and break every\n // collection-file fetch on that registry (CodeRabbit review on #1837). The\n // index URL is fetched directly so a query is fine there; only rawBase\n // needs the constraint.\n if (rawBaseUrl.includes(\"?\") || rawBaseUrl.includes(\"#\")) {\n return `entry[${index}].rawBaseUrl must not contain a query (?) or fragment (#) — it's joined as a path prefix`;\n }\n // Strip trailing slashes so `${rawBaseUrl}/${path}` joins cleanly regardless\n // of what the user wrote. Plain-loop trim instead of a regex — the linter\n // flags any unanchored-quantifier regex as ReDoS-suspect even when it's\n // safe, and the loop is just as clear at this size.\n let normalizedRawBase = rawBaseUrl;\n while (normalizedRawBase.endsWith(\"/\")) normalizedRawBase = normalizedRawBase.slice(0, -1);\n return { name, indexUrl, rawBaseUrl: normalizedRawBase };\n}\n\n/** Parse the JSON text of `config/collections-registries.json`. Bad entries\n * drop with a warning; valid entries are returned in source order. Exported\n * for unit testing — the I/O wrapper `loadRegistriesConfig` below reads the\n * workspace file and delegates here. */\nexport function parseRegistriesConfig(raw: unknown): RegistryConfigEntry[] {\n if (!Array.isArray(raw)) {\n log.warn(\"collections-registry\", \"registries config is not an array — ignoring\");\n return [];\n }\n const seen = new Set<string>();\n const out: RegistryConfigEntry[] = [];\n for (let i = 0; i < raw.length; i++) {\n const parsed = parseEntry(raw[i], i);\n if (typeof parsed === \"string\") {\n log.warn(\"collections-registry\", \"registry config entry rejected\", { reason: parsed });\n continue;\n }\n if (seen.has(parsed.name)) {\n log.warn(\"collections-registry\", \"registry config duplicate name dropped\", { name: parsed.name });\n continue;\n }\n seen.add(parsed.name);\n out.push(parsed);\n }\n return out;\n}\n\n// Missing file ⇒ null (the most common case — most users only want the official\n// registry). Other read errors propagate, matching the host's prior behavior.\nfunction readConfigTextOrNull(filePath: string): string | null {\n try {\n return readFileSync(filePath, \"utf-8\");\n } catch (err) {\n if (isRecord(err) && err.code === \"ENOENT\") return null;\n throw err;\n }\n}\n\n/** Read `config/collections-registries.json` for a workspace. `workspaceRoot`\n * defaults to the host's configured root; a multi-root host passes the project\n * it is serving. Missing file ⇒ empty list. Malformed JSON or rejected entries\n * log but never throw. */\nexport function loadRegistriesConfig(opts: RegistryScope = {}): RegistryConfigEntry[] {\n const text = readConfigTextOrNull(collectionsRegistriesConfigPath(opts.workspaceRoot ?? getWorkspaceRoot()));\n if (text === null) return [];\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch (err) {\n log.warn(\"collections-registry\", \"registries config is not valid JSON — ignoring\", {\n error: err instanceof Error ? err.message : String(err),\n });\n return [];\n }\n return parseRegistriesConfig(parsed);\n}\n","// Server-side client for the curated collection registries' published index.json\n// files. The official registry (receptron/mulmoclaude-collections via GitHub Pages)\n// is always loaded; users can append more via `config/collections-registries.json`.\n// Each index is fetched over HTTPS with a timeout, validated against the index\n// contract, and memo-cached per-registry so the Discover tab doesn't hammer the\n// upstreams. On a transient upstream failure we serve that registry's last good\n// index rather than failing the whole Discover view.\n\nimport { fetchWithTimeout } from \"../../../utils/fetch.js\";\nimport { errorMessage, ONE_SECOND_MS } from \"../../server/util.js\";\nimport { log } from \"../../server/host.js\";\nimport { parseRegistryIndex, type RegistryEntry, type RegistryIndex } from \"../registryIndex.js\";\nimport { loadRegistriesConfig, OFFICIAL_REGISTRY_NAME, type RegistryConfigEntry, type RegistryScope } from \"./registriesConfig.js\";\n\nconst DEFAULT_OFFICIAL_INDEX_URL = \"https://receptron.github.io/mulmoclaude-collections/index.json\";\nconst DEFAULT_OFFICIAL_RAW_BASE = \"https://raw.githubusercontent.com/receptron/mulmoclaude-collections/main\";\nexport const CACHE_TTL_MS = 5 * 60 * ONE_SECOND_MS;\n// During an outage (cache past TTL + failing upstream) don't re-hit the network\n// more than once per this window — serve stale immediately in between, so a down\n// upstream can't add its full timeout to every request.\nexport const STALE_RETRY_BACKOFF_MS = 60 * ONE_SECOND_MS;\nconst FETCH_TIMEOUT_MS = 10 * ONE_SECOND_MS;\nconst STATUS_BAD_GATEWAY = 502;\nconst STATUS_UNAVAILABLE = 503;\n\n/** Per-registry fetch outcome. `ok: false` is reported per-registry — the\n * merged Discover response still succeeds when at least one registry is\n * reachable; failed registries just contribute zero entries. */\nexport type FetchIndexResult = { ok: true; index: RegistryIndex; stale: boolean } | { ok: false; status: number; error: string };\n\ninterface CacheEntry {\n index: RegistryIndex;\n atMs: number;\n}\n\nconst cache = new Map<string, CacheEntry>();\nconst lastFailureMs = new Map<string, number>();\n\n/** Resolved descriptor for one registry the host should fetch — name + URLs.\n * `official` is always synthesized; further entries come from\n * `config/collections-registries.json`. Currently structurally identical to\n * `RegistryConfigEntry`; aliased so we can add server-only fields (e.g.\n * auth headers) without touching the on-disk config schema. */\nexport type RegistryDescriptor = RegistryConfigEntry;\n\nfunction officialDescriptor(): RegistryDescriptor {\n return {\n name: OFFICIAL_REGISTRY_NAME,\n indexUrl: process.env.COLLECTIONS_REGISTRY_URL ?? DEFAULT_OFFICIAL_INDEX_URL,\n rawBaseUrl: process.env.COLLECTIONS_REGISTRY_RAW_BASE ?? DEFAULT_OFFICIAL_RAW_BASE,\n };\n}\n\n/** The full ordered registry list: official first, then user-configured ones.\n * Re-reads the config file on every call so a Discover refresh picks up edits\n * without a server restart (the config is small + read-rarely). */\nexport function listRegistries(scope: RegistryScope = {}): RegistryDescriptor[] {\n return [officialDescriptor(), ...loadRegistriesConfig(scope)];\n}\n\n/** Look up one registry descriptor by name. Used by the preview / import path,\n * which has the registry label from the entry and needs the rawBase. */\nexport function findRegistry(name: string, scope: RegistryScope = {}): RegistryDescriptor | null {\n return listRegistries(scope).find((reg) => reg.name === name) ?? null;\n}\n\nasync function loadFromNetwork(descriptor: RegistryDescriptor): Promise<FetchIndexResult> {\n let res: Response;\n try {\n res = await fetchWithTimeout(descriptor.indexUrl, { timeoutMs: FETCH_TIMEOUT_MS, headers: { accept: \"application/json\" } });\n } catch (err) {\n log.warn(\"collections-registry\", \"index fetch failed\", { registry: descriptor.name, url: descriptor.indexUrl, error: errorMessage(err) });\n return { ok: false, status: STATUS_UNAVAILABLE, error: \"registry unreachable\" };\n }\n if (!res.ok) return { ok: false, status: STATUS_BAD_GATEWAY, error: `registry responded ${res.status}` };\n const json: unknown = await res.json().catch(() => null);\n const parsed = parseRegistryIndex(json, descriptor.name);\n if (!parsed.ok) {\n log.warn(\"collections-registry\", \"index invalid\", { registry: descriptor.name, url: descriptor.indexUrl, error: parsed.error });\n return { ok: false, status: STATUS_BAD_GATEWAY, error: `registry index invalid: ${parsed.error}` };\n }\n return { ok: true, index: parsed.index, stale: false };\n}\n\nexport type IndexLoader = (descriptor: RegistryDescriptor) => Promise<FetchIndexResult>;\n\n/** Cache key incorporates BOTH URLs so editing `indexUrl` or `rawBaseUrl` for\n * an existing registry name invalidates the cached index automatically — same\n * refresh cycle as if the user had renamed it (CodeRabbit review on #1837).\n * Without this, the Discover catalog could keep serving the old upstream's\n * entries while preview / import resolved the new rawBase, drifting until TTL\n * expiry. Tab + key-content guarantees no collision between e.g. swapped\n * name/url pairs. */\nfunction descriptorCacheKey(descriptor: RegistryDescriptor): string {\n return `${descriptor.name}\\t${descriptor.indexUrl}\\t${descriptor.rawBaseUrl}`;\n}\n\n/** Fetch one registry's index. Same cache + stale-on-failure semantics as the\n * original single-registry implementation — just keyed by descriptor identity\n * (name + both URLs) so multiple registries don't fight over one slot AND a\n * reconfigured registry doesn't serve a stale index from the prior URL. */\nexport async function fetchRegistryIndex(\n descriptor: RegistryDescriptor,\n opts: { force?: boolean; nowMs?: number; loader?: IndexLoader } = {},\n): Promise<FetchIndexResult> {\n const nowMs = opts.nowMs ?? Date.now();\n const loader = opts.loader ?? loadFromNetwork;\n const key = descriptorCacheKey(descriptor);\n const cached = cache.get(key);\n if (!opts.force && cached && nowMs - cached.atMs < CACHE_TTL_MS) {\n return { ok: true, index: cached.index, stale: false };\n }\n const failedAt = lastFailureMs.get(key);\n if (!opts.force && cached && failedAt !== undefined && nowMs - failedAt < STALE_RETRY_BACKOFF_MS) {\n return { ok: true, index: cached.index, stale: true };\n }\n const fresh = await loader(descriptor);\n if (fresh.ok) {\n cache.set(key, { index: fresh.index, atMs: nowMs });\n lastFailureMs.delete(key);\n return { ok: true, index: fresh.index, stale: false };\n }\n lastFailureMs.set(key, nowMs);\n if (cached) return { ok: true, index: cached.index, stale: true };\n return fresh;\n}\n\n/** One registry's contribution to the merged Discover view. `failed` registries\n * surface in the response so the UI can show a per-registry error badge while\n * still rendering the entries from the registries that did work. */\nexport interface MergedRegistryResult {\n name: string;\n status: \"ok\" | \"stale\" | \"failed\";\n generatedAt: string | null;\n error: string | null;\n entries: RegistryEntry[];\n}\n\n/** Fetch every configured registry in parallel and return per-registry\n * outcomes. Callers (the Discover route) concatenate `entries` from each.\n * Failure of any single registry doesn't abort the others — that's the point\n * of supporting multiple registries. */\nexport async function fetchAllRegistries(\n opts: { force?: boolean; nowMs?: number; loader?: IndexLoader } & RegistryScope = {},\n): Promise<MergedRegistryResult[]> {\n const descriptors = listRegistries(opts);\n return await Promise.all(\n descriptors.map(async (descriptor): Promise<MergedRegistryResult> => {\n const result = await fetchRegistryIndex(descriptor, opts);\n if (!result.ok) {\n return { name: descriptor.name, status: \"failed\", generatedAt: null, error: result.error, entries: [] };\n }\n return {\n name: descriptor.name,\n status: result.stale ? \"stale\" : \"ok\",\n generatedAt: result.index.generatedAt,\n error: null,\n entries: result.index.collections,\n };\n }),\n );\n}\n\n/** Test seam: reset the module cache + failure backoff state (all registries). */\nexport function resetRegistryCache(): void {\n cache.clear();\n lastFailureMs.clear();\n}\n","// Fetch individual files of a registry collection (schema.json, meta.json, …)\n// from the registry repo's raw content. The collection's repo-relative dir comes\n// from a trusted index entry (never raw user input), so the host can only fetch\n// files of collections that actually appear in the published index.\n//\n// The index itself is served from GitHub Pages (see client.ts); per-collection\n// files are served from raw.githubusercontent. With multi-registry support the\n// rawBase is no longer a single module-level value — it comes from the entry's\n// source registry descriptor, so user-added registries can live anywhere.\n\nimport { fetchWithTimeout } from \"../../../utils/fetch.js\";\nimport { errorMessage, ONE_SECOND_MS } from \"../../server/util.js\";\nimport { isRecord } from \"../guards.js\";\nimport { fetchAllRegistries, findRegistry } from \"./client.js\";\nimport type { RegistryScope } from \"./registriesConfig.js\";\nimport type { RegistryEntry } from \"../registryIndex.js\";\n\nconst FETCH_TIMEOUT_MS = 10 * ONE_SECOND_MS;\nconst STATUS_BAD_GATEWAY = 502;\nconst STATUS_UNAVAILABLE = 503;\nconst STATUS_NOT_FOUND = 404;\n\n/** Compose the raw URL for `<dirPath>/<relFile>` under a given registry's\n * rawBase. Empty and traversal (`.`/`..`) segments are dropped so the URL can\n * never escape the base, even if an upstream check is bypassed (the index\n * parser already rejects such identifiers — this is defense-in-depth). The\n * rawBase is trailing-slash-normalized: `parseRegistriesConfig` already trims\n * user-config trailing slashes, but the official descriptor and any test\n * bypass parse — repeating the trim here keeps the join `${base}/${path}`\n * from producing `//` even when the caller bypassed config validation\n * (CodeRabbit review on #1837). */\nexport function collectionFileUrl(rawBase: string, dirPath: string, relFile: string): string {\n let base = rawBase;\n while (base.endsWith(\"/\")) base = base.slice(0, -1);\n const segments = dirPath.split(\"/\").filter((segment) => segment.length > 0 && segment !== \".\" && segment !== \"..\");\n return `${base}/${segments.join(\"/\")}/${relFile}`;\n}\n\nexport type FileResult = { ok: true; text: string } | { ok: false; status: number; error: string };\n\n/** Fetch one file out of a registry collection. `rawBase` comes from the\n * entry's source registry (`findRegistry(entry.registryName).rawBaseUrl`),\n * not a module-level setting — that's what makes additional user-configured\n * registries reachable. */\nexport async function fetchCollectionFile(rawBase: string, dirPath: string, relFile: string): Promise<FileResult> {\n const url = collectionFileUrl(rawBase, dirPath, relFile);\n let res: Response;\n try {\n res = await fetchWithTimeout(url, { timeoutMs: FETCH_TIMEOUT_MS });\n } catch (err) {\n return { ok: false, status: STATUS_UNAVAILABLE, error: `fetch failed: ${errorMessage(err)}` };\n }\n if (!res.ok) return { ok: false, status: STATUS_BAD_GATEWAY, error: `${relFile} responded ${res.status}` };\n return { ok: true, text: await res.text() };\n}\n\nexport type JsonObjectResult = { ok: true; value: Record<string, unknown> } | { ok: false; error: string };\n\nexport function parseJsonObject(text: string, label: string): JsonObjectResult {\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return { ok: false, error: `${label} is not valid JSON` };\n }\n if (!isRecord(parsed)) return { ok: false, error: `${label} is not an object` };\n return { ok: true, value: parsed };\n}\n\nasync function fetchJsonObject(\n rawBase: string,\n dirPath: string,\n relFile: string,\n label: string,\n): Promise<{ ok: true; value: Record<string, unknown> } | { ok: false; status: number; error: string }> {\n const file = await fetchCollectionFile(rawBase, dirPath, relFile);\n if (!file.ok) return { ok: false, status: file.status, error: `${label}: ${file.error}` };\n const parsed = parseJsonObject(file.text, label);\n if (!parsed.ok) return { ok: false, status: STATUS_BAD_GATEWAY, error: parsed.error };\n return { ok: true, value: parsed.value };\n}\n\n/** Resolve an entry's rawBase from its `registryName`. A missing match (the\n * user removed the registry from config while a cached index still references\n * it) returns null — the caller surfaces it as a 404 rather than crashing. */\nexport function rawBaseForEntry(entry: Pick<RegistryEntry, \"registryName\">, scope: RegistryScope = {}): string | null {\n const registry = findRegistry(entry.registryName, scope);\n return registry?.rawBaseUrl ?? null;\n}\n\nexport type PreviewResult =\n { ok: true; entry: RegistryEntry; schema: Record<string, unknown>; meta: Record<string, unknown> } | { ok: false; status: number; error: string };\n\n/** Resolve an entry by author+slug across every cached registry's entries.\n * When `registry` is passed we constrain to that registry — needed because\n * multiple registries can publish the same author/slug, and the UI should\n * follow the card it just clicked. */\nfunction findEntryInMergedView(\n merged: { name: string; entries: RegistryEntry[] }[],\n author: string,\n slug: string,\n registry: string | null,\n): RegistryEntry | null {\n for (const reg of merged) {\n if (registry !== null && reg.name !== registry) continue;\n const match = reg.entries.find((candidate) => candidate.author === author && candidate.slug === slug);\n if (match) return match;\n }\n return null;\n}\n\n/** Preview a registry collection: confirm it's in some registry's published\n * index, then fetch + parse its schema.json + meta.json so the Discover tab\n * can show fields/views before import. With multi-registry support the\n * `registry` arg disambiguates same-name collections from different sources. */\nexport async function previewCollection(author: string, slug: string, registry: string | null = null, scope: RegistryScope = {}): Promise<PreviewResult> {\n const merged = await fetchAllRegistries(scope);\n const entry = findEntryInMergedView(merged, author, slug, registry);\n if (!entry) return { ok: false, status: STATUS_NOT_FOUND, error: `unknown collection: ${author}/${slug}` };\n const rawBase = rawBaseForEntry(entry, scope);\n if (!rawBase) return { ok: false, status: STATUS_NOT_FOUND, error: `registry \"${entry.registryName}\" is no longer configured` };\n const schema = await fetchJsonObject(rawBase, entry.path, \"schema.json\", \"schema.json\");\n if (!schema.ok) return schema;\n const meta = await fetchJsonObject(rawBase, entry.path, \"meta.json\", \"meta.json\");\n if (!meta.ok) return meta;\n return { ok: true, entry, schema: schema.value, meta: meta.value };\n}\n","// Import-side fetch + transform helpers for a registry collection. The writer\n// (writes into .claude/skills/, materializes seed, records provenance) builds on\n// these; kept separate so the pure, security-critical parts are unit-tested.\n//\n// Files to fetch come from the collection's manifest.json (published by the\n// registry's build-index). Every manifest path is re-checked for safety here —\n// the host must never write outside the target skill dir even if the manifest is\n// malformed/poisoned.\n\nimport { isRecord } from \"../guards.js\";\nimport { fetchCollectionFile, parseJsonObject, rawBaseForEntry } from \"./collectionFiles.js\";\nimport type { RegistryEntry } from \"../registryIndex.js\";\nimport type { RegistryScope } from \"./registriesConfig.js\";\n\nconst MANIFEST_FILE = \"manifest.json\";\nconst STATUS_BAD_GATEWAY = 502;\n\n/** A manifest entry must be a relative path that stays inside the collection dir:\n * no absolute paths, no backslashes, no empty / `.` / `..` segments. */\nexport function isSafeBundlePath(rel: unknown): rel is string {\n if (typeof rel !== \"string\" || rel.length === 0) return false;\n if (rel.startsWith(\"/\") || rel.includes(\"\\\\\")) return false;\n return !rel.split(\"/\").some((segment) => segment === \"\" || segment === \".\" || segment === \"..\");\n}\n\nexport type ManifestResult = { ok: true; files: string[] } | { ok: false; error: string };\n\nexport function parseManifest(value: unknown): ManifestResult {\n if (!isRecord(value) || !Array.isArray(value.files)) return { ok: false, error: \"manifest is missing a files[] array\" };\n const unsafe = value.files.find((file) => !isSafeBundlePath(file));\n if (unsafe !== undefined) return { ok: false, error: `manifest contains an unsafe path: ${String(unsafe)}` };\n return { ok: true, files: value.files.filter(isSafeBundlePath) };\n}\n\n/** `data/collections/<localSlug>/items` — the host owns dataPath, never the\n * registry's authored value, so imported collections can't collide on disk. */\nexport function normalizedDataPath(localSlug: string): string {\n return `data/collections/${localSlug}/items`;\n}\n\nexport function withNormalizedDataPath(schema: Record<string, unknown>, localSlug: string): Record<string, unknown> {\n return { ...schema, dataPath: normalizedDataPath(localSlug) };\n}\n\nexport type ManifestFetch = { ok: true; files: string[] } | { ok: false; status: number; error: string };\n\nexport async function fetchManifest(entry: RegistryEntry, scope: RegistryScope = {}): Promise<ManifestFetch> {\n const rawBase = rawBaseForEntry(entry, scope);\n if (!rawBase) return { ok: false, status: STATUS_BAD_GATEWAY, error: `registry \"${entry.registryName}\" is no longer configured` };\n const file = await fetchCollectionFile(rawBase, entry.path, MANIFEST_FILE);\n if (!file.ok) return { ok: false, status: file.status, error: `manifest.json: ${file.error}` };\n const obj = parseJsonObject(file.text, \"manifest.json\");\n if (!obj.ok) return { ok: false, status: STATUS_BAD_GATEWAY, error: obj.error };\n const manifest = parseManifest(obj.value);\n if (!manifest.ok) return { ok: false, status: STATUS_BAD_GATEWAY, error: manifest.error };\n return { ok: true, files: manifest.files };\n}\n\nexport type BundleFetch = { ok: true; files: Map<string, string> } | { ok: false; status: number; error: string };\n\n/** Fetch every manifest file. Paths are already safety-checked by parseManifest. */\nexport async function fetchBundle(entry: RegistryEntry, fileList: readonly string[], scope: RegistryScope = {}): Promise<BundleFetch> {\n const rawBase = rawBaseForEntry(entry, scope);\n if (!rawBase) return { ok: false, status: STATUS_BAD_GATEWAY, error: `registry \"${entry.registryName}\" is no longer configured` };\n const files = new Map<string, string>();\n for (const rel of fileList) {\n const file = await fetchCollectionFile(rawBase, entry.path, rel);\n if (!file.ok) return { ok: false, status: file.status, error: `${rel}: ${file.error}` };\n files.set(rel, file.text);\n }\n return { ok: true, files };\n}\n","// Import a registry collection into the active workspace. The writer fetches\n// the bundle, re-validates the schema with the host's own gates (R7 — the\n// index is not a trust boundary), writes the bundle into `data/skills/<localSlug>/`\n// with a host-owned dataPath (R3), materializes any seed records into that\n// dataPath when it's empty, and records provenance in `.origin.json` for\n// update detection (R5/R8). After the data-skills swap, the bundle's\n// allowlisted files (SKILL.md, schema.json, templates/<safe>) are mirrored\n// 1:1 into `.claude/skills/<localSlug>/` via the same skill-bridge rules an\n// agent-authored skill would go through — so an authored and an imported\n// collection live in EXACTLY the same place on disk, and the user can edit\n// either one identically.\n//\n// `.origin.json` lives ONLY in `data/skills/<slug>/.origin.json` and is NOT\n// mirrored — the skill-bridge allowlist deliberately excludes host\n// bookkeeping. Its presence is what distinguishes \"imported\" from\n// \"user-authored\" in every downstream tool.\n//\n// `writeImportedCollection` takes the already-fetched bundle + an explicit\n// workspaceRoot/clock so it is unit-testable against a temp workspace with\n// no network; `performImport` is the thin glue that fetches and calls it.\n\nimport { mkdir, readFile, readdir, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport type { Dirent } from \"node:fs\";\nimport path from \"node:path\";\n\nimport { acceptParsedSchema, CollectionSchemaZ, isSafeActionTemplatePath, safeRecordId } from \"../../server/index.js\";\nimport type { CollectionSchema } from \"../../index.js\";\nimport { claudeSkillDir, dataSkillDir, mirrorSkillWrite } from \"../../../skill-bridge/index.js\";\n\nimport { log, projectSkillsDir, skillsStagingDir } from \"../../server/host.js\";\nimport { errorMessage } from \"../../server/util.js\";\nimport { writeFileAtomic } from \"../../../files/atomic.js\";\nimport { isRecord } from \"../guards.js\";\nimport { fetchAllRegistries } from \"./client.js\";\nimport { fetchBundle, fetchManifest, normalizedDataPath } from \"./importCollection.js\";\nimport type { RegistryEntry } from \"../registryIndex.js\";\n\nconst ORIGIN_FILE = \".origin.json\";\nconst SEED_PREFIX = \"seed/items/\";\nconst SCHEMA_FILE = \"schema.json\";\nconst SKILL_FILE = \"SKILL.md\";\nconst STATUS_NOT_FOUND = 404;\nconst STATUS_CONFLICT = 409;\nconst STATUS_UNPROCESSABLE = 422;\n\nexport interface ImportOrigin {\n registry: string;\n author: string;\n slug: string;\n version: string;\n contentSha: string;\n importedAt: string;\n}\n\nexport type ImportResult =\n { ok: true; localSlug: string; updated: boolean; seedWritten: number; seedSkipped: boolean } | { ok: false; status: number; error: string };\n\nasync function statType(target: string): Promise<\"dir\" | \"other\" | \"absent\"> {\n try {\n return (await stat(target)).isDirectory() ? \"dir\" : \"other\";\n } catch (err) {\n // EACCES etc. are path-shape conflicts mkdir would later throw on → \"other\".\n // A file ancestor surfaces as ENOTDIR on POSIX but ENOENT on Windows (which\n // reports the whole path as missing), so ENOENT can't be trusted as \"absent\"\n // until we've confirmed no existing ancestor is a non-directory file.\n if (!(isRecord(err) && err.code === \"ENOENT\")) return \"other\";\n return (await hasNonDirAncestor(target)) ? \"other\" : \"absent\";\n }\n}\n\n// Walk up from `target` to the nearest ancestor that exists: a non-directory\n// file there means `target` can never be mkdir'd (a path-shape conflict → 409),\n// whichever errno the platform's `stat(target)` chose. An all-absent-or-directory\n// chain up to the filesystem root is a genuine \"absent\".\nasync function hasNonDirAncestor(target: string): Promise<boolean> {\n let current = path.dirname(target);\n while (current !== path.dirname(current)) {\n try {\n return !(await stat(current)).isDirectory();\n } catch (err) {\n if (isRecord(err) && err.code === \"ENOENT\") {\n current = path.dirname(current);\n continue;\n }\n return true;\n }\n }\n return false;\n}\n\nasync function isEmptyOrAbsentDir(target: string): Promise<boolean> {\n try {\n return (await readdir(target)).length === 0;\n } catch {\n return true;\n }\n}\n\nfunction originMatches(origin: unknown, registry: string, author: string, slug: string): boolean {\n return isRecord(origin) && origin.registry === registry && origin.author === author && origin.slug === slug;\n}\n\nasync function readOrigin(targetDir: string): Promise<unknown> {\n try {\n return JSON.parse(await readFile(path.join(targetDir, ORIGIN_FILE), \"utf-8\"));\n } catch {\n return null;\n }\n}\n\ntype TargetResolution = { targetDir: string; localSlug: string; updated: boolean } | { conflict: string };\n\n// Only bounds the fresh-slug search (a safety cap, far above any realistic\n// number of same-named collections — the first free slug is normally found in\n// 1–2 iterations). An EXISTING install is found via the directory scan below,\n// not this loop, so updates are never missed regardless of how high the rename\n// suffix is.\nconst MAX_SLUG_ATTEMPTS = 10000;\n\nconst renameCandidate = (slug: string, attempt: number): string => (attempt === 0 ? slug : `${slug}-${attempt + 1}`);\n\n// True when `name` is the registry slug or a `<slug>-<n>` rename of it.\nfunction isRenameOf(name: string, slug: string): boolean {\n if (name === slug) return true;\n if (!name.startsWith(`${slug}-`)) return false;\n const suffix = name.slice(slug.length + 1);\n return suffix.length > 0 && /^\\d+$/.test(suffix);\n}\n\n// Find an existing install of this registry collection at ANY rename suffix\n// by scanning `data/skills/` — independent of any candidate bound, so a\n// re-import always updates the existing install rather than duplicating it\n// (even if an earlier slug freed up). An authored skill without `.origin.json`\n// is invisible to this scan and so never collides with an update.\nasync function findMatchingInstall(skillsDir: string, registry: string, entry: RegistryEntry): Promise<string | null> {\n const names = await readdir(skillsDir).catch((): string[] => []);\n for (const name of names) {\n if (!isRenameOf(name, entry.slug)) continue;\n const dir = path.join(skillsDir, name);\n if ((await statType(dir)) === \"dir\" && originMatches(await readOrigin(dir), registry, entry.author, entry.slug)) return name;\n }\n return null;\n}\n\n// Pick the local install slug (rename-on-collision, R8). First reuse an\n// existing matching install (update). Otherwise install fresh at the first\n// free slug — the registry slug, else `<slug>-2`, `-3`, … — never clobbering\n// a user's own same-named collection.\n//\n// A slug is \"free\" only when BOTH `data/skills/<slug>/` AND\n// `.claude/skills/<slug>/` are absent. The mirror check matters because\n// `mirrorToClaudeSkills` overwrites the mirror dir's allowlisted files and\n// prunes anything else — if the user has a manually-installed Claude skill\n// at `.claude/skills/<slug>/` with no corresponding `data/skills/<slug>/`\n// (e.g. installed by the Claude CLI directly, or a legacy pre-refactor\n// import we never migrated), picking that slug would silently overwrite\n// their SKILL.md/schema.json and prune the rest of their files (CodeRabbit\n// review on #1839).\n/** Where an import INSTALLS in this root, and whether a mirror step follows.\n *\n * With a staging tree the install is the usual two-step: write\n * `data/skills/<slug>/` and mirror the allowlist into `.claude/skills/<slug>/`.\n * A root with NO staging tree has no bridge to mirror through, so the active\n * skill dir IS the install — and installing into `data/skills/` there would be\n * worse than useless: the mirror makes the collection discoverable, but its\n * `.origin.json` and every non-allowlisted asset (notably `views/`) stay in a\n * tree that reads and deletes now deliberately ignore. Update would not find\n * the provenance, and delete would leave the install behind. */\nfunction importInstall(workspaceRoot: string): { skillsDir: string; mirrors: boolean } {\n const staging = skillsStagingDir(workspaceRoot);\n return staging === null ? { skillsDir: projectSkillsDir(workspaceRoot), mirrors: false } : { skillsDir: staging, mirrors: true };\n}\n\nconst installDir = (workspaceRoot: string, slug: string): string => path.join(importInstall(workspaceRoot).skillsDir, slug);\n\nasync function resolveTarget(workspaceRoot: string, registry: string, entry: RegistryEntry): Promise<TargetResolution> {\n const { skillsDir, mirrors } = importInstall(workspaceRoot);\n const existing = await findMatchingInstall(skillsDir, registry, entry);\n if (existing) return { targetDir: path.join(skillsDir, existing), localSlug: existing, updated: true };\n for (let attempt = 0; attempt < MAX_SLUG_ATTEMPTS; attempt += 1) {\n const localSlug = renameCandidate(entry.slug, attempt);\n const sourceAbsent = (await statType(path.join(skillsDir, localSlug))) === \"absent\";\n // Without a mirror the two checks are the same directory, so the install\n // dir being free is the whole condition.\n const mirrorAbsent = !mirrors || (await statType(claudeSkillDir(workspaceRoot, localSlug))) === \"absent\";\n if (sourceAbsent && mirrorAbsent) {\n return { targetDir: path.join(skillsDir, localSlug), localSlug, updated: false };\n }\n }\n return { conflict: `couldn't find an available slug for '${entry.slug}'` };\n}\n\ntype SchemaResolution = { schema: CollectionSchema } | { error: string };\n\nfunction validateAndNormalize(bundle: Map<string, string>, localSlug: string, workspaceRoot: string): SchemaResolution {\n // SKILL.md is required: `mirrorToClaudeSkills` calls\n // `mirrorSkillWrite({slug, relSegments: [SKILL_FILE]})` unconditionally\n // after the data-side swap, which throws if SKILL.md is missing. Caught\n // outside, that throw is logged but the data-side write still succeeds and\n // the function returns `ok: true` for an unusable import. Reject up front\n // so a missing SKILL.md surfaces as a clean 422 instead (CodeRabbit review\n // on #1839).\n if (bundle.get(SKILL_FILE) === undefined) return { error: \"bundle is missing SKILL.md\" };\n const schemaText = bundle.get(SCHEMA_FILE);\n if (schemaText === undefined) return { error: \"bundle is missing schema.json\" };\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(schemaText);\n } catch {\n return { error: \"schema.json is not valid JSON\" };\n }\n const parsed = CollectionSchemaZ.safeParse(parsedJson);\n if (!parsed.success) return { error: `schema.json failed validation: ${parsed.error.issues[0]?.message ?? \"invalid\"}` };\n // A `dataSource` collection can't be imported: its records are the rows of\n // a machine-local data file the bundle doesn't (and can't) carry — and the\n // R3 dataPath stamp below would collide with the exactly-one-of rule.\n if (parsed.data.dataSource !== undefined) {\n return { error: \"schema.json declares `dataSource` — external-data collections are machine-local and cannot be imported from a registry\" };\n }\n const schema: CollectionSchema = { ...parsed.data, dataPath: normalizedDataPath(localSlug) };\n const acceptance = acceptParsedSchema(schema, { source: \"project\", workspaceRoot, slug: localSlug });\n if (!acceptance.ok) return { error: `schema.json rejected: ${acceptance.reason}` };\n return { schema };\n}\n\nasync function writeBundleFiles(targetDir: string, bundle: Map<string, string>, schema: CollectionSchema): Promise<void> {\n for (const [rel, content] of bundle) {\n if (rel.startsWith(SEED_PREFIX)) continue; // seed goes to dataPath, not the skill dir\n const dest = path.join(targetDir, ...rel.split(\"/\"));\n if (dest !== targetDir && !dest.startsWith(targetDir + path.sep)) continue; // belt-and-suspenders (paths pre-validated)\n await mkdir(path.dirname(dest), { recursive: true });\n await writeFile(dest, rel === SCHEMA_FILE ? `${JSON.stringify(schema, null, 2)}\\n` : content, \"utf-8\");\n }\n}\n\nasync function materializeSeed(dataDir: string, bundle: Map<string, string>): Promise<{ written: number; skipped: boolean }> {\n const seedEntries = [...bundle].filter(([rel]) => rel.startsWith(SEED_PREFIX));\n if (seedEntries.length === 0) return { written: 0, skipped: false };\n if (!(await isEmptyOrAbsentDir(dataDir))) return { written: 0, skipped: true };\n await mkdir(dataDir, { recursive: true });\n let written = 0;\n for (const [rel, content] of seedEntries) {\n const fileName = rel.slice(SEED_PREFIX.length);\n if (fileName.includes(\"/\") || safeRecordId(fileName.replace(/\\.json$/, \"\")) === null) {\n log.warn(\"collections-registry\", \"skipped unsafe seed record\", { rel });\n continue;\n }\n await writeFile(path.join(dataDir, fileName), content, \"utf-8\");\n written += 1;\n }\n return { written, skipped: false };\n}\n\n/** Compute the set of bundle-relative paths that should land in `.claude/skills/<slug>/`.\n * Matches the bridge allowlist exactly — SKILL.md, schema.json,\n * `templates/<safe>` — so anything else (seed, meta.json, views, README,\n * assets) stays source-side. */\nfunction bridgeAllowlistFiles(bundle: Map<string, string>): Set<string> {\n const wanted = new Set<string>([SKILL_FILE, SCHEMA_FILE]);\n for (const rel of bundle.keys()) {\n if (rel === SKILL_FILE || rel === SCHEMA_FILE) continue;\n if (rel.startsWith(SEED_PREFIX)) continue;\n if (!isSafeActionTemplatePath(rel)) continue;\n wanted.add(rel);\n }\n return wanted;\n}\n\n/** Recursively list every file under `root`, returned as forward-slash paths\n * relative to `root`. Empty list when `root` doesn't exist. Used to spot\n * mirror files left over from a previous install so we can prune them after\n * writing the new ones. */\nasync function listFilesRecursive(root: string, prefix = \"\"): Promise<string[]> {\n const dir = prefix ? path.join(root, ...prefix.split(\"/\")) : root;\n const entries: Dirent[] = await readdir(dir, { withFileTypes: true }).catch((): Dirent[] => []);\n const out: string[] = [];\n for (const entry of entries) {\n const rel = prefix ? `${prefix}/${entry.name}` : entry.name;\n if (entry.isFile()) out.push(rel);\n else if (entry.isDirectory()) out.push(...(await listFilesRecursive(root, rel)));\n }\n return out;\n}\n\n// Mirror the just-written `data/skills/<slug>/` into `.claude/skills/<slug>/`\n// using the shared bridge package — exactly the set of files an\n// agent-authored skill would mirror, no more (SKILL.md, schema.json,\n// templates/<safe>). `.origin.json` is host bookkeeping and stays only on the\n// data side.\n//\n// **Write-then-prune ordering** (Codex review on #1839): write the new\n// bundle's allowlisted files FIRST (each is a per-file tmp+rename, atomic),\n// THEN prune mirror files that aren't in the new bundle. A transient mirror\n// failure during the writes leaves the previous mirror's files in place —\n// agent discovery keeps working with the prior state instead of finding an\n// empty `.claude/skills/<slug>/`. The prune step is best-effort: a failure\n// there leaves harmless stale leftovers but doesn't break the new install.\n// Compare with the old `mirrorSkillDelete`-first ordering, which could\n// silently leave an empty mirror if a subsequent write threw.\nasync function mirrorToClaudeSkills(workspaceRoot: string, localSlug: string, bundle: Map<string, string>): Promise<void> {\n const wanted = bridgeAllowlistFiles(bundle);\n // 1. Write the new set first (overwrites existing via tmp+rename).\n for (const rel of wanted) {\n mirrorSkillWrite(workspaceRoot, { slug: localSlug, relSegments: rel.split(\"/\") });\n }\n // 2. Prune files that exist in the mirror but aren't in the new bundle.\n const mirrorRoot = claudeSkillDir(workspaceRoot, localSlug);\n const existing = await listFilesRecursive(mirrorRoot);\n for (const rel of existing) {\n if (wanted.has(rel)) continue;\n await rm(path.join(mirrorRoot, ...rel.split(\"/\")), { force: true }).catch(() => undefined);\n }\n}\n\nexport async function writeImportedCollection(params: {\n registry: string;\n entry: RegistryEntry;\n bundle: Map<string, string>;\n workspaceRoot: string;\n nowIso: string;\n}): Promise<ImportResult> {\n const { registry, entry, bundle, workspaceRoot, nowIso } = params;\n const target = await resolveTarget(workspaceRoot, registry, entry);\n if (\"conflict\" in target) return { ok: false, status: STATUS_CONFLICT, error: target.conflict };\n\n const { localSlug } = target;\n\n // Pre-flight the data dir before schema validation: a non-directory at the dataPath\n // (or an ancestor that's a file → ENOTDIR) would otherwise surface as a generic 500\n // on mkdir. statType maps ENOTDIR/other to a deterministic 409 path-shape conflict.\n const dataDir = path.join(workspaceRoot, ...normalizedDataPath(localSlug).split(\"/\"));\n if ((await statType(dataDir)) === \"other\") {\n return { ok: false, status: STATUS_CONFLICT, error: `data path for slug '${localSlug}' exists and is not a directory` };\n }\n\n const validated = validateAndNormalize(bundle, localSlug, workspaceRoot);\n if (\"error\" in validated) return { ok: false, status: STATUS_UNPROCESSABLE, error: validated.error };\n\n // Build the replacement fully in a hidden sibling staging dir under\n // `data/skills/` (bundle + origin), so the prior install is untouched until\n // everything is durably written. Leftover staging/backup dirs from a\n // crashed import are cleaned first, keeping retries possible.\n const skillsParent = path.dirname(target.targetDir);\n await mkdir(skillsParent, { recursive: true }); // first-import case — data/skills/ may not exist\n const staging = path.join(skillsParent, `.importing-${localSlug}`);\n const backup = path.join(skillsParent, `.backup-${localSlug}`);\n await rm(staging, { recursive: true, force: true });\n await rm(backup, { recursive: true, force: true });\n await mkdir(staging, { recursive: true });\n await writeBundleFiles(staging, bundle, validated.schema);\n const origin: ImportOrigin = { registry, author: entry.author, slug: entry.slug, version: entry.version, contentSha: entry.contentSha, importedAt: nowIso };\n await writeFileAtomic(path.join(staging, ORIGIN_FILE), `${JSON.stringify(origin, null, 2)}\\n`);\n\n // Swap with rollback: move the old install aside (rename), move the new in\n // (rename), then discard the old. If the swap fails, restore the old so we\n // never end up with no installed collection. Records live in dataPath (a\n // separate dir) and are untouched.\n if (target.updated) await rename(target.targetDir, backup);\n try {\n await rename(staging, target.targetDir);\n } catch (err) {\n if (target.updated) await rename(backup, target.targetDir).catch(() => undefined);\n throw err;\n }\n await rm(backup, { recursive: true, force: true });\n\n // Replicate the new `data/skills/<slug>/` set into `.claude/skills/<slug>/`\n // via the shared bridge package — same allowlist + tmp+rename semantics the\n // hook uses for agent-authored writes. Write-then-prune ordering inside\n // mirrorToClaudeSkills means a transient failure here can't leave the mirror\n // empty (Codex review on #1839); the worst case is a half-written mirror\n // where the new install's SKILL.md/schema.json may not have updated, but the\n // prior install remains accessible to the agent until the next mirror\n // attempt. Logged-not-thrown matches the hook's posture for agent writes.\n try {\n // Skipped where the install already IS the active skill dir — there is\n // nothing to mirror to, and `mirrorSkillWrite` would read a staging file\n // that never exists.\n if (importInstall(workspaceRoot).mirrors) await mirrorToClaudeSkills(workspaceRoot, localSlug, bundle);\n } catch (err) {\n log.warn(\"collections-registry\", \"mirror to .claude/skills/ failed (data/skills write succeeded; prior mirror left intact)\", {\n localSlug,\n error: errorMessage(err),\n });\n }\n\n const seed = await materializeSeed(dataDir, bundle);\n return { ok: true, localSlug, updated: target.updated, seedWritten: seed.written, seedSkipped: seed.skipped };\n}\n\nexport async function performImport(author: string, slug: string, workspaceRoot: string, registry: string | null = null): Promise<ImportResult> {\n // The import's target workspace is also the registry scope — a multi-root\n // host's per-project `collections-registries.json` must be the one consulted.\n const merged = await fetchAllRegistries({ workspaceRoot });\n let entry: RegistryEntry | undefined;\n for (const reg of merged) {\n if (registry !== null && reg.name !== registry) continue;\n entry = reg.entries.find((candidate) => candidate.author === author && candidate.slug === slug);\n if (entry) break;\n }\n if (!entry) return { ok: false, status: STATUS_NOT_FOUND, error: `unknown collection: ${author}/${slug}` };\n const manifest = await fetchManifest(entry, { workspaceRoot });\n if (!manifest.ok) return { ok: false, status: manifest.status, error: manifest.error };\n const bundle = await fetchBundle(entry, manifest.files, { workspaceRoot });\n if (!bundle.ok) return { ok: false, status: bundle.status, error: bundle.error };\n try {\n return await writeImportedCollection({\n registry: entry.registryName,\n entry,\n bundle: bundle.files,\n workspaceRoot,\n nowIso: new Date().toISOString(),\n });\n } catch (err) {\n log.warn(\"collections-registry\", \"import write failed\", { author, slug, registry: entry.registryName, error: errorMessage(err) });\n return { ok: false, status: 500, error: `import failed: ${errorMessage(err)}` };\n }\n}\n\n// Exported for downstream code that wants the path conventions without\n// importing skill-bridge directly.\nexport { claudeSkillDir, dataSkillDir, installDir, hasNonDirAncestor };\n","// Export a workspace collection into the curated registry's contribution layout\n// (collections/<author>/<slug>/ + meta.json + optional seed/) under\n// data/registry-export/, so the user can open a PR to the registry. The producer\n// side of the registry loop.\n//\n// writeCollectionExport takes explicit dirs + a workspace root so it is unit-testable\n// against temp dirs with no discovery/network; performExport (separate file) is the\n// thin glue that resolves the collection from the live workspace.\n\nimport { cp, mkdir, readdir, readFile, rm, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { log } from \"../../server/host.js\";\nimport { toPosixRelPath } from \"../../../files/relPath.js\";\n\nexport const EXPORT_BASE = \"data/registry-export\";\nconst BUNDLE_FILES = [\"SKILL.md\", \"schema.json\"] as const;\nconst OPTIONAL_FILES = [\"screenshot.png\"] as const;\nconst BUNDLE_DIRS = [\"views\", \"templates\"] as const;\nconst AUTHOR_RE = /^[A-Za-z0-9][A-Za-z0-9-]{0,38}$/;\nconst SLUG_RE = /^[a-z0-9][a-z0-9-]{0,62}$/;\nconst STATUS_BAD_REQUEST = 400;\n// Block obvious credentials from being published in seed data; PII is only warned.\nconst SECRET_PATTERNS = [\n /AKIA[0-9A-Z]{16}/,\n /gh[pousr]_[A-Za-z0-9]{20,}/,\n /-----BEGIN [A-Z ]*PRIVATE KEY-----/,\n /sk-[A-Za-z0-9]{20,}/,\n /xox[baprs]-[A-Za-z0-9-]{10,}/,\n];\nconst hasSecret = (text: string): boolean => SECRET_PATTERNS.some((pattern) => pattern.test(text));\n// Bounded + non-overlapping (no ReDoS): local@label.tld with single quantifier runs.\nconst EMAIL_RE = /[\\w.%+-]{1,64}@[\\w-]{1,255}\\.[a-z]{2,24}/i;\n\nexport interface ExportMeta {\n author: string;\n slug: string;\n version: string;\n title: string;\n description: string;\n tags: string[];\n license: string;\n}\n\nexport type ExportResult =\n | { ok: true; outputPath: string; fileCount: number; seedCount: number; seedSkipped: number; warnings: string[] }\n | { ok: false; status: number; error: string };\n\nasync function pathKind(target: string): Promise<\"dir\" | \"file\" | \"absent\"> {\n try {\n return (await stat(target)).isDirectory() ? \"dir\" : \"file\";\n } catch {\n return \"absent\";\n }\n}\n\nasync function copyBundle(skillDir: string, outDir: string): Promise<number> {\n let count = 0;\n for (const file of [...BUNDLE_FILES, ...OPTIONAL_FILES]) {\n const src = path.join(skillDir, file);\n if ((await pathKind(src)) !== \"file\") continue;\n await cp(src, path.join(outDir, file));\n count += 1;\n }\n for (const dir of BUNDLE_DIRS) {\n const src = path.join(skillDir, dir);\n if ((await pathKind(src)) !== \"dir\") continue;\n await cp(src, path.join(outDir, dir), { recursive: true });\n count += (await readdir(src)).filter((name) => !name.startsWith(\".\")).length;\n }\n return count;\n}\n\nasync function exportSeed(dataDir: string, outDir: string): Promise<{ count: number; skipped: number; warnings: string[] }> {\n let names: string[];\n try {\n names = (await readdir(dataDir)).filter((name) => name.endsWith(\".json\"));\n } catch {\n return { count: 0, skipped: 0, warnings: [] };\n }\n if (names.length === 0) return { count: 0, skipped: 0, warnings: [] };\n const seedDir = path.join(outDir, \"seed\", \"items\");\n await mkdir(seedDir, { recursive: true });\n const warnings: string[] = [];\n let count = 0;\n let skipped = 0;\n for (const name of names) {\n const base = path.basename(name);\n const text = await readFile(path.join(dataDir, base), \"utf-8\");\n if (hasSecret(text)) {\n warnings.push(`skipped ${base}: contains a possible credential`);\n skipped += 1;\n continue;\n }\n if (EMAIL_RE.test(text)) warnings.push(`${base}: contains a possible email/PII (kept — your responsibility)`);\n await writeFile(path.join(seedDir, base), text, \"utf-8\");\n count += 1;\n }\n return { count, skipped, warnings };\n}\n\n// Resolve `segments` under `root` and return the absolute path only when it stays\n// inside `root` — a containment barrier for the user-derived author/slug and the\n// collection dirs, on top of the AUTHOR_RE/SLUG_RE format checks. `..` or an\n// absolute escape resolves outside `root` and yields null (rejected upstream).\nfunction resolveWithin(root: string, ...segments: string[]): string | null {\n const resolvedRoot = path.resolve(root);\n const target = path.resolve(resolvedRoot, ...segments);\n return target === resolvedRoot || target.startsWith(resolvedRoot + path.sep) ? target : null;\n}\n\n// Required bundle files (SKILL.md, schema.json) must be present — exporting an\n// incomplete bundle would pass here but fail downstream registry curation.\n// Returns the first missing required file, or null when all are present.\nasync function findMissingRequired(skillDir: string): Promise<string | null> {\n for (const file of BUNDLE_FILES) {\n if ((await pathKind(path.join(skillDir, file))) !== \"file\") return file;\n }\n return null;\n}\n\nexport async function writeCollectionExport(params: {\n workspaceRoot: string;\n skillDir: string;\n dataDir: string;\n meta: ExportMeta;\n includeSeed: boolean;\n}): Promise<ExportResult> {\n const { workspaceRoot, skillDir, dataDir, meta, includeSeed } = params;\n if (!AUTHOR_RE.test(meta.author)) return { ok: false, status: STATUS_BAD_REQUEST, error: `author '${meta.author}' is not a valid GitHub login` };\n if (!SLUG_RE.test(meta.slug)) return { ok: false, status: STATUS_BAD_REQUEST, error: `slug '${meta.slug}' is invalid` };\n\n const wsRoot = path.resolve(workspaceRoot);\n const exportRoot = path.join(wsRoot, ...EXPORT_BASE.split(\"/\"));\n const outDir = resolveWithin(exportRoot, meta.author, meta.slug);\n const safeSkillDir = resolveWithin(wsRoot, skillDir);\n const safeDataDir = resolveWithin(wsRoot, dataDir);\n if (!outDir || !safeSkillDir || !safeDataDir) {\n return { ok: false, status: STATUS_BAD_REQUEST, error: \"resolved path escapes the workspace\" };\n }\n\n const missing = await findMissingRequired(safeSkillDir);\n if (missing) {\n return { ok: false, status: STATUS_BAD_REQUEST, error: `required bundle file missing: ${missing}` };\n }\n\n await rm(outDir, { recursive: true, force: true });\n await mkdir(outDir, { recursive: true });\n\n const bundleCount = await copyBundle(safeSkillDir, outDir);\n const seed = includeSeed ? await exportSeed(safeDataDir, outDir) : { count: 0, skipped: 0, warnings: [] };\n const metaOut = { ...meta, ...(seed.count > 0 ? { dataConsent: true } : {}) };\n await writeFile(path.join(outDir, \"meta.json\"), `${JSON.stringify(metaOut, null, 2)}\\n`, \"utf-8\");\n log.info(\"collections-registry\", \"exported collection\", { slug: meta.slug, author: meta.author, seedCount: seed.count });\n const outRel = toPosixRelPath(path.relative(wsRoot, outDir));\n return { ok: true, outputPath: outRel, fileCount: bundleCount + 1, seedCount: seed.count, seedSkipped: seed.skipped, warnings: seed.warnings };\n}\n","// `description:` reader for a SKILL.md YAML frontmatter envelope.\n//\n// The host (MulmoClaude) parses SKILL.md frontmatter with js-yaml, but\n// @mulmoclaude/core deliberately carries no YAML dependency and export only needs\n// the single `description` scalar for the registry meta.json (best-effort — it\n// defaults to \"\" when absent). Rather than pull in a YAML parser we scan the\n// envelope for the first `description:` line and resolve the common YAML scalar\n// forms so exported descriptions match what the host produced:\n// - double-quoted (\"…\") → unescaped, trailing inline comment ignored\n// - single-quoted ('…') → '' → ', trailing inline comment ignored\n// - plain (unquoted) → trailing \" #comment\" stripped (YAML comment rule)\n// Block scalars (`description: |`) are NOT expanded — they yield \"\" (skill\n// descriptions are single-line in practice).\n\nconst FENCE = \"---\";\nconst KEY = \"description:\";\nconst BLOCK_SCALAR_INDICATORS = new Set([\"|\", \">\", \"|-\", \">-\", \"|+\", \">+\"]);\n\nconst isYamlSpace = (char: string | undefined): boolean => char === \" \" || char === \"\\t\";\n\n// After a closing quote, YAML allows only whitespace and an optional \"#\" comment.\n// Anything else (e.g. `\"foo\" bar`) is malformed — js-yaml would throw, so we\n// reject (→ null → \"\") rather than silently truncate.\nconst isOnlyTrailingComment = (rest: string): boolean => {\n const trimmed = rest.trimStart();\n return trimmed === \"\" || trimmed.startsWith(\"#\");\n};\n\n// Escapes a description realistically carries inside a double-quoted scalar;\n// any other `\\x` collapses to the literal `x`.\nconst DOUBLE_QUOTE_ESCAPES: Record<string, string> = { n: \"\\n\", t: \"\\t\" };\nconst unescapeDoubleQuote = (next: string): string => DOUBLE_QUOTE_ESCAPES[next] ?? next;\n\n// The text a two-character escape pair at `index` stands for, or null when the\n// pair isn't an escape — which makes `index` an ordinary character (possibly the\n// closing quote). Both YAML quoted forms differ only in this one rule.\ntype EscapeReader = (value: string, index: number) => string | null;\n\n// Walk a quoted scalar from just past its opening quote to the closing one.\n// Returns null for a malformed scalar (no closing quote, or trailing non-comment\n// text) so the caller degrades to \"\" — matching the host's js-yaml behavior.\nfunction scanQuotedScalar(value: string, quote: string, readEscape: EscapeReader): string | null {\n const out: string[] = [];\n // `charAt` (not `value[i]`) so the scanner reads a plain `string`: every\n // index below is in range, and an out-of-range read would be \"\" either way.\n for (let i = 1; i < value.length; i += 1) {\n const escaped = readEscape(value, i);\n if (escaped !== null) {\n out.push(escaped);\n i += 1;\n continue;\n }\n const char = value.charAt(i);\n if (char === quote) return isOnlyTrailingComment(value.slice(i + 1)) ? out.join(\"\") : null;\n out.push(char);\n }\n return null; // unterminated\n}\n\n// A trailing backslash has nothing to escape, so it stays a literal backslash.\nconst readDoubleQuoteEscape: EscapeReader = (value, index) =>\n value.charAt(index) === \"\\\\\" && index + 1 < value.length ? unescapeDoubleQuote(value.charAt(index + 1)) : null;\n\n// The only escape in a single-quoted scalar is a doubled quote ('').\nconst readSingleQuoteEscape: EscapeReader = (value, index) => (value.charAt(index) === \"'\" && value[index + 1] === \"'\" ? \"'\" : null);\n\nfunction parseDoubleQuoted(value: string): string | null {\n return scanQuotedScalar(value, '\"', readDoubleQuoteEscape);\n}\n\nfunction parseSingleQuoted(value: string): string | null {\n return scanQuotedScalar(value, \"'\", readSingleQuoteEscape);\n}\n\n// Plain scalar: a \"#\" preceded by whitespace (or at the start) begins a YAML\n// comment; \"#\" not preceded by whitespace (e.g. \"C#\") is literal.\nfunction stripPlainComment(value: string): string {\n for (let i = 0; i < value.length; i += 1) {\n if (value[i] === \"#\" && (i === 0 || isYamlSpace(value[i - 1]))) return value.slice(0, i);\n }\n return value;\n}\n\n/** Extract the frontmatter `description` from raw SKILL.md text. Returns \"\" when\n * there's no `---` envelope, no `description:` key, or the value is a block\n * scalar indicator. */\nexport function parseSkillDescription(raw: string): string {\n const lines = raw.split(/\\r?\\n/);\n if (lines[0]?.trim() !== FENCE) return \"\";\n for (const line of lines.slice(1)) {\n if (line.trim() === FENCE) return \"\"; // end of envelope, key not found\n if (!line.startsWith(KEY)) continue;\n return resolveScalar(line.slice(KEY.length).trim());\n }\n return \"\";\n}\n\n/** Resolve the raw text after `description:` to its scalar value. */\nfunction resolveScalar(value: string): string {\n if (value === \"\" || BLOCK_SCALAR_INDICATORS.has(value)) return \"\";\n if (value.startsWith('\"')) return parseDoubleQuoted(value) ?? \"\";\n if (value.startsWith(\"'\")) return parseSingleQuoted(value) ?? \"\";\n return stripPlainComment(value).trim();\n}\n","// Thin glue for the collection export: resolve the collection from the live workspace\n// (its skill dir + data dir + schema), derive meta from SKILL.md front-matter / any\n// existing meta.json, then hand off to the testable writeCollectionExport. Kept\n// separate so the file-writing core stays import-light + unit-testable.\n\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { loadCollection } from \"../../server/index.js\";\nimport { isRecord } from \"../guards.js\";\nimport { parseSkillDescription } from \"./skillDescription.js\";\nimport { writeCollectionExport, type ExportMeta, type ExportResult } from \"./exportCollection.js\";\n\nconst STATUS_NOT_FOUND = 404;\nconst STATUS_UNPROCESSABLE = 422;\n\nasync function readJsonObject(file: string): Promise<Record<string, unknown> | null> {\n try {\n const parsed: unknown = JSON.parse(await readFile(file, \"utf-8\"));\n return isRecord(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\nexport async function performExport(\n slug: string,\n opts: { author: string; license: string; includeSeed: boolean },\n workspaceRoot: string,\n): Promise<ExportResult> {\n const collection = await loadCollection(slug);\n if (!collection) return { ok: false, status: STATUS_NOT_FOUND, error: `collection not found: ${slug}` };\n // Mirrors the import-side refusal: a `dataSource` collection's records are\n // a machine-local data file a bundle can't carry.\n if (collection.schema.dataSource !== undefined) {\n return { ok: false, status: STATUS_UNPROCESSABLE, error: `collection '${slug}' is backed by an external dataSource and cannot be exported to a registry` };\n }\n const skillMd = await readFile(path.join(collection.skillDir, \"SKILL.md\"), \"utf-8\").catch(() => \"\");\n const existingMeta = await readJsonObject(path.join(collection.skillDir, \"meta.json\"));\n const meta: ExportMeta = {\n author: opts.author,\n slug,\n version: typeof existingMeta?.version === \"string\" ? existingMeta.version : \"1.0.0\",\n title: collection.schema.title,\n description: parseSkillDescription(skillMd),\n tags: [],\n license: opts.license,\n };\n return writeCollectionExport({ workspaceRoot, skillDir: collection.skillDir, dataDir: collection.dataDir, meta, includeSeed: opts.includeSeed });\n}\n","// @mulmoclaude/core/collection/registry/server — node-only collection registry\n// engine (Discover catalog fetch/cache, import writer, export writer). Shares the\n// `configureCollectionHost` binding with @mulmoclaude/core/collection/server, so a\n// host wires the workspace + logger + paths ONCE and both engines work. The\n// isomorphic contract types live on @mulmoclaude/core/collection/registry.\n\nimport { fetchAllRegistries } from \"./client.js\";\nimport type { RegistryScope } from \"./registriesConfig.js\";\nimport { performImport } from \"./importWriter.js\";\nimport type { RegistryListResponse, RegistryImportResponse } from \"../types.js\";\n\nexport {\n CACHE_TTL_MS,\n STALE_RETRY_BACKOFF_MS,\n fetchAllRegistries,\n fetchRegistryIndex,\n findRegistry,\n listRegistries,\n resetRegistryCache,\n type FetchIndexResult,\n type IndexLoader,\n type MergedRegistryResult,\n type RegistryDescriptor,\n} from \"./client.js\";\nexport {\n collectionFileUrl,\n fetchCollectionFile,\n parseJsonObject,\n previewCollection,\n rawBaseForEntry,\n type FileResult,\n type JsonObjectResult,\n type PreviewResult,\n} from \"./collectionFiles.js\";\nexport {\n fetchBundle,\n fetchManifest,\n isSafeBundlePath,\n normalizedDataPath,\n parseManifest,\n withNormalizedDataPath,\n type BundleFetch,\n type ManifestFetch,\n type ManifestResult,\n} from \"./importCollection.js\";\nexport { claudeSkillDir, dataSkillDir, performImport, writeImportedCollection, type ImportOrigin, type ImportResult } from \"./importWriter.js\";\nexport { EXPORT_BASE, writeCollectionExport, type ExportMeta, type ExportResult } from \"./exportCollection.js\";\nexport { performExport } from \"./performExport.js\";\nexport { parseSkillDescription } from \"./skillDescription.js\";\nexport { loadRegistriesConfig, parseRegistriesConfig, OFFICIAL_REGISTRY_NAME, type RegistryConfigEntry, type RegistryScope } from \"./registriesConfig.js\";\nexport type { RegistryEntry, RegistryIndex, ParseResult } from \"../registryIndex.js\";\nexport { parseRegistryIndex } from \"../registryIndex.js\";\nexport type { RegistrySummary, RegistryListResponse, RegistryImportResponse } from \"../types.js\";\n\n/** Build the merged Discover catalog response (`GET …collectionsRegistry.list`).\n * Wraps `fetchAllRegistries` with the per-registry summary + stale-flag shaping\n * every host needs, so a downstream host doesn't re-implement the mapping. */\nexport async function listRegistry(scope: RegistryScope = {}): Promise<RegistryListResponse> {\n const merged = await fetchAllRegistries(scope);\n return {\n registries: merged.map((reg) => ({\n name: reg.name,\n status: reg.status,\n generatedAt: reg.generatedAt,\n error: reg.error,\n entryCount: reg.entries.length,\n })),\n stale: merged.some((reg) => reg.status === \"stale\"),\n collections: merged.flatMap((reg) => reg.entries),\n };\n}\n\nexport type RegistryImportOutcome = { ok: true; response: RegistryImportResponse } | { ok: false; status: number; error: string };\n\n/** Import a registry collection and return the host-facing response shape\n * (`POST …collectionsRegistry.import`). On failure carries the HTTP status so\n * the host route can pass it straight through. */\nexport async function importRegistry(author: string, slug: string, workspaceRoot: string, registry: string | null = null): Promise<RegistryImportOutcome> {\n const result = await performImport(author, slug, workspaceRoot, registry);\n if (!result.ok) return { ok: false, status: result.status, error: result.error };\n return {\n ok: true,\n response: { localSlug: result.localSlug, updated: result.updated, seedWritten: result.seedWritten, seedSkipped: result.seedSkipped },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../../../src/collection/registry/server/registriesConfig.ts","../../../../src/collection/registry/server/client.ts","../../../../src/collection/registry/server/collectionFiles.ts","../../../../src/collection/registry/server/importCollection.ts","../../../../src/collection/registry/server/importWriter.ts","../../../../src/collection/registry/server/exportCollection.ts","../../../../src/collection/registry/server/skillDescription.ts","../../../../src/collection/registry/server/performExport.ts","../../../../src/collection/registry/server/index.ts"],"sourcesContent":["// User-supplied list of extra collection registries (`config/collections-registries.json`).\n// The official receptron/mulmoclaude-collections registry is always loaded; this\n// file adds more on top so a community / org / private registry shows up in the\n// same Discover catalog.\n//\n// Shape on disk:\n// [\n// { \"name\": \"myorg\", \"indexUrl\": \"https://...\", \"rawBaseUrl\": \"https://...\" }\n// ]\n//\n// Validation rules (a bad entry is dropped + logged; the rest still load):\n// - `name` matches `[A-Za-z0-9][A-Za-z0-9_-]{0,31}` and is NOT the reserved\n// `official` (which is always synthesized by client.ts)\n// - Both URLs are HTTPS, parseable, and have no userinfo/credentials\n// - Names are unique (later duplicates are dropped, first wins)\n//\n// Pure read + parse — no network. Caller invalidates by re-reading the file.\n\nimport { readFileSync } from \"node:fs\";\n\nimport { isRecord } from \"../guards.js\";\nimport { OFFICIAL_REGISTRY_NAME, type RegistryConfigEntry } from \"../types.js\";\nimport { collectionsRegistriesConfigPath, getWorkspaceRoot, log } from \"../../server/host.js\";\n\n/** Which workspace a registry read is scoped to. Omitted ⇒ the host's\n * configured root (and, under an explicit-root binding, a loud throw rather\n * than a silent read of another project's config). */\nexport interface RegistryScope {\n workspaceRoot?: string | undefined;\n}\n\nexport { OFFICIAL_REGISTRY_NAME, type RegistryConfigEntry };\n\nconst NAME_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$/;\n\nfunction isValidHttpsUrl(value: unknown): value is string {\n if (typeof value !== \"string\" || value.length === 0) return false;\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return false;\n }\n if (url.protocol !== \"https:\") return false;\n if (url.username !== \"\" || url.password !== \"\") return false;\n return true;\n}\n\nfunction parseEntry(value: unknown, index: number): RegistryConfigEntry | string {\n if (!isRecord(value)) return `entry[${index}] is not an object`;\n const { name, indexUrl, rawBaseUrl } = value;\n if (typeof name !== \"string\" || !NAME_RE.test(name)) {\n return `entry[${index}].name must match ${NAME_RE.source}`;\n }\n if (name === OFFICIAL_REGISTRY_NAME) {\n return `entry[${index}].name \"${OFFICIAL_REGISTRY_NAME}\" is reserved`;\n }\n if (!isValidHttpsUrl(indexUrl)) return `entry[${index}].indexUrl must be a valid HTTPS URL (no credentials)`;\n if (!isValidHttpsUrl(rawBaseUrl)) return `entry[${index}].rawBaseUrl must be a valid HTTPS URL (no credentials)`;\n // `rawBaseUrl` gets joined as `${rawBaseUrl}/<path>` downstream — a query or\n // fragment would land in the middle of the composed URL and break every\n // collection-file fetch on that registry (CodeRabbit review on #1837). The\n // index URL is fetched directly so a query is fine there; only rawBase\n // needs the constraint.\n if (rawBaseUrl.includes(\"?\") || rawBaseUrl.includes(\"#\")) {\n return `entry[${index}].rawBaseUrl must not contain a query (?) or fragment (#) — it's joined as a path prefix`;\n }\n // Strip trailing slashes so `${rawBaseUrl}/${path}` joins cleanly regardless\n // of what the user wrote. Plain-loop trim instead of a regex — the linter\n // flags any unanchored-quantifier regex as ReDoS-suspect even when it's\n // safe, and the loop is just as clear at this size.\n let normalizedRawBase = rawBaseUrl;\n while (normalizedRawBase.endsWith(\"/\")) normalizedRawBase = normalizedRawBase.slice(0, -1);\n return { name, indexUrl, rawBaseUrl: normalizedRawBase };\n}\n\n/** Parse the JSON text of `config/collections-registries.json`. Bad entries\n * drop with a warning; valid entries are returned in source order. Exported\n * for unit testing — the I/O wrapper `loadRegistriesConfig` below reads the\n * workspace file and delegates here. */\nexport function parseRegistriesConfig(raw: unknown): RegistryConfigEntry[] {\n if (!Array.isArray(raw)) {\n log.warn(\"collections-registry\", \"registries config is not an array — ignoring\");\n return [];\n }\n const seen = new Set<string>();\n const out: RegistryConfigEntry[] = [];\n for (let i = 0; i < raw.length; i++) {\n const parsed = parseEntry(raw[i], i);\n if (typeof parsed === \"string\") {\n log.warn(\"collections-registry\", \"registry config entry rejected\", { reason: parsed });\n continue;\n }\n if (seen.has(parsed.name)) {\n log.warn(\"collections-registry\", \"registry config duplicate name dropped\", { name: parsed.name });\n continue;\n }\n seen.add(parsed.name);\n out.push(parsed);\n }\n return out;\n}\n\n// Missing file ⇒ null (the most common case — most users only want the official\n// registry). Other read errors propagate, matching the host's prior behavior.\nfunction readConfigTextOrNull(filePath: string): string | null {\n try {\n return readFileSync(filePath, \"utf-8\");\n } catch (err) {\n if (isRecord(err) && err.code === \"ENOENT\") return null;\n throw err;\n }\n}\n\n/** Read `config/collections-registries.json` for a workspace. `workspaceRoot`\n * defaults to the host's configured root; a multi-root host passes the project\n * it is serving. Missing file ⇒ empty list. Malformed JSON or rejected entries\n * log but never throw. */\nexport function loadRegistriesConfig(opts: RegistryScope = {}): RegistryConfigEntry[] {\n const text = readConfigTextOrNull(collectionsRegistriesConfigPath(opts.workspaceRoot ?? getWorkspaceRoot()));\n if (text === null) return [];\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch (err) {\n log.warn(\"collections-registry\", \"registries config is not valid JSON — ignoring\", {\n error: err instanceof Error ? err.message : String(err),\n });\n return [];\n }\n return parseRegistriesConfig(parsed);\n}\n","// Server-side client for the curated collection registries' published index.json\n// files. The official registry (receptron/mulmoclaude-collections via GitHub Pages)\n// is always loaded; users can append more via `config/collections-registries.json`.\n// Each index is fetched over HTTPS with a timeout, validated against the index\n// contract, and memo-cached per-registry so the Discover tab doesn't hammer the\n// upstreams. On a transient upstream failure we serve that registry's last good\n// index rather than failing the whole Discover view.\n\nimport { fetchWithTimeout } from \"../../../utils/fetch.js\";\nimport { errorMessage, ONE_SECOND_MS } from \"../../server/util.js\";\nimport { log } from \"../../server/host.js\";\nimport { parseRegistryIndex, type RegistryEntry, type RegistryIndex } from \"../registryIndex.js\";\nimport { loadRegistriesConfig, OFFICIAL_REGISTRY_NAME, type RegistryConfigEntry, type RegistryScope } from \"./registriesConfig.js\";\n\nconst DEFAULT_OFFICIAL_INDEX_URL = \"https://receptron.github.io/mulmoclaude-collections/index.json\";\nconst DEFAULT_OFFICIAL_RAW_BASE = \"https://raw.githubusercontent.com/receptron/mulmoclaude-collections/main\";\nexport const CACHE_TTL_MS = 5 * 60 * ONE_SECOND_MS;\n// During an outage (cache past TTL + failing upstream) don't re-hit the network\n// more than once per this window — serve stale immediately in between, so a down\n// upstream can't add its full timeout to every request.\nexport const STALE_RETRY_BACKOFF_MS = 60 * ONE_SECOND_MS;\nconst FETCH_TIMEOUT_MS = 10 * ONE_SECOND_MS;\nconst STATUS_BAD_GATEWAY = 502;\nconst STATUS_UNAVAILABLE = 503;\n\n/** Per-registry fetch outcome. `ok: false` is reported per-registry — the\n * merged Discover response still succeeds when at least one registry is\n * reachable; failed registries just contribute zero entries. */\nexport type FetchIndexResult = { ok: true; index: RegistryIndex; stale: boolean } | { ok: false; status: number; error: string };\n\ninterface CacheEntry {\n index: RegistryIndex;\n atMs: number;\n}\n\nconst cache = new Map<string, CacheEntry>();\nconst lastFailureMs = new Map<string, number>();\n\n/** Resolved descriptor for one registry the host should fetch — name + URLs.\n * `official` is always synthesized; further entries come from\n * `config/collections-registries.json`. Currently structurally identical to\n * `RegistryConfigEntry`; aliased so we can add server-only fields (e.g.\n * auth headers) without touching the on-disk config schema. */\nexport type RegistryDescriptor = RegistryConfigEntry;\n\nfunction officialDescriptor(): RegistryDescriptor {\n return {\n name: OFFICIAL_REGISTRY_NAME,\n indexUrl: process.env.COLLECTIONS_REGISTRY_URL ?? DEFAULT_OFFICIAL_INDEX_URL,\n rawBaseUrl: process.env.COLLECTIONS_REGISTRY_RAW_BASE ?? DEFAULT_OFFICIAL_RAW_BASE,\n };\n}\n\n/** The full ordered registry list: official first, then user-configured ones.\n * Re-reads the config file on every call so a Discover refresh picks up edits\n * without a server restart (the config is small + read-rarely). */\nexport function listRegistries(scope: RegistryScope = {}): RegistryDescriptor[] {\n return [officialDescriptor(), ...loadRegistriesConfig(scope)];\n}\n\n/** Look up one registry descriptor by name. Used by the preview / import path,\n * which has the registry label from the entry and needs the rawBase. */\nexport function findRegistry(name: string, scope: RegistryScope = {}): RegistryDescriptor | null {\n return listRegistries(scope).find((reg) => reg.name === name) ?? null;\n}\n\nasync function loadFromNetwork(descriptor: RegistryDescriptor): Promise<FetchIndexResult> {\n let res: Response;\n try {\n res = await fetchWithTimeout(descriptor.indexUrl, { timeoutMs: FETCH_TIMEOUT_MS, headers: { accept: \"application/json\" } });\n } catch (err) {\n log.warn(\"collections-registry\", \"index fetch failed\", { registry: descriptor.name, url: descriptor.indexUrl, error: errorMessage(err) });\n return { ok: false, status: STATUS_UNAVAILABLE, error: \"registry unreachable\" };\n }\n if (!res.ok) return { ok: false, status: STATUS_BAD_GATEWAY, error: `registry responded ${res.status}` };\n const json: unknown = await res.json().catch(() => null);\n const parsed = parseRegistryIndex(json, descriptor.name);\n if (!parsed.ok) {\n log.warn(\"collections-registry\", \"index invalid\", { registry: descriptor.name, url: descriptor.indexUrl, error: parsed.error });\n return { ok: false, status: STATUS_BAD_GATEWAY, error: `registry index invalid: ${parsed.error}` };\n }\n return { ok: true, index: parsed.index, stale: false };\n}\n\nexport type IndexLoader = (descriptor: RegistryDescriptor) => Promise<FetchIndexResult>;\n\n/** Cache key incorporates BOTH URLs so editing `indexUrl` or `rawBaseUrl` for\n * an existing registry name invalidates the cached index automatically — same\n * refresh cycle as if the user had renamed it (CodeRabbit review on #1837).\n * Without this, the Discover catalog could keep serving the old upstream's\n * entries while preview / import resolved the new rawBase, drifting until TTL\n * expiry. Tab + key-content guarantees no collision between e.g. swapped\n * name/url pairs. */\nfunction descriptorCacheKey(descriptor: RegistryDescriptor): string {\n return `${descriptor.name}\\t${descriptor.indexUrl}\\t${descriptor.rawBaseUrl}`;\n}\n\n/** Fetch one registry's index. Same cache + stale-on-failure semantics as the\n * original single-registry implementation — just keyed by descriptor identity\n * (name + both URLs) so multiple registries don't fight over one slot AND a\n * reconfigured registry doesn't serve a stale index from the prior URL. */\nexport async function fetchRegistryIndex(\n descriptor: RegistryDescriptor,\n opts: { force?: boolean; nowMs?: number; loader?: IndexLoader } = {},\n): Promise<FetchIndexResult> {\n const nowMs = opts.nowMs ?? Date.now();\n const loader = opts.loader ?? loadFromNetwork;\n const key = descriptorCacheKey(descriptor);\n const cached = cache.get(key);\n if (!opts.force && cached && nowMs - cached.atMs < CACHE_TTL_MS) {\n return { ok: true, index: cached.index, stale: false };\n }\n const failedAt = lastFailureMs.get(key);\n if (!opts.force && cached && failedAt !== undefined && nowMs - failedAt < STALE_RETRY_BACKOFF_MS) {\n return { ok: true, index: cached.index, stale: true };\n }\n const fresh = await loader(descriptor);\n if (fresh.ok) {\n cache.set(key, { index: fresh.index, atMs: nowMs });\n lastFailureMs.delete(key);\n return { ok: true, index: fresh.index, stale: false };\n }\n lastFailureMs.set(key, nowMs);\n if (cached) return { ok: true, index: cached.index, stale: true };\n return fresh;\n}\n\n/** One registry's contribution to the merged Discover view. `failed` registries\n * surface in the response so the UI can show a per-registry error badge while\n * still rendering the entries from the registries that did work. */\nexport interface MergedRegistryResult {\n name: string;\n status: \"ok\" | \"stale\" | \"failed\";\n generatedAt: string | null;\n error: string | null;\n entries: RegistryEntry[];\n}\n\n/** Fetch every configured registry in parallel and return per-registry\n * outcomes. Callers (the Discover route) concatenate `entries` from each.\n * Failure of any single registry doesn't abort the others — that's the point\n * of supporting multiple registries. */\nexport async function fetchAllRegistries(\n opts: { force?: boolean; nowMs?: number; loader?: IndexLoader } & RegistryScope = {},\n): Promise<MergedRegistryResult[]> {\n const descriptors = listRegistries(opts);\n return await Promise.all(\n descriptors.map(async (descriptor): Promise<MergedRegistryResult> => {\n const result = await fetchRegistryIndex(descriptor, opts);\n if (!result.ok) {\n return { name: descriptor.name, status: \"failed\", generatedAt: null, error: result.error, entries: [] };\n }\n return {\n name: descriptor.name,\n status: result.stale ? \"stale\" : \"ok\",\n generatedAt: result.index.generatedAt,\n error: null,\n entries: result.index.collections,\n };\n }),\n );\n}\n\n/** Test seam: reset the module cache + failure backoff state (all registries). */\nexport function resetRegistryCache(): void {\n cache.clear();\n lastFailureMs.clear();\n}\n","// Fetch individual files of a registry collection (schema.json, meta.json, …)\n// from the registry repo's raw content. The collection's repo-relative dir comes\n// from a trusted index entry (never raw user input), so the host can only fetch\n// files of collections that actually appear in the published index.\n//\n// The index itself is served from GitHub Pages (see client.ts); per-collection\n// files are served from raw.githubusercontent. With multi-registry support the\n// rawBase is no longer a single module-level value — it comes from the entry's\n// source registry descriptor, so user-added registries can live anywhere.\n\nimport { fetchWithTimeout } from \"../../../utils/fetch.js\";\nimport { errorMessage, ONE_SECOND_MS } from \"../../server/util.js\";\nimport { isRecord } from \"../guards.js\";\nimport { fetchAllRegistries, findRegistry } from \"./client.js\";\nimport type { RegistryScope } from \"./registriesConfig.js\";\nimport type { RegistryEntry } from \"../registryIndex.js\";\n\nconst FETCH_TIMEOUT_MS = 10 * ONE_SECOND_MS;\nconst STATUS_BAD_GATEWAY = 502;\nconst STATUS_UNAVAILABLE = 503;\nconst STATUS_NOT_FOUND = 404;\n\n/** Compose the raw URL for `<dirPath>/<relFile>` under a given registry's\n * rawBase. Empty and traversal (`.`/`..`) segments are dropped so the URL can\n * never escape the base, even if an upstream check is bypassed (the index\n * parser already rejects such identifiers — this is defense-in-depth). The\n * rawBase is trailing-slash-normalized: `parseRegistriesConfig` already trims\n * user-config trailing slashes, but the official descriptor and any test\n * bypass parse — repeating the trim here keeps the join `${base}/${path}`\n * from producing `//` even when the caller bypassed config validation\n * (CodeRabbit review on #1837). */\nexport function collectionFileUrl(rawBase: string, dirPath: string, relFile: string): string {\n let base = rawBase;\n while (base.endsWith(\"/\")) base = base.slice(0, -1);\n const segments = dirPath.split(\"/\").filter((segment) => segment.length > 0 && segment !== \".\" && segment !== \"..\");\n return `${base}/${segments.join(\"/\")}/${relFile}`;\n}\n\nexport type FileResult = { ok: true; text: string } | { ok: false; status: number; error: string };\n\n/** Fetch one file out of a registry collection. `rawBase` comes from the\n * entry's source registry (`findRegistry(entry.registryName).rawBaseUrl`),\n * not a module-level setting — that's what makes additional user-configured\n * registries reachable. */\nexport async function fetchCollectionFile(rawBase: string, dirPath: string, relFile: string): Promise<FileResult> {\n const url = collectionFileUrl(rawBase, dirPath, relFile);\n let res: Response;\n try {\n res = await fetchWithTimeout(url, { timeoutMs: FETCH_TIMEOUT_MS });\n } catch (err) {\n return { ok: false, status: STATUS_UNAVAILABLE, error: `fetch failed: ${errorMessage(err)}` };\n }\n if (!res.ok) return { ok: false, status: STATUS_BAD_GATEWAY, error: `${relFile} responded ${res.status}` };\n return { ok: true, text: await res.text() };\n}\n\nexport type JsonObjectResult = { ok: true; value: Record<string, unknown> } | { ok: false; error: string };\n\nexport function parseJsonObject(text: string, label: string): JsonObjectResult {\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return { ok: false, error: `${label} is not valid JSON` };\n }\n if (!isRecord(parsed)) return { ok: false, error: `${label} is not an object` };\n return { ok: true, value: parsed };\n}\n\nasync function fetchJsonObject(\n rawBase: string,\n dirPath: string,\n relFile: string,\n label: string,\n): Promise<{ ok: true; value: Record<string, unknown> } | { ok: false; status: number; error: string }> {\n const file = await fetchCollectionFile(rawBase, dirPath, relFile);\n if (!file.ok) return { ok: false, status: file.status, error: `${label}: ${file.error}` };\n const parsed = parseJsonObject(file.text, label);\n if (!parsed.ok) return { ok: false, status: STATUS_BAD_GATEWAY, error: parsed.error };\n return { ok: true, value: parsed.value };\n}\n\n/** Resolve an entry's rawBase from its `registryName`. A missing match (the\n * user removed the registry from config while a cached index still references\n * it) returns null — the caller surfaces it as a 404 rather than crashing. */\nexport function rawBaseForEntry(entry: Pick<RegistryEntry, \"registryName\">, scope: RegistryScope = {}): string | null {\n const registry = findRegistry(entry.registryName, scope);\n return registry?.rawBaseUrl ?? null;\n}\n\nexport type PreviewResult =\n { ok: true; entry: RegistryEntry; schema: Record<string, unknown>; meta: Record<string, unknown> } | { ok: false; status: number; error: string };\n\n/** Resolve an entry by author+slug across every cached registry's entries.\n * When `registry` is passed we constrain to that registry — needed because\n * multiple registries can publish the same author/slug, and the UI should\n * follow the card it just clicked. */\nfunction findEntryInMergedView(\n merged: { name: string; entries: RegistryEntry[] }[],\n author: string,\n slug: string,\n registry: string | null,\n): RegistryEntry | null {\n for (const reg of merged) {\n if (registry !== null && reg.name !== registry) continue;\n const match = reg.entries.find((candidate) => candidate.author === author && candidate.slug === slug);\n if (match) return match;\n }\n return null;\n}\n\n/** Preview a registry collection: confirm it's in some registry's published\n * index, then fetch + parse its schema.json + meta.json so the Discover tab\n * can show fields/views before import. With multi-registry support the\n * `registry` arg disambiguates same-name collections from different sources. */\nexport async function previewCollection(author: string, slug: string, registry: string | null = null, scope: RegistryScope = {}): Promise<PreviewResult> {\n const merged = await fetchAllRegistries(scope);\n const entry = findEntryInMergedView(merged, author, slug, registry);\n if (!entry) return { ok: false, status: STATUS_NOT_FOUND, error: `unknown collection: ${author}/${slug}` };\n const rawBase = rawBaseForEntry(entry, scope);\n if (!rawBase) return { ok: false, status: STATUS_NOT_FOUND, error: `registry \"${entry.registryName}\" is no longer configured` };\n const schema = await fetchJsonObject(rawBase, entry.path, \"schema.json\", \"schema.json\");\n if (!schema.ok) return schema;\n const meta = await fetchJsonObject(rawBase, entry.path, \"meta.json\", \"meta.json\");\n if (!meta.ok) return meta;\n return { ok: true, entry, schema: schema.value, meta: meta.value };\n}\n","// Import-side fetch + transform helpers for a registry collection. The writer\n// (writes into .claude/skills/, materializes seed, records provenance) builds on\n// these; kept separate so the pure, security-critical parts are unit-tested.\n//\n// Files to fetch come from the collection's manifest.json (published by the\n// registry's build-index). Every manifest path is re-checked for safety here —\n// the host must never write outside the target skill dir even if the manifest is\n// malformed/poisoned.\n\nimport { isRecord } from \"../guards.js\";\nimport { fetchCollectionFile, parseJsonObject, rawBaseForEntry } from \"./collectionFiles.js\";\nimport type { RegistryEntry } from \"../registryIndex.js\";\nimport type { RegistryScope } from \"./registriesConfig.js\";\n\nconst MANIFEST_FILE = \"manifest.json\";\nconst STATUS_BAD_GATEWAY = 502;\n\n/** A manifest entry must be a relative path that stays inside the collection dir:\n * no absolute paths, no backslashes, no empty / `.` / `..` segments. */\nexport function isSafeBundlePath(rel: unknown): rel is string {\n if (typeof rel !== \"string\" || rel.length === 0) return false;\n if (rel.startsWith(\"/\") || rel.includes(\"\\\\\")) return false;\n return !rel.split(\"/\").some((segment) => segment === \"\" || segment === \".\" || segment === \"..\");\n}\n\nexport type ManifestResult = { ok: true; files: string[] } | { ok: false; error: string };\n\nexport function parseManifest(value: unknown): ManifestResult {\n if (!isRecord(value) || !Array.isArray(value.files)) return { ok: false, error: \"manifest is missing a files[] array\" };\n const unsafe = value.files.find((file) => !isSafeBundlePath(file));\n if (unsafe !== undefined) return { ok: false, error: `manifest contains an unsafe path: ${String(unsafe)}` };\n return { ok: true, files: value.files.filter(isSafeBundlePath) };\n}\n\n/** `data/collections/<localSlug>/items` — the host owns dataPath, never the\n * registry's authored value, so imported collections can't collide on disk. */\nexport function normalizedDataPath(localSlug: string): string {\n return `data/collections/${localSlug}/items`;\n}\n\nexport function withNormalizedDataPath(schema: Record<string, unknown>, localSlug: string): Record<string, unknown> {\n return { ...schema, dataPath: normalizedDataPath(localSlug) };\n}\n\nexport type ManifestFetch = { ok: true; files: string[] } | { ok: false; status: number; error: string };\n\nexport async function fetchManifest(entry: RegistryEntry, scope: RegistryScope = {}): Promise<ManifestFetch> {\n const rawBase = rawBaseForEntry(entry, scope);\n if (!rawBase) return { ok: false, status: STATUS_BAD_GATEWAY, error: `registry \"${entry.registryName}\" is no longer configured` };\n const file = await fetchCollectionFile(rawBase, entry.path, MANIFEST_FILE);\n if (!file.ok) return { ok: false, status: file.status, error: `manifest.json: ${file.error}` };\n const obj = parseJsonObject(file.text, \"manifest.json\");\n if (!obj.ok) return { ok: false, status: STATUS_BAD_GATEWAY, error: obj.error };\n const manifest = parseManifest(obj.value);\n if (!manifest.ok) return { ok: false, status: STATUS_BAD_GATEWAY, error: manifest.error };\n return { ok: true, files: manifest.files };\n}\n\nexport type BundleFetch = { ok: true; files: Map<string, string> } | { ok: false; status: number; error: string };\n\n/** Fetch every manifest file. Paths are already safety-checked by parseManifest. */\nexport async function fetchBundle(entry: RegistryEntry, fileList: readonly string[], scope: RegistryScope = {}): Promise<BundleFetch> {\n const rawBase = rawBaseForEntry(entry, scope);\n if (!rawBase) return { ok: false, status: STATUS_BAD_GATEWAY, error: `registry \"${entry.registryName}\" is no longer configured` };\n const files = new Map<string, string>();\n for (const rel of fileList) {\n const file = await fetchCollectionFile(rawBase, entry.path, rel);\n if (!file.ok) return { ok: false, status: file.status, error: `${rel}: ${file.error}` };\n files.set(rel, file.text);\n }\n return { ok: true, files };\n}\n","// Import a registry collection into the active workspace. The writer fetches\n// the bundle, re-validates the schema with the host's own gates (R7 — the\n// index is not a trust boundary), writes the bundle into `data/skills/<localSlug>/`\n// with a host-owned dataPath (R3), materializes any seed records into that\n// dataPath when it's empty, and records provenance in `.origin.json` for\n// update detection (R5/R8). After the data-skills swap, the bundle's\n// allowlisted files (SKILL.md, schema.json, templates/<safe>) are mirrored\n// 1:1 into `.claude/skills/<localSlug>/` via the same skill-bridge rules an\n// agent-authored skill would go through — so an authored and an imported\n// collection live in EXACTLY the same place on disk, and the user can edit\n// either one identically.\n//\n// `.origin.json` lives ONLY in `data/skills/<slug>/.origin.json` and is NOT\n// mirrored — the skill-bridge allowlist deliberately excludes host\n// bookkeeping. Its presence is what distinguishes \"imported\" from\n// \"user-authored\" in every downstream tool.\n//\n// `writeImportedCollection` takes the already-fetched bundle + an explicit\n// workspaceRoot/clock so it is unit-testable against a temp workspace with\n// no network; `performImport` is the thin glue that fetches and calls it.\n\nimport { mkdir, readFile, readdir, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport type { Dirent } from \"node:fs\";\nimport path from \"node:path\";\n\nimport { acceptParsedSchema, CollectionSchemaZ, isSafeActionTemplatePath, safeRecordId } from \"../../server/index.js\";\nimport type { CollectionSchema } from \"../../index.js\";\nimport { claudeSkillDir, dataSkillDir, mirrorSkillWrite } from \"../../../skill-bridge/index.js\";\n\nimport { log, projectSkillsDir, skillsStagingDir } from \"../../server/host.js\";\nimport { errorMessage } from \"../../server/util.js\";\nimport { writeFileAtomic } from \"../../../files/atomic.js\";\nimport { isRecord } from \"../guards.js\";\nimport { fetchAllRegistries } from \"./client.js\";\nimport { fetchBundle, fetchManifest, normalizedDataPath } from \"./importCollection.js\";\nimport type { RegistryEntry } from \"../registryIndex.js\";\n\nconst ORIGIN_FILE = \".origin.json\";\nconst SEED_PREFIX = \"seed/items/\";\nconst SCHEMA_FILE = \"schema.json\";\nconst SKILL_FILE = \"SKILL.md\";\nconst STATUS_NOT_FOUND = 404;\nconst STATUS_CONFLICT = 409;\nconst STATUS_UNPROCESSABLE = 422;\n\nexport interface ImportOrigin {\n registry: string;\n author: string;\n slug: string;\n version: string;\n contentSha: string;\n importedAt: string;\n}\n\nexport type ImportResult =\n { ok: true; localSlug: string; updated: boolean; seedWritten: number; seedSkipped: boolean } | { ok: false; status: number; error: string };\n\nasync function statType(target: string): Promise<\"dir\" | \"other\" | \"absent\"> {\n try {\n return (await stat(target)).isDirectory() ? \"dir\" : \"other\";\n } catch (err) {\n // EACCES etc. are path-shape conflicts mkdir would later throw on → \"other\".\n // A file ancestor surfaces as ENOTDIR on POSIX but ENOENT on Windows (which\n // reports the whole path as missing), so ENOENT can't be trusted as \"absent\"\n // until we've confirmed no existing ancestor is a non-directory file.\n if (!(isRecord(err) && err.code === \"ENOENT\")) return \"other\";\n return (await hasNonDirAncestor(target)) ? \"other\" : \"absent\";\n }\n}\n\n// Walk up from `target` to the nearest ancestor that exists: a non-directory\n// file there means `target` can never be mkdir'd (a path-shape conflict → 409),\n// whichever errno the platform's `stat(target)` chose. An all-absent-or-directory\n// chain up to the filesystem root is a genuine \"absent\".\nasync function hasNonDirAncestor(target: string): Promise<boolean> {\n let current = path.dirname(target);\n while (current !== path.dirname(current)) {\n try {\n return !(await stat(current)).isDirectory();\n } catch (err) {\n if (isRecord(err) && err.code === \"ENOENT\") {\n current = path.dirname(current);\n continue;\n }\n return true;\n }\n }\n return false;\n}\n\nasync function isEmptyOrAbsentDir(target: string): Promise<boolean> {\n try {\n return (await readdir(target)).length === 0;\n } catch {\n return true;\n }\n}\n\nfunction originMatches(origin: unknown, registry: string, author: string, slug: string): boolean {\n return isRecord(origin) && origin.registry === registry && origin.author === author && origin.slug === slug;\n}\n\nasync function readOrigin(targetDir: string): Promise<unknown> {\n try {\n return JSON.parse(await readFile(path.join(targetDir, ORIGIN_FILE), \"utf-8\"));\n } catch {\n return null;\n }\n}\n\ntype TargetResolution = { targetDir: string; localSlug: string; updated: boolean } | { conflict: string };\n\n// Only bounds the fresh-slug search (a safety cap, far above any realistic\n// number of same-named collections — the first free slug is normally found in\n// 1–2 iterations). An EXISTING install is found via the directory scan below,\n// not this loop, so updates are never missed regardless of how high the rename\n// suffix is.\nconst MAX_SLUG_ATTEMPTS = 10000;\n\nconst renameCandidate = (slug: string, attempt: number): string => (attempt === 0 ? slug : `${slug}-${attempt + 1}`);\n\n// True when `name` is the registry slug or a `<slug>-<n>` rename of it.\nfunction isRenameOf(name: string, slug: string): boolean {\n if (name === slug) return true;\n if (!name.startsWith(`${slug}-`)) return false;\n const suffix = name.slice(slug.length + 1);\n return suffix.length > 0 && /^\\d+$/.test(suffix);\n}\n\n// Find an existing install of this registry collection at ANY rename suffix\n// by scanning `data/skills/` — independent of any candidate bound, so a\n// re-import always updates the existing install rather than duplicating it\n// (even if an earlier slug freed up). An authored skill without `.origin.json`\n// is invisible to this scan and so never collides with an update.\nasync function findMatchingInstall(skillsDir: string, registry: string, entry: RegistryEntry): Promise<string | null> {\n const names = await readdir(skillsDir).catch((): string[] => []);\n for (const name of names) {\n if (!isRenameOf(name, entry.slug)) continue;\n const dir = path.join(skillsDir, name);\n if ((await statType(dir)) === \"dir\" && originMatches(await readOrigin(dir), registry, entry.author, entry.slug)) return name;\n }\n return null;\n}\n\n// Pick the local install slug (rename-on-collision, R8). First reuse an\n// existing matching install (update). Otherwise install fresh at the first\n// free slug — the registry slug, else `<slug>-2`, `-3`, … — never clobbering\n// a user's own same-named collection.\n//\n// A slug is \"free\" only when BOTH `data/skills/<slug>/` AND\n// `.claude/skills/<slug>/` are absent. The mirror check matters because\n// `mirrorToClaudeSkills` overwrites the mirror dir's allowlisted files and\n// prunes anything else — if the user has a manually-installed Claude skill\n// at `.claude/skills/<slug>/` with no corresponding `data/skills/<slug>/`\n// (e.g. installed by the Claude CLI directly, or a legacy pre-refactor\n// import we never migrated), picking that slug would silently overwrite\n// their SKILL.md/schema.json and prune the rest of their files (CodeRabbit\n// review on #1839).\n/** Where an import INSTALLS in this root, and whether a mirror step follows.\n *\n * With a staging tree the install is the usual two-step: write\n * `data/skills/<slug>/` and mirror the allowlist into `.claude/skills/<slug>/`.\n * A root with NO staging tree has no bridge to mirror through, so the active\n * skill dir IS the install — and installing into `data/skills/` there would be\n * worse than useless: the mirror makes the collection discoverable, but its\n * `.origin.json` and every non-allowlisted asset (notably `views/`) stay in a\n * tree that reads and deletes now deliberately ignore. Update would not find\n * the provenance, and delete would leave the install behind. */\nfunction importInstall(workspaceRoot: string): { skillsDir: string; mirrors: boolean } {\n const staging = skillsStagingDir(workspaceRoot);\n return staging === null ? { skillsDir: projectSkillsDir(workspaceRoot), mirrors: false } : { skillsDir: staging, mirrors: true };\n}\n\nconst installDir = (workspaceRoot: string, slug: string): string => path.join(importInstall(workspaceRoot).skillsDir, slug);\n\nasync function resolveTarget(workspaceRoot: string, registry: string, entry: RegistryEntry): Promise<TargetResolution> {\n const { skillsDir, mirrors } = importInstall(workspaceRoot);\n const existing = await findMatchingInstall(skillsDir, registry, entry);\n if (existing) return { targetDir: path.join(skillsDir, existing), localSlug: existing, updated: true };\n for (let attempt = 0; attempt < MAX_SLUG_ATTEMPTS; attempt += 1) {\n const localSlug = renameCandidate(entry.slug, attempt);\n const sourceAbsent = (await statType(path.join(skillsDir, localSlug))) === \"absent\";\n // Without a mirror the two checks are the same directory, so the install\n // dir being free is the whole condition.\n const mirrorAbsent = !mirrors || (await statType(claudeSkillDir(workspaceRoot, localSlug))) === \"absent\";\n if (sourceAbsent && mirrorAbsent) {\n return { targetDir: path.join(skillsDir, localSlug), localSlug, updated: false };\n }\n }\n return { conflict: `couldn't find an available slug for '${entry.slug}'` };\n}\n\ntype SchemaResolution = { schema: CollectionSchema } | { error: string };\n\nfunction validateAndNormalize(bundle: Map<string, string>, localSlug: string, workspaceRoot: string): SchemaResolution {\n // SKILL.md is required: `mirrorToClaudeSkills` calls\n // `mirrorSkillWrite({slug, relSegments: [SKILL_FILE]})` unconditionally\n // after the data-side swap, which throws if SKILL.md is missing. Caught\n // outside, that throw is logged but the data-side write still succeeds and\n // the function returns `ok: true` for an unusable import. Reject up front\n // so a missing SKILL.md surfaces as a clean 422 instead (CodeRabbit review\n // on #1839).\n if (bundle.get(SKILL_FILE) === undefined) return { error: \"bundle is missing SKILL.md\" };\n const schemaText = bundle.get(SCHEMA_FILE);\n if (schemaText === undefined) return { error: \"bundle is missing schema.json\" };\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(schemaText);\n } catch {\n return { error: \"schema.json is not valid JSON\" };\n }\n const parsed = CollectionSchemaZ.safeParse(parsedJson);\n if (!parsed.success) return { error: `schema.json failed validation: ${parsed.error.issues[0]?.message ?? \"invalid\"}` };\n // A `dataSource` collection can't be imported: its records are the rows of\n // a machine-local data file the bundle doesn't (and can't) carry — and the\n // R3 dataPath stamp below would collide with the exactly-one-of rule.\n if (parsed.data.dataSource !== undefined) {\n return { error: \"schema.json declares `dataSource` — external-data collections are machine-local and cannot be imported from a registry\" };\n }\n const schema: CollectionSchema = { ...parsed.data, dataPath: normalizedDataPath(localSlug) };\n const acceptance = acceptParsedSchema(schema, { source: \"project\", workspaceRoot, slug: localSlug });\n if (!acceptance.ok) return { error: `schema.json rejected: ${acceptance.reason}` };\n return { schema };\n}\n\nasync function writeBundleFiles(targetDir: string, bundle: Map<string, string>, schema: CollectionSchema): Promise<void> {\n for (const [rel, content] of bundle) {\n if (rel.startsWith(SEED_PREFIX)) continue; // seed goes to dataPath, not the skill dir\n const dest = path.join(targetDir, ...rel.split(\"/\"));\n if (dest !== targetDir && !dest.startsWith(targetDir + path.sep)) continue; // belt-and-suspenders (paths pre-validated)\n await mkdir(path.dirname(dest), { recursive: true });\n await writeFile(dest, rel === SCHEMA_FILE ? `${JSON.stringify(schema, null, 2)}\\n` : content, \"utf-8\");\n }\n}\n\nasync function materializeSeed(dataDir: string, bundle: Map<string, string>): Promise<{ written: number; skipped: boolean }> {\n const seedEntries = [...bundle].filter(([rel]) => rel.startsWith(SEED_PREFIX));\n if (seedEntries.length === 0) return { written: 0, skipped: false };\n if (!(await isEmptyOrAbsentDir(dataDir))) return { written: 0, skipped: true };\n await mkdir(dataDir, { recursive: true });\n let written = 0;\n for (const [rel, content] of seedEntries) {\n const fileName = rel.slice(SEED_PREFIX.length);\n if (fileName.includes(\"/\") || safeRecordId(fileName.replace(/\\.json$/, \"\")) === null) {\n log.warn(\"collections-registry\", \"skipped unsafe seed record\", { rel });\n continue;\n }\n await writeFile(path.join(dataDir, fileName), content, \"utf-8\");\n written += 1;\n }\n return { written, skipped: false };\n}\n\n/** Compute the set of bundle-relative paths that should land in `.claude/skills/<slug>/`.\n * Matches the bridge allowlist exactly — SKILL.md, schema.json,\n * `templates/<safe>` — so anything else (seed, meta.json, views, README,\n * assets) stays source-side. */\nfunction bridgeAllowlistFiles(bundle: Map<string, string>): Set<string> {\n const wanted = new Set<string>([SKILL_FILE, SCHEMA_FILE]);\n for (const rel of bundle.keys()) {\n if (rel === SKILL_FILE || rel === SCHEMA_FILE) continue;\n if (rel.startsWith(SEED_PREFIX)) continue;\n if (!isSafeActionTemplatePath(rel)) continue;\n wanted.add(rel);\n }\n return wanted;\n}\n\n/** Recursively list every file under `root`, returned as forward-slash paths\n * relative to `root`. Empty list when `root` doesn't exist. Used to spot\n * mirror files left over from a previous install so we can prune them after\n * writing the new ones. */\nasync function listFilesRecursive(root: string, prefix = \"\"): Promise<string[]> {\n const dir = prefix ? path.join(root, ...prefix.split(\"/\")) : root;\n const entries: Dirent[] = await readdir(dir, { withFileTypes: true }).catch((): Dirent[] => []);\n const out: string[] = [];\n for (const entry of entries) {\n const rel = prefix ? `${prefix}/${entry.name}` : entry.name;\n if (entry.isFile()) out.push(rel);\n else if (entry.isDirectory()) out.push(...(await listFilesRecursive(root, rel)));\n }\n return out;\n}\n\n// Mirror the just-written `data/skills/<slug>/` into `.claude/skills/<slug>/`\n// using the shared bridge package — exactly the set of files an\n// agent-authored skill would mirror, no more (SKILL.md, schema.json,\n// templates/<safe>). `.origin.json` is host bookkeeping and stays only on the\n// data side.\n//\n// **Write-then-prune ordering** (Codex review on #1839): write the new\n// bundle's allowlisted files FIRST (each is a per-file tmp+rename, atomic),\n// THEN prune mirror files that aren't in the new bundle. A transient mirror\n// failure during the writes leaves the previous mirror's files in place —\n// agent discovery keeps working with the prior state instead of finding an\n// empty `.claude/skills/<slug>/`. The prune step is best-effort: a failure\n// there leaves harmless stale leftovers but doesn't break the new install.\n// Compare with the old `mirrorSkillDelete`-first ordering, which could\n// silently leave an empty mirror if a subsequent write threw.\nasync function mirrorToClaudeSkills(workspaceRoot: string, localSlug: string, bundle: Map<string, string>): Promise<void> {\n const wanted = bridgeAllowlistFiles(bundle);\n // 1. Write the new set first (overwrites existing via tmp+rename).\n for (const rel of wanted) {\n mirrorSkillWrite(workspaceRoot, { slug: localSlug, relSegments: rel.split(\"/\") });\n }\n // 2. Prune files that exist in the mirror but aren't in the new bundle.\n const mirrorRoot = claudeSkillDir(workspaceRoot, localSlug);\n const existing = await listFilesRecursive(mirrorRoot);\n for (const rel of existing) {\n if (wanted.has(rel)) continue;\n await rm(path.join(mirrorRoot, ...rel.split(\"/\")), { force: true }).catch(() => undefined);\n }\n}\n\nexport async function writeImportedCollection(params: {\n registry: string;\n entry: RegistryEntry;\n bundle: Map<string, string>;\n workspaceRoot: string;\n nowIso: string;\n}): Promise<ImportResult> {\n const { registry, entry, bundle, workspaceRoot, nowIso } = params;\n const target = await resolveTarget(workspaceRoot, registry, entry);\n if (\"conflict\" in target) return { ok: false, status: STATUS_CONFLICT, error: target.conflict };\n\n const { localSlug } = target;\n\n // Pre-flight the data dir before schema validation: a non-directory at the dataPath\n // (or an ancestor that's a file → ENOTDIR) would otherwise surface as a generic 500\n // on mkdir. statType maps ENOTDIR/other to a deterministic 409 path-shape conflict.\n const dataDir = path.join(workspaceRoot, ...normalizedDataPath(localSlug).split(\"/\"));\n if ((await statType(dataDir)) === \"other\") {\n return { ok: false, status: STATUS_CONFLICT, error: `data path for slug '${localSlug}' exists and is not a directory` };\n }\n\n const validated = validateAndNormalize(bundle, localSlug, workspaceRoot);\n if (\"error\" in validated) return { ok: false, status: STATUS_UNPROCESSABLE, error: validated.error };\n\n // Build the replacement fully in a hidden sibling staging dir under\n // `data/skills/` (bundle + origin), so the prior install is untouched until\n // everything is durably written. Leftover staging/backup dirs from a\n // crashed import are cleaned first, keeping retries possible.\n const skillsParent = path.dirname(target.targetDir);\n await mkdir(skillsParent, { recursive: true }); // first-import case — data/skills/ may not exist\n const staging = path.join(skillsParent, `.importing-${localSlug}`);\n const backup = path.join(skillsParent, `.backup-${localSlug}`);\n await rm(staging, { recursive: true, force: true });\n await rm(backup, { recursive: true, force: true });\n await mkdir(staging, { recursive: true });\n await writeBundleFiles(staging, bundle, validated.schema);\n const origin: ImportOrigin = { registry, author: entry.author, slug: entry.slug, version: entry.version, contentSha: entry.contentSha, importedAt: nowIso };\n await writeFileAtomic(path.join(staging, ORIGIN_FILE), `${JSON.stringify(origin, null, 2)}\\n`);\n\n // Swap with rollback: move the old install aside (rename), move the new in\n // (rename), then discard the old. If the swap fails, restore the old so we\n // never end up with no installed collection. Records live in dataPath (a\n // separate dir) and are untouched.\n if (target.updated) await rename(target.targetDir, backup);\n try {\n await rename(staging, target.targetDir);\n } catch (err) {\n if (target.updated) await rename(backup, target.targetDir).catch(() => undefined);\n throw err;\n }\n await rm(backup, { recursive: true, force: true });\n\n // Replicate the new `data/skills/<slug>/` set into `.claude/skills/<slug>/`\n // via the shared bridge package — same allowlist + tmp+rename semantics the\n // hook uses for agent-authored writes. Write-then-prune ordering inside\n // mirrorToClaudeSkills means a transient failure here can't leave the mirror\n // empty (Codex review on #1839); the worst case is a half-written mirror\n // where the new install's SKILL.md/schema.json may not have updated, but the\n // prior install remains accessible to the agent until the next mirror\n // attempt. Logged-not-thrown matches the hook's posture for agent writes.\n try {\n // Skipped where the install already IS the active skill dir — there is\n // nothing to mirror to, and `mirrorSkillWrite` would read a staging file\n // that never exists.\n if (importInstall(workspaceRoot).mirrors) await mirrorToClaudeSkills(workspaceRoot, localSlug, bundle);\n } catch (err) {\n log.warn(\"collections-registry\", \"mirror to .claude/skills/ failed (data/skills write succeeded; prior mirror left intact)\", {\n localSlug,\n error: errorMessage(err),\n });\n }\n\n const seed = await materializeSeed(dataDir, bundle);\n return { ok: true, localSlug, updated: target.updated, seedWritten: seed.written, seedSkipped: seed.skipped };\n}\n\nexport async function performImport(author: string, slug: string, workspaceRoot: string, registry: string | null = null): Promise<ImportResult> {\n // The import's target workspace is also the registry scope — a multi-root\n // host's per-project `collections-registries.json` must be the one consulted.\n const merged = await fetchAllRegistries({ workspaceRoot });\n let entry: RegistryEntry | undefined;\n for (const reg of merged) {\n if (registry !== null && reg.name !== registry) continue;\n entry = reg.entries.find((candidate) => candidate.author === author && candidate.slug === slug);\n if (entry) break;\n }\n if (!entry) return { ok: false, status: STATUS_NOT_FOUND, error: `unknown collection: ${author}/${slug}` };\n const manifest = await fetchManifest(entry, { workspaceRoot });\n if (!manifest.ok) return { ok: false, status: manifest.status, error: manifest.error };\n const bundle = await fetchBundle(entry, manifest.files, { workspaceRoot });\n if (!bundle.ok) return { ok: false, status: bundle.status, error: bundle.error };\n try {\n return await writeImportedCollection({\n registry: entry.registryName,\n entry,\n bundle: bundle.files,\n workspaceRoot,\n nowIso: new Date().toISOString(),\n });\n } catch (err) {\n log.warn(\"collections-registry\", \"import write failed\", { author, slug, registry: entry.registryName, error: errorMessage(err) });\n return { ok: false, status: 500, error: `import failed: ${errorMessage(err)}` };\n }\n}\n\n// Exported for downstream code that wants the path conventions without\n// importing skill-bridge directly.\nexport { claudeSkillDir, dataSkillDir, installDir, hasNonDirAncestor };\n","// Export a workspace collection into the curated registry's contribution layout\n// (collections/<author>/<slug>/ + meta.json + optional seed/) under\n// data/registry-export/, so the user can open a PR to the registry. The producer\n// side of the registry loop.\n//\n// writeCollectionExport takes explicit dirs + a workspace root so it is unit-testable\n// against temp dirs with no discovery/network; performExport (separate file) is the\n// thin glue that resolves the collection from the live workspace.\n\nimport { cp, mkdir, readdir, readFile, rm, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { log } from \"../../server/host.js\";\nimport { toPosixRelPath } from \"../../../files/relPath.js\";\n\nexport const EXPORT_BASE = \"data/registry-export\";\nconst BUNDLE_FILES = [\"SKILL.md\", \"schema.json\"] as const;\nconst OPTIONAL_FILES = [\"screenshot.png\"] as const;\nconst BUNDLE_DIRS = [\"views\", \"templates\"] as const;\nconst AUTHOR_RE = /^[A-Za-z0-9][A-Za-z0-9-]{0,38}$/;\nconst SLUG_RE = /^[a-z0-9][a-z0-9-]{0,62}$/;\nconst STATUS_BAD_REQUEST = 400;\n// Block obvious credentials from being published in seed data; PII is only warned.\nconst SECRET_PATTERNS = [\n /AKIA[0-9A-Z]{16}/,\n /gh[pousr]_[A-Za-z0-9]{20,}/,\n /-----BEGIN [A-Z ]*PRIVATE KEY-----/,\n /sk-[A-Za-z0-9]{20,}/,\n /xox[baprs]-[A-Za-z0-9-]{10,}/,\n];\nconst hasSecret = (text: string): boolean => SECRET_PATTERNS.some((pattern) => pattern.test(text));\n// Bounded + non-overlapping (no ReDoS): local@label.tld with single quantifier runs.\nconst EMAIL_RE = /[\\w.%+-]{1,64}@[\\w-]{1,255}\\.[a-z]{2,24}/i;\n\nexport interface ExportMeta {\n author: string;\n slug: string;\n version: string;\n title: string;\n description: string;\n tags: string[];\n license: string;\n}\n\nexport type ExportResult =\n | { ok: true; outputPath: string; fileCount: number; seedCount: number; seedSkipped: number; warnings: string[] }\n | { ok: false; status: number; error: string };\n\nasync function pathKind(target: string): Promise<\"dir\" | \"file\" | \"absent\"> {\n try {\n return (await stat(target)).isDirectory() ? \"dir\" : \"file\";\n } catch {\n return \"absent\";\n }\n}\n\nasync function copyBundle(skillDir: string, outDir: string): Promise<number> {\n let count = 0;\n for (const file of [...BUNDLE_FILES, ...OPTIONAL_FILES]) {\n const src = path.join(skillDir, file);\n if ((await pathKind(src)) !== \"file\") continue;\n await cp(src, path.join(outDir, file));\n count += 1;\n }\n for (const dir of BUNDLE_DIRS) {\n const src = path.join(skillDir, dir);\n if ((await pathKind(src)) !== \"dir\") continue;\n await cp(src, path.join(outDir, dir), { recursive: true });\n count += (await readdir(src)).filter((name) => !name.startsWith(\".\")).length;\n }\n return count;\n}\n\nasync function exportSeed(dataDir: string, outDir: string): Promise<{ count: number; skipped: number; warnings: string[] }> {\n let names: string[];\n try {\n // Dot-prefixed files are NOT records — the same rule every other reader of\n // a data dir applies (`io.ts#listItems`, `ontology.ts`, the store watcher,\n // `validate.ts`), and the bundle-dir count above. Sync state lives here too\n // (`.calendar-sync.json`), and shipping it would put the user's calendar id\n // in a published bundle and count it toward `dataConsent` (Codex review\n // #2853).\n names = (await readdir(dataDir)).filter((name) => name.endsWith(\".json\") && !name.startsWith(\".\"));\n } catch {\n return { count: 0, skipped: 0, warnings: [] };\n }\n if (names.length === 0) return { count: 0, skipped: 0, warnings: [] };\n const seedDir = path.join(outDir, \"seed\", \"items\");\n await mkdir(seedDir, { recursive: true });\n const warnings: string[] = [];\n let count = 0;\n let skipped = 0;\n for (const name of names) {\n const base = path.basename(name);\n const text = await readFile(path.join(dataDir, base), \"utf-8\");\n if (hasSecret(text)) {\n warnings.push(`skipped ${base}: contains a possible credential`);\n skipped += 1;\n continue;\n }\n if (EMAIL_RE.test(text)) warnings.push(`${base}: contains a possible email/PII (kept — your responsibility)`);\n await writeFile(path.join(seedDir, base), text, \"utf-8\");\n count += 1;\n }\n return { count, skipped, warnings };\n}\n\n// Resolve `segments` under `root` and return the absolute path only when it stays\n// inside `root` — a containment barrier for the user-derived author/slug and the\n// collection dirs, on top of the AUTHOR_RE/SLUG_RE format checks. `..` or an\n// absolute escape resolves outside `root` and yields null (rejected upstream).\nfunction resolveWithin(root: string, ...segments: string[]): string | null {\n const resolvedRoot = path.resolve(root);\n const target = path.resolve(resolvedRoot, ...segments);\n return target === resolvedRoot || target.startsWith(resolvedRoot + path.sep) ? target : null;\n}\n\n// Required bundle files (SKILL.md, schema.json) must be present — exporting an\n// incomplete bundle would pass here but fail downstream registry curation.\n// Returns the first missing required file, or null when all are present.\nasync function findMissingRequired(skillDir: string): Promise<string | null> {\n for (const file of BUNDLE_FILES) {\n if ((await pathKind(path.join(skillDir, file))) !== \"file\") return file;\n }\n return null;\n}\n\nexport async function writeCollectionExport(params: {\n workspaceRoot: string;\n skillDir: string;\n dataDir: string;\n meta: ExportMeta;\n includeSeed: boolean;\n}): Promise<ExportResult> {\n const { workspaceRoot, skillDir, dataDir, meta, includeSeed } = params;\n if (!AUTHOR_RE.test(meta.author)) return { ok: false, status: STATUS_BAD_REQUEST, error: `author '${meta.author}' is not a valid GitHub login` };\n if (!SLUG_RE.test(meta.slug)) return { ok: false, status: STATUS_BAD_REQUEST, error: `slug '${meta.slug}' is invalid` };\n\n const wsRoot = path.resolve(workspaceRoot);\n const exportRoot = path.join(wsRoot, ...EXPORT_BASE.split(\"/\"));\n const outDir = resolveWithin(exportRoot, meta.author, meta.slug);\n const safeSkillDir = resolveWithin(wsRoot, skillDir);\n const safeDataDir = resolveWithin(wsRoot, dataDir);\n if (!outDir || !safeSkillDir || !safeDataDir) {\n return { ok: false, status: STATUS_BAD_REQUEST, error: \"resolved path escapes the workspace\" };\n }\n\n const missing = await findMissingRequired(safeSkillDir);\n if (missing) {\n return { ok: false, status: STATUS_BAD_REQUEST, error: `required bundle file missing: ${missing}` };\n }\n\n await rm(outDir, { recursive: true, force: true });\n await mkdir(outDir, { recursive: true });\n\n const bundleCount = await copyBundle(safeSkillDir, outDir);\n const seed = includeSeed ? await exportSeed(safeDataDir, outDir) : { count: 0, skipped: 0, warnings: [] };\n const metaOut = { ...meta, ...(seed.count > 0 ? { dataConsent: true } : {}) };\n await writeFile(path.join(outDir, \"meta.json\"), `${JSON.stringify(metaOut, null, 2)}\\n`, \"utf-8\");\n log.info(\"collections-registry\", \"exported collection\", { slug: meta.slug, author: meta.author, seedCount: seed.count });\n const outRel = toPosixRelPath(path.relative(wsRoot, outDir));\n return { ok: true, outputPath: outRel, fileCount: bundleCount + 1, seedCount: seed.count, seedSkipped: seed.skipped, warnings: seed.warnings };\n}\n","// `description:` reader for a SKILL.md YAML frontmatter envelope.\n//\n// The host (MulmoClaude) parses SKILL.md frontmatter with js-yaml, but\n// @mulmoclaude/core deliberately carries no YAML dependency and export only needs\n// the single `description` scalar for the registry meta.json (best-effort — it\n// defaults to \"\" when absent). Rather than pull in a YAML parser we scan the\n// envelope for the first `description:` line and resolve the common YAML scalar\n// forms so exported descriptions match what the host produced:\n// - double-quoted (\"…\") → unescaped, trailing inline comment ignored\n// - single-quoted ('…') → '' → ', trailing inline comment ignored\n// - plain (unquoted) → trailing \" #comment\" stripped (YAML comment rule)\n// Block scalars (`description: |`) are NOT expanded — they yield \"\" (skill\n// descriptions are single-line in practice).\n\nconst FENCE = \"---\";\nconst KEY = \"description:\";\nconst BLOCK_SCALAR_INDICATORS = new Set([\"|\", \">\", \"|-\", \">-\", \"|+\", \">+\"]);\n\nconst isYamlSpace = (char: string | undefined): boolean => char === \" \" || char === \"\\t\";\n\n// After a closing quote, YAML allows only whitespace and an optional \"#\" comment.\n// Anything else (e.g. `\"foo\" bar`) is malformed — js-yaml would throw, so we\n// reject (→ null → \"\") rather than silently truncate.\nconst isOnlyTrailingComment = (rest: string): boolean => {\n const trimmed = rest.trimStart();\n return trimmed === \"\" || trimmed.startsWith(\"#\");\n};\n\n// Escapes a description realistically carries inside a double-quoted scalar;\n// any other `\\x` collapses to the literal `x`.\nconst DOUBLE_QUOTE_ESCAPES: Record<string, string> = { n: \"\\n\", t: \"\\t\" };\nconst unescapeDoubleQuote = (next: string): string => DOUBLE_QUOTE_ESCAPES[next] ?? next;\n\n// The text a two-character escape pair at `index` stands for, or null when the\n// pair isn't an escape — which makes `index` an ordinary character (possibly the\n// closing quote). Both YAML quoted forms differ only in this one rule.\ntype EscapeReader = (value: string, index: number) => string | null;\n\n// Walk a quoted scalar from just past its opening quote to the closing one.\n// Returns null for a malformed scalar (no closing quote, or trailing non-comment\n// text) so the caller degrades to \"\" — matching the host's js-yaml behavior.\nfunction scanQuotedScalar(value: string, quote: string, readEscape: EscapeReader): string | null {\n const out: string[] = [];\n // `charAt` (not `value[i]`) so the scanner reads a plain `string`: every\n // index below is in range, and an out-of-range read would be \"\" either way.\n for (let i = 1; i < value.length; i += 1) {\n const escaped = readEscape(value, i);\n if (escaped !== null) {\n out.push(escaped);\n i += 1;\n continue;\n }\n const char = value.charAt(i);\n if (char === quote) return isOnlyTrailingComment(value.slice(i + 1)) ? out.join(\"\") : null;\n out.push(char);\n }\n return null; // unterminated\n}\n\n// A trailing backslash has nothing to escape, so it stays a literal backslash.\nconst readDoubleQuoteEscape: EscapeReader = (value, index) =>\n value.charAt(index) === \"\\\\\" && index + 1 < value.length ? unescapeDoubleQuote(value.charAt(index + 1)) : null;\n\n// The only escape in a single-quoted scalar is a doubled quote ('').\nconst readSingleQuoteEscape: EscapeReader = (value, index) => (value.charAt(index) === \"'\" && value[index + 1] === \"'\" ? \"'\" : null);\n\nfunction parseDoubleQuoted(value: string): string | null {\n return scanQuotedScalar(value, '\"', readDoubleQuoteEscape);\n}\n\nfunction parseSingleQuoted(value: string): string | null {\n return scanQuotedScalar(value, \"'\", readSingleQuoteEscape);\n}\n\n// Plain scalar: a \"#\" preceded by whitespace (or at the start) begins a YAML\n// comment; \"#\" not preceded by whitespace (e.g. \"C#\") is literal.\nfunction stripPlainComment(value: string): string {\n for (let i = 0; i < value.length; i += 1) {\n if (value[i] === \"#\" && (i === 0 || isYamlSpace(value[i - 1]))) return value.slice(0, i);\n }\n return value;\n}\n\n/** Extract the frontmatter `description` from raw SKILL.md text. Returns \"\" when\n * there's no `---` envelope, no `description:` key, or the value is a block\n * scalar indicator. */\nexport function parseSkillDescription(raw: string): string {\n const lines = raw.split(/\\r?\\n/);\n if (lines[0]?.trim() !== FENCE) return \"\";\n for (const line of lines.slice(1)) {\n if (line.trim() === FENCE) return \"\"; // end of envelope, key not found\n if (!line.startsWith(KEY)) continue;\n return resolveScalar(line.slice(KEY.length).trim());\n }\n return \"\";\n}\n\n/** Resolve the raw text after `description:` to its scalar value. */\nfunction resolveScalar(value: string): string {\n if (value === \"\" || BLOCK_SCALAR_INDICATORS.has(value)) return \"\";\n if (value.startsWith('\"')) return parseDoubleQuoted(value) ?? \"\";\n if (value.startsWith(\"'\")) return parseSingleQuoted(value) ?? \"\";\n return stripPlainComment(value).trim();\n}\n","// Thin glue for the collection export: resolve the collection from the live workspace\n// (its skill dir + data dir + schema), derive meta from SKILL.md front-matter / any\n// existing meta.json, then hand off to the testable writeCollectionExport. Kept\n// separate so the file-writing core stays import-light + unit-testable.\n\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { loadCollection } from \"../../server/index.js\";\nimport { isRecord } from \"../guards.js\";\nimport { parseSkillDescription } from \"./skillDescription.js\";\nimport { writeCollectionExport, type ExportMeta, type ExportResult } from \"./exportCollection.js\";\n\nconst STATUS_NOT_FOUND = 404;\nconst STATUS_UNPROCESSABLE = 422;\n\nasync function readJsonObject(file: string): Promise<Record<string, unknown> | null> {\n try {\n const parsed: unknown = JSON.parse(await readFile(file, \"utf-8\"));\n return isRecord(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\nexport async function performExport(\n slug: string,\n opts: { author: string; license: string; includeSeed: boolean },\n workspaceRoot: string,\n): Promise<ExportResult> {\n const collection = await loadCollection(slug);\n if (!collection) return { ok: false, status: STATUS_NOT_FOUND, error: `collection not found: ${slug}` };\n // Mirrors the import-side refusal: a `dataSource` collection's records are\n // a machine-local data file a bundle can't carry.\n if (collection.schema.dataSource !== undefined) {\n return { ok: false, status: STATUS_UNPROCESSABLE, error: `collection '${slug}' is backed by an external dataSource and cannot be exported to a registry` };\n }\n const skillMd = await readFile(path.join(collection.skillDir, \"SKILL.md\"), \"utf-8\").catch(() => \"\");\n const existingMeta = await readJsonObject(path.join(collection.skillDir, \"meta.json\"));\n const meta: ExportMeta = {\n author: opts.author,\n slug,\n version: typeof existingMeta?.version === \"string\" ? existingMeta.version : \"1.0.0\",\n title: collection.schema.title,\n description: parseSkillDescription(skillMd),\n tags: [],\n license: opts.license,\n };\n return writeCollectionExport({ workspaceRoot, skillDir: collection.skillDir, dataDir: collection.dataDir, meta, includeSeed: opts.includeSeed });\n}\n","// @mulmoclaude/core/collection/registry/server — node-only collection registry\n// engine (Discover catalog fetch/cache, import writer, export writer). Shares the\n// `configureCollectionHost` binding with @mulmoclaude/core/collection/server, so a\n// host wires the workspace + logger + paths ONCE and both engines work. The\n// isomorphic contract types live on @mulmoclaude/core/collection/registry.\n\nimport { fetchAllRegistries } from \"./client.js\";\nimport type { RegistryScope } from \"./registriesConfig.js\";\nimport { performImport } from \"./importWriter.js\";\nimport type { RegistryListResponse, RegistryImportResponse } from \"../types.js\";\n\nexport {\n CACHE_TTL_MS,\n STALE_RETRY_BACKOFF_MS,\n fetchAllRegistries,\n fetchRegistryIndex,\n findRegistry,\n listRegistries,\n resetRegistryCache,\n type FetchIndexResult,\n type IndexLoader,\n type MergedRegistryResult,\n type RegistryDescriptor,\n} from \"./client.js\";\nexport {\n collectionFileUrl,\n fetchCollectionFile,\n parseJsonObject,\n previewCollection,\n rawBaseForEntry,\n type FileResult,\n type JsonObjectResult,\n type PreviewResult,\n} from \"./collectionFiles.js\";\nexport {\n fetchBundle,\n fetchManifest,\n isSafeBundlePath,\n normalizedDataPath,\n parseManifest,\n withNormalizedDataPath,\n type BundleFetch,\n type ManifestFetch,\n type ManifestResult,\n} from \"./importCollection.js\";\nexport { claudeSkillDir, dataSkillDir, performImport, writeImportedCollection, type ImportOrigin, type ImportResult } from \"./importWriter.js\";\nexport { EXPORT_BASE, writeCollectionExport, type ExportMeta, type ExportResult } from \"./exportCollection.js\";\nexport { performExport } from \"./performExport.js\";\nexport { parseSkillDescription } from \"./skillDescription.js\";\nexport { loadRegistriesConfig, parseRegistriesConfig, OFFICIAL_REGISTRY_NAME, type RegistryConfigEntry, type RegistryScope } from \"./registriesConfig.js\";\nexport type { RegistryEntry, RegistryIndex, ParseResult } from \"../registryIndex.js\";\nexport { parseRegistryIndex } from \"../registryIndex.js\";\nexport type { RegistrySummary, RegistryListResponse, RegistryImportResponse } from \"../types.js\";\n\n/** Build the merged Discover catalog response (`GET …collectionsRegistry.list`).\n * Wraps `fetchAllRegistries` with the per-registry summary + stale-flag shaping\n * every host needs, so a downstream host doesn't re-implement the mapping. */\nexport async function listRegistry(scope: RegistryScope = {}): Promise<RegistryListResponse> {\n const merged = await fetchAllRegistries(scope);\n return {\n registries: merged.map((reg) => ({\n name: reg.name,\n status: reg.status,\n generatedAt: reg.generatedAt,\n error: reg.error,\n entryCount: reg.entries.length,\n })),\n stale: merged.some((reg) => reg.status === \"stale\"),\n collections: merged.flatMap((reg) => reg.entries),\n };\n}\n\nexport type RegistryImportOutcome = { ok: true; response: RegistryImportResponse } | { ok: false; status: number; error: string };\n\n/** Import a registry collection and return the host-facing response shape\n * (`POST …collectionsRegistry.import`). On failure carries the HTTP status so\n * the host route can pass it straight through. */\nexport async function importRegistry(author: string, slug: string, workspaceRoot: string, registry: string | null = null): Promise<RegistryImportOutcome> {\n const result = await performImport(author, slug, workspaceRoot, registry);\n if (!result.ok) return { ok: false, status: result.status, error: result.error };\n return {\n ok: true,\n response: { localSlug: result.localSlug, updated: result.updated, seedWritten: result.seedWritten, seedSkipped: result.seedSkipped },\n };\n}\n"],"mappings":"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"}
@@ -2,9 +2,9 @@ import { t as errorMessage } from "../../../dist-D8zokgGo.js";
2
2
  import { n as writeFileAtomic } from "../../../root-BMroU_mB.js";
3
3
  import { n as toPosixRelPath } from "../../../relPath-DW8MC8VO.js";
4
4
  import { fetchWithTimeout } from "../../../utils/fetch.js";
5
- import { $ as projectSkillsDir, H as safeRecordId, Y as getWorkspaceRoot, Z as log, nt as skillsStagingDir, q as collectionsRegistriesConfigPath, r as loadCollection, s as CollectionSchemaZ, t as acceptParsedSchema } from "../../../discovery-CC7kuwHf.js";
5
+ import { $ as projectSkillsDir, H as safeRecordId, Y as getWorkspaceRoot, Z as log, nt as skillsStagingDir, q as collectionsRegistriesConfigPath, r as loadCollection, s as CollectionSchemaZ, t as acceptParsedSchema } from "../../../discovery-D7LCN8gJ.js";
6
6
  import { t as isSafeActionTemplatePath } from "../../../templatePath-k_WNbL_Q.js";
7
- import { g as ONE_SECOND_MS } from "../../../server-AA2kfDfk.js";
7
+ import { g as ONE_SECOND_MS } from "../../../server-D6QY853r.js";
8
8
  import { claudeSkillDir, dataSkillDir, mirrorSkillWrite } from "../../../skill-bridge/index.js";
9
9
  import { n as parseRegistryIndex, r as isRecord, t as OFFICIAL_REGISTRY_NAME } from "../../../types-BKVZrtyc.js";
10
10
  import { readFileSync } from "node:fs";
@@ -832,7 +832,7 @@ async function copyBundle(skillDir, outDir) {
832
832
  async function exportSeed(dataDir, outDir) {
833
833
  let names;
834
834
  try {
835
- names = (await readdir(dataDir)).filter((name) => name.endsWith(".json"));
835
+ names = (await readdir(dataDir)).filter((name) => name.endsWith(".json") && !name.startsWith("."));
836
836
  } catch {
837
837
  return {
838
838
  count: 0,