@hawkeyexl/manni 0.2.0 → 0.3.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.
@@ -3,7 +3,7 @@
3
3
  // package.json
4
4
  var package_default = {
5
5
  name: "@hawkeyexl/manni",
6
- version: "0.2.0",
6
+ version: "0.3.0",
7
7
  description: "One command line for documentation that is meant to be checked. `manni meta` validates the presence and format of document metadata (Markdown frontmatter and more) against JSON Schema, built for CI. Formerly docmeta.",
8
8
  type: "module",
9
9
  bin: {
@@ -154,8 +154,8 @@ var RENAME_ATTEMPTS = 3;
154
154
  function errorCode(err) {
155
155
  return typeof err === "object" && err !== null && "code" in err ? String(err.code) : void 0;
156
156
  }
157
- var wait = (ms) => new Promise((resolve12) => {
158
- setTimeout(resolve12, ms);
157
+ var wait = (ms) => new Promise((resolve14) => {
158
+ setTimeout(resolve14, ms);
159
159
  });
160
160
  async function writeFileAtomic(path, contents) {
161
161
  const tmp = join(
@@ -2611,11 +2611,17 @@ var BUILTINS = /* @__PURE__ */ new Map([
2611
2611
  ["anthropic:claude-subagent:2.1", __default23]
2612
2612
  ]);
2613
2613
  function assertPublishableBuiltinId(id) {
2614
- if (id.split(":")[0] === "check") {
2614
+ const first = id.split(":")[0];
2615
+ if (first === "check") {
2615
2616
  throw new Error(
2616
2617
  `Built-in id "${id}" is not publishable: the "check" first segment is reserved for corpus check identities (check:<name>, proposal 0026).`
2617
2618
  );
2618
2619
  }
2620
+ if (first === "sidecar") {
2621
+ throw new Error(
2622
+ `Built-in id "${id}" is not publishable: the "sidecar" first segment is reserved for sidecar finding identities (sidecar:owned, proposal 0037).`
2623
+ );
2624
+ }
2619
2625
  }
2620
2626
  for (const id of BUILTINS.keys()) assertPublishableBuiltinId(id);
2621
2627
  function listBuiltins() {
@@ -2743,8 +2749,8 @@ var SCHEMA_KEYS = [
2743
2749
  "multipleOf"
2744
2750
  ];
2745
2751
  var RETRY_DELAY_MS = 500;
2746
- var wait2 = (ms) => new Promise((resolve12) => {
2747
- setTimeout(resolve12, ms);
2752
+ var wait2 = (ms) => new Promise((resolve14) => {
2753
+ setTimeout(resolve14, ms);
2748
2754
  });
2749
2755
  var RetryableFetchError = class extends DocmetaError {
2750
2756
  };
@@ -2996,6 +3002,277 @@ async function loadSchema(ref, options = {}) {
2996
3002
  }
2997
3003
  }
2998
3004
 
3005
+ // src/meta/core/resolve-schema.ts
3006
+ import { isAbsolute, relative, resolve } from "node:path";
3007
+ import picomatch from "picomatch";
3008
+ var DEFAULT_SCHEMAS = Object.freeze([
3009
+ "google:okf:0.1",
3010
+ "passo-uno:seven-action:1.0"
3011
+ ]);
3012
+ var FILE_SCHEMA_KEY = "$schema";
3013
+ function schemaEntryRef(entry) {
3014
+ return typeof entry === "string" ? entry : entry.ref;
3015
+ }
3016
+ function collectSchemaPins(config) {
3017
+ const pins = /* @__PURE__ */ new Map();
3018
+ for (const entry of config?.schemas ?? []) {
3019
+ if (typeof entry === "string") continue;
3020
+ if (entry.source === void 0 && entry.integrity === void 0) continue;
3021
+ pins.set(entry.ref, {
3022
+ ...entry.source !== void 0 ? { source: entry.source } : {},
3023
+ ...entry.integrity !== void 0 ? { integrity: entry.integrity } : {}
3024
+ });
3025
+ }
3026
+ return pins;
3027
+ }
3028
+ function rebaseConfigSchemaRefs(config, configDir, cwd) {
3029
+ if (resolve(configDir) === resolve(cwd)) return config;
3030
+ const rebase = (ref) => classifyRef(ref).kind === "file" && !isAbsolute(ref) ? resolve(configDir, ref) : ref;
3031
+ const rebaseEntry = (entry) => typeof entry === "string" ? rebase(entry) : {
3032
+ ...entry,
3033
+ ref: rebase(entry.ref),
3034
+ ...entry.source !== void 0 ? { source: rebase(entry.source) } : {}
3035
+ };
3036
+ return {
3037
+ ...config,
3038
+ ...config.schemas ? { schemas: config.schemas.map(rebaseEntry) } : {},
3039
+ ...config.overrides ? {
3040
+ overrides: config.overrides.map((o) => ({
3041
+ ...o,
3042
+ schemas: o.schemas.map(rebase)
3043
+ }))
3044
+ } : {}
3045
+ };
3046
+ }
3047
+ var matcherCache = /* @__PURE__ */ new Map();
3048
+ function overrideGlobs(files) {
3049
+ return typeof files === "string" ? [files] : files;
3050
+ }
3051
+ function matchesFileGlob(glob, filePath) {
3052
+ return matches(glob, filePath);
3053
+ }
3054
+ function matches(glob, filePath) {
3055
+ const key = JSON.stringify(glob);
3056
+ let m = matcherCache.get(key);
3057
+ if (!m) {
3058
+ m = picomatch(glob, { dot: true });
3059
+ matcherCache.set(key, m);
3060
+ }
3061
+ return m(filePath.replace(/\\/g, "/"));
3062
+ }
3063
+ function coerceFileSchema(value) {
3064
+ if (value === void 0 || value === null) return void 0;
3065
+ if (typeof value === "string") return [value];
3066
+ if (Array.isArray(value) && value.every((v) => typeof v === "string")) {
3067
+ return value;
3068
+ }
3069
+ throw new Error(
3070
+ `Invalid "${FILE_SCHEMA_KEY}": must be a string or a list of strings.`
3071
+ );
3072
+ }
3073
+ function dedupe(refs) {
3074
+ return [...new Set(refs)];
3075
+ }
3076
+ function hostAllowed(url, hosts) {
3077
+ const hostname = url.hostname.toLowerCase();
3078
+ const host = url.host.toLowerCase();
3079
+ return hosts.some((h) => {
3080
+ const want = h.trim().toLowerCase();
3081
+ return want === hostname || want === host;
3082
+ });
3083
+ }
3084
+ function assertDocumentRefAllowed(ref, mode, params) {
3085
+ const { kind } = classifyRef(ref);
3086
+ if (kind === "builtin" || isPublishedBuiltinUrl(ref)) return;
3087
+ if (kind === "url") {
3088
+ if (mode === "local") {
3089
+ throw new DocmetaError(
3090
+ `Refusing the "${FILE_SCHEMA_KEY}" URL "${ref}": schemaTrust.documentRefs is "local", so a document may name a built-in id or a schema file inside the repository, but not a URL. Vendor it with \`manni meta schemas vendor ${ref}\` and reference the local copy, or put the URL in \`schemas:\` where an operator controls it.`
3091
+ );
3092
+ }
3093
+ const hosts = params.config?.schemaTrust?.hosts;
3094
+ if (!hosts || hosts.length === 0) return;
3095
+ let url;
3096
+ try {
3097
+ url = new URL(ref);
3098
+ } catch {
3099
+ throw new DocmetaError(
3100
+ `Refusing the "${FILE_SCHEMA_KEY}" URL "${ref}": it cannot be parsed as a URL, so it cannot be checked against schemaTrust.hosts.`
3101
+ );
3102
+ }
3103
+ if (!hostAllowed(url, hosts)) {
3104
+ throw new DocmetaError(
3105
+ `Refusing the "${FILE_SCHEMA_KEY}" URL "${ref}": host "${url.host}" is not in schemaTrust.hosts (${hosts.join(", ")}). Add the host there, or reference a schema the config already names.`
3106
+ );
3107
+ }
3108
+ return;
3109
+ }
3110
+ const root = params.trustRoot;
3111
+ if (!root) return;
3112
+ const abs = resolve(params.fileBase ?? process.cwd(), ref);
3113
+ const within = relative(root.dir, abs);
3114
+ if (within !== "" && !within.startsWith("..") && !isAbsolute(within)) return;
3115
+ const boundary = root.source === "git" ? `the repository root (${root.dir})` : root.source === "config" ? `${root.dir} \u2014 no git repository was found, so the config file's own directory is the boundary` : `${root.dir} \u2014 no git repository and no config file were found, so the directory the command was run from is the boundary`;
3116
+ const scope = root.source === "git" ? "the repository" : "the project";
3117
+ throw new DocmetaError(
3118
+ `Refusing the "${FILE_SCHEMA_KEY}" path "${ref}": it resolves outside ${boundary}. A document may only name a schema inside ${scope}; a path in \`schemas:\` or on \`--schema\` is not restricted this way.`
3119
+ );
3120
+ }
3121
+ function resolveSchemaSetWithSource(params) {
3122
+ const { filePath, fileSchema, cliSchemas, config } = params;
3123
+ if (cliSchemas && cliSchemas.length > 0) {
3124
+ return { schemas: dedupe(cliSchemas), source: "cli" };
3125
+ }
3126
+ const fromFile = coerceFileSchema(fileSchema);
3127
+ if (fromFile && fromFile.length > 0) {
3128
+ const mode = config?.schemaTrust?.documentRefs ?? "any";
3129
+ if (mode === "none") {
3130
+ params.onNotice?.(
3131
+ `${filePath}: "${FILE_SCHEMA_KEY}" is ignored (${fromFile.join(", ")}) \u2014 schemaTrust.documentRefs is "none", so the schema set comes from the config instead. Remove the key from the document, or set schemaTrust.documentRefs to "local" or "any" to honor it.`
3132
+ );
3133
+ } else {
3134
+ for (const ref of fromFile) assertDocumentRefAllowed(ref, mode, params);
3135
+ return { schemas: dedupe(fromFile), source: "document" };
3136
+ }
3137
+ }
3138
+ if (config?.overrides) {
3139
+ for (const [i, ov] of config.overrides.entries()) {
3140
+ if (matches(ov.files, filePath) && ov.schemas.length > 0) {
3141
+ return { schemas: dedupe(ov.schemas), source: "override", overrideIndex: i };
3142
+ }
3143
+ }
3144
+ }
3145
+ if (config?.schemas && config.schemas.length > 0) {
3146
+ return {
3147
+ schemas: dedupe(config.schemas.map(schemaEntryRef)),
3148
+ source: "config"
3149
+ };
3150
+ }
3151
+ return { schemas: [...DEFAULT_SCHEMAS], source: "default" };
3152
+ }
3153
+ function resolveSchemaSet(params) {
3154
+ return resolveSchemaSetWithSource(params).schemas;
3155
+ }
3156
+ function resolveElements(filePath, config) {
3157
+ const out = [];
3158
+ if (config?.elements) out.push(...config.elements);
3159
+ for (const ov of config?.overrides ?? []) {
3160
+ if (ov.elements && matches(ov.files, filePath)) out.push(...ov.elements);
3161
+ }
3162
+ return dedupe(out);
3163
+ }
3164
+
3165
+ // src/meta/core/sidecar-fetch.ts
3166
+ var SIDECAR_FETCH_TIMEOUT_MS = 1e4;
3167
+ var SIDECAR_FETCH_MAX_BYTES = 5 * 1024 * 1024;
3168
+ var MAX_REDIRECTS = 5;
3169
+ var RETRY_DELAY_MS2 = 500;
3170
+ function sidecarUrlProblem(url) {
3171
+ let parsed2;
3172
+ try {
3173
+ parsed2 = new URL(url);
3174
+ } catch {
3175
+ return "is not a valid URL";
3176
+ }
3177
+ if (parsed2.username !== "" || parsed2.password !== "") {
3178
+ return 'carries a credential in the URL; put the token in an environment variable and name it with "tokenEnv"';
3179
+ }
3180
+ if (parsed2.protocol === "https:") return null;
3181
+ if (parsed2.protocol === "http:" && isLoopback(parsed2.hostname)) return null;
3182
+ if (parsed2.protocol === "http:") {
3183
+ return "is plain http://; a bearer token over plaintext is a leak, and a public manifest is served over https too";
3184
+ }
3185
+ return `has the unsupported scheme "${parsed2.protocol}"`;
3186
+ }
3187
+ function isLoopback(host) {
3188
+ return host === "localhost" || host === "127.0.0.1" || host === "[::1]" || host === "::1" || host.startsWith("127.");
3189
+ }
3190
+ async function fetchSidecar(url, opts = {}) {
3191
+ if (opts.offline) {
3192
+ throw new DocmetaError(
3193
+ `Sidecar manifest ${url} is remote and the run is offline. Vendor it to a path, or drop --offline.`
3194
+ );
3195
+ }
3196
+ const timeoutMs = opts.timeoutMs ?? SIDECAR_FETCH_TIMEOUT_MS;
3197
+ const maxBytes = opts.maxBytes ?? SIDECAR_FETCH_MAX_BYTES;
3198
+ const env = opts.env ?? process.env;
3199
+ let token;
3200
+ if (opts.tokenEnv !== void 0) {
3201
+ token = env[opts.tokenEnv];
3202
+ if (token === void 0 || token === "") {
3203
+ throw new DocmetaError(
3204
+ `Sidecar manifest ${url}: the environment variable ${opts.tokenEnv} named by "tokenEnv" is not set.`
3205
+ );
3206
+ }
3207
+ }
3208
+ let res = await requestWithRedirects(url, token, timeoutMs);
3209
+ if (res instanceof Error || res.status >= 500) {
3210
+ await new Promise((r) => setTimeout(r, RETRY_DELAY_MS2));
3211
+ res = await requestWithRedirects(url, token, timeoutMs);
3212
+ }
3213
+ if (res instanceof Error) {
3214
+ throw new DocmetaError(
3215
+ `Sidecar manifest ${url} could not be fetched: ${res.message}`
3216
+ );
3217
+ }
3218
+ if (!res.ok) {
3219
+ const hint = res.status === 404 && token === void 0 ? ' (a private file answers 404 without a token; set "tokenEnv")' : res.status === 404 ? " (a private file answers 404 without a valid token)" : "";
3220
+ throw new DocmetaError(
3221
+ `Sidecar manifest ${url} could not be fetched: HTTP ${res.status}${hint}.`
3222
+ );
3223
+ }
3224
+ return readCapped(url, res, maxBytes);
3225
+ }
3226
+ async function requestWithRedirects(url, token, timeoutMs) {
3227
+ const origin = new URL(url).origin;
3228
+ let current = url;
3229
+ for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
3230
+ const sameOrigin = new URL(current).origin === origin;
3231
+ const headers = { accept: "text/plain, application/x-yaml, */*" };
3232
+ if (token !== void 0 && sameOrigin) headers.authorization = `Bearer ${token}`;
3233
+ let res;
3234
+ try {
3235
+ res = await fetch(current, {
3236
+ headers,
3237
+ redirect: "manual",
3238
+ signal: AbortSignal.timeout(timeoutMs)
3239
+ });
3240
+ } catch (err) {
3241
+ const isAbort2 = err instanceof Error && err.name === "TimeoutError";
3242
+ return isAbort2 ? new Error(`timed out after ${timeoutMs}ms`) : new Error(err instanceof Error ? err.message : String(err));
3243
+ }
3244
+ if (res.status >= 300 && res.status < 400) {
3245
+ const location = res.headers.get("location");
3246
+ if (location === null) return res;
3247
+ await res.body?.cancel();
3248
+ current = new URL(location, current).toString();
3249
+ continue;
3250
+ }
3251
+ return res;
3252
+ }
3253
+ return new Error(`more than ${MAX_REDIRECTS} redirects`);
3254
+ }
3255
+ async function readCapped(url, res, maxBytes) {
3256
+ const body = res.body;
3257
+ if (!body) return "";
3258
+ const reader = body.getReader();
3259
+ const chunks = [];
3260
+ let total = 0;
3261
+ for (; ; ) {
3262
+ const { done, value } = await reader.read();
3263
+ if (done || value === void 0) break;
3264
+ total += value.byteLength;
3265
+ if (total > maxBytes) {
3266
+ await reader.cancel();
3267
+ throw new DocmetaError(
3268
+ `Sidecar manifest ${url} is too large: the response exceeds the ${maxBytes}-byte limit.`
3269
+ );
3270
+ }
3271
+ chunks.push(value);
3272
+ }
3273
+ return Buffer.concat(chunks).toString("utf8");
3274
+ }
3275
+
2999
3276
  // src/meta/extractors/frontmatter.ts
3000
3277
  import { parseDocument, LineCounter, isMap, isSeq, isScalar } from "yaml";
3001
3278
  import { parse as parseToml } from "smol-toml";
@@ -3755,8 +4032,8 @@ function ditaShape(content, root, filePath) {
3755
4032
  const lower = filePath?.toLowerCase() ?? "";
3756
4033
  const byExtension2 = (lower.endsWith(".dita") || lower.endsWith(".ditamap")) && DITA_ROOTS.has(name2);
3757
4034
  if (!byDoctype && !byClass && !byArch && !byExtension2) return void 0;
3758
- const isMap4 = /^\s*[-+]\s+map\//.test(cls) || MAP_ROOTS.has(name2) || lower.endsWith(".ditamap");
3759
- return isMap4 ? { kind: "map", container: "topicmeta", preamble: /* @__PURE__ */ new Set(["title"]) } : {
4035
+ const isMap5 = /^\s*[-+]\s+map\//.test(cls) || MAP_ROOTS.has(name2) || lower.endsWith(".ditamap");
4036
+ return isMap5 ? { kind: "map", container: "topicmeta", preamble: /* @__PURE__ */ new Set(["title"]) } : {
3760
4037
  kind: "topic",
3761
4038
  container: "metadata",
3762
4039
  preamble: /* @__PURE__ */ new Set([
@@ -5181,202 +5458,477 @@ function listFormats() {
5181
5458
  }));
5182
5459
  }
5183
5460
 
5184
- // src/meta/core/resolve-schema.ts
5185
- import { isAbsolute, relative, resolve } from "node:path";
5186
- import picomatch from "picomatch";
5187
- var DEFAULT_SCHEMAS = Object.freeze([
5188
- "google:okf:0.1",
5189
- "passo-uno:seven-action:1.0"
5190
- ]);
5191
- var FILE_SCHEMA_KEY = "$schema";
5192
- function schemaEntryRef(entry) {
5193
- return typeof entry === "string" ? entry : entry.ref;
5194
- }
5195
- function collectSchemaPins(config) {
5196
- const pins = /* @__PURE__ */ new Map();
5197
- for (const entry of config?.schemas ?? []) {
5198
- if (typeof entry === "string") continue;
5199
- if (entry.source === void 0 && entry.integrity === void 0) continue;
5200
- pins.set(entry.ref, {
5201
- ...entry.source !== void 0 ? { source: entry.source } : {},
5202
- ...entry.integrity !== void 0 ? { integrity: entry.integrity } : {}
5203
- });
5461
+ // src/meta/core/sidecars.ts
5462
+ import { readFile as readFile3 } from "node:fs/promises";
5463
+ import { isAbsolute as isAbsolute2, relative as relative4, resolve as resolve5, sep as sep2 } from "node:path";
5464
+ import {
5465
+ LineCounter as LineCounter2,
5466
+ isMap as isMap3,
5467
+ isNode,
5468
+ isScalar as isScalar2,
5469
+ parseDocument as parseDocument3
5470
+ } from "yaml";
5471
+
5472
+ // src/meta/core/load-files.ts
5473
+ import { stat as stat3 } from "node:fs/promises";
5474
+ import { extname, relative as relative3, resolve as resolve4 } from "node:path";
5475
+ import fg from "fast-glob";
5476
+ import picomatch2 from "picomatch";
5477
+
5478
+ // src/meta/core/gitignore.ts
5479
+ import { spawn } from "node:child_process";
5480
+ import { dirname as dirname3, relative as relative2, resolve as resolve3, sep } from "node:path";
5481
+
5482
+ // src/shared/git-root.ts
5483
+ import { existsSync } from "node:fs";
5484
+ import { dirname as dirname2, join as join3, resolve as resolve2 } from "node:path";
5485
+ function findGitRoot(cwd) {
5486
+ let dir = resolve2(cwd);
5487
+ for (; ; ) {
5488
+ if (existsSync(join3(dir, ".git"))) return dir;
5489
+ const parent = dirname2(dir);
5490
+ if (parent === dir) return null;
5491
+ dir = parent;
5204
5492
  }
5205
- return pins;
5206
5493
  }
5207
- function rebaseConfigSchemaRefs(config, configDir, cwd) {
5208
- if (resolve(configDir) === resolve(cwd)) return config;
5209
- const rebase = (ref) => classifyRef(ref).kind === "file" && !isAbsolute(ref) ? resolve(configDir, ref) : ref;
5210
- const rebaseEntry = (entry) => typeof entry === "string" ? rebase(entry) : {
5211
- ...entry,
5212
- ref: rebase(entry.ref),
5213
- ...entry.source !== void 0 ? { source: rebase(entry.source) } : {}
5214
- };
5215
- return {
5216
- ...config,
5217
- ...config.schemas ? { schemas: config.schemas.map(rebaseEntry) } : {},
5218
- ...config.overrides ? {
5219
- overrides: config.overrides.map((o) => ({
5220
- ...o,
5221
- schemas: o.schemas.map(rebase)
5222
- }))
5223
- } : {}
5224
- };
5494
+ function searchPath(cwd) {
5495
+ const start = resolve2(cwd);
5496
+ const root = findGitRoot(start);
5497
+ if (root === null) return [start];
5498
+ const chain = [];
5499
+ let dir = start;
5500
+ for (; ; ) {
5501
+ chain.push(dir);
5502
+ if (dir === root) return chain;
5503
+ const parent = dirname2(dir);
5504
+ if (parent === dir) return chain;
5505
+ dir = parent;
5506
+ }
5225
5507
  }
5226
- var matcherCache = /* @__PURE__ */ new Map();
5227
- function overrideGlobs(files) {
5228
- return typeof files === "string" ? [files] : files;
5508
+
5509
+ // src/meta/core/gitignore.ts
5510
+ var GITIGNORE_UNAVAILABLE = ".gitignore filtering was requested but git could not answer here (no repository, or no git on PATH); no files were skipped.";
5511
+ var unavailable = () => ({
5512
+ ignored: /* @__PURE__ */ new Set(),
5513
+ available: false
5514
+ });
5515
+ async function gitIgnored(paths, cwd) {
5516
+ if (paths.length === 0) {
5517
+ return { ignored: /* @__PURE__ */ new Set(), available: true };
5518
+ }
5519
+ const rootOf = /* @__PURE__ */ new Map();
5520
+ const byRoot = /* @__PURE__ */ new Map();
5521
+ let inSomeRepo = false;
5522
+ for (const spelled of paths) {
5523
+ const abs = resolve3(cwd, spelled);
5524
+ const dir = dirname3(abs);
5525
+ let root = rootOf.get(dir);
5526
+ if (root === void 0) {
5527
+ root = findGitRoot(dir);
5528
+ rootOf.set(dir, root);
5529
+ }
5530
+ if (root === null) continue;
5531
+ inSomeRepo = true;
5532
+ const rel = relative2(root, abs).split(sep).join("/");
5533
+ const bucket = byRoot.get(root);
5534
+ if (bucket) bucket.push({ rel, spelled });
5535
+ else byRoot.set(root, [{ rel, spelled }]);
5536
+ }
5537
+ if (!inSomeRepo) return unavailable();
5538
+ const ignored = /* @__PURE__ */ new Set();
5539
+ let answered = 0;
5540
+ for (const [root, bucket] of byRoot) {
5541
+ const answer = await checkIgnore(
5542
+ bucket.map((b) => b.rel),
5543
+ root
5544
+ );
5545
+ if (!answer.available) continue;
5546
+ answered += 1;
5547
+ for (const b of bucket) {
5548
+ if (answer.ignored.has(b.rel)) ignored.add(b.spelled);
5549
+ }
5550
+ }
5551
+ return answered > 0 ? { ignored, available: true } : unavailable();
5229
5552
  }
5230
- function matchesFileGlob(glob, filePath) {
5231
- return matches(glob, filePath);
5553
+ function checkIgnore(paths, cwd) {
5554
+ return new Promise((settle) => {
5555
+ let done = false;
5556
+ const finish = (answer) => {
5557
+ if (done) return;
5558
+ done = true;
5559
+ settle(answer);
5560
+ };
5561
+ let child;
5562
+ try {
5563
+ child = spawn("git", ["check-ignore", "--stdin", "-z"], {
5564
+ cwd,
5565
+ windowsHide: true
5566
+ });
5567
+ } catch {
5568
+ finish(unavailable());
5569
+ return;
5570
+ }
5571
+ let out = "";
5572
+ child.stdout.setEncoding("utf8");
5573
+ child.stdout.on("data", (chunk) => {
5574
+ out += chunk;
5575
+ });
5576
+ child.stderr.resume();
5577
+ child.on("error", () => {
5578
+ finish(unavailable());
5579
+ });
5580
+ child.stdin.on("error", () => {
5581
+ });
5582
+ child.on("close", (code) => {
5583
+ if (code !== 0 && code !== 1) {
5584
+ finish(unavailable());
5585
+ return;
5586
+ }
5587
+ finish({
5588
+ ignored: new Set(out.split("\0").filter((p) => p !== "")),
5589
+ available: true
5590
+ });
5591
+ });
5592
+ child.stdin.end(paths.join("\0"));
5593
+ });
5232
5594
  }
5233
- function matches(glob, filePath) {
5234
- const key = JSON.stringify(glob);
5235
- let m = matcherCache.get(key);
5236
- if (!m) {
5237
- m = picomatch(glob, { dot: true });
5238
- matcherCache.set(key, m);
5239
- }
5240
- return m(filePath.replace(/\\/g, "/"));
5595
+
5596
+ // src/meta/core/load-files.ts
5597
+ var DEFAULT_IGNORE = ["**/node_modules/**", "**/.git/**"];
5598
+ var STDIN_TOKEN = "-";
5599
+ var STDIN_LABEL = "<stdin>";
5600
+ function toPosix(p) {
5601
+ return p.replace(/\\/g, "/");
5241
5602
  }
5242
- function coerceFileSchema(value) {
5243
- if (value === void 0 || value === null) return void 0;
5244
- if (typeof value === "string") return [value];
5245
- if (Array.isArray(value) && value.every((v) => typeof v === "string")) {
5246
- return value;
5603
+ async function statOrNull(p) {
5604
+ try {
5605
+ return await stat3(p);
5606
+ } catch {
5607
+ return null;
5247
5608
  }
5248
- throw new Error(
5249
- `Invalid "${FILE_SCHEMA_KEY}": must be a string or a list of strings.`
5250
- );
5251
- }
5252
- function dedupe(refs) {
5253
- return [...new Set(refs)];
5254
5609
  }
5255
- function hostAllowed(url, hosts) {
5256
- const hostname = url.hostname.toLowerCase();
5257
- const host = url.host.toLowerCase();
5258
- return hosts.some((h) => {
5259
- const want = h.trim().toLowerCase();
5260
- return want === hostname || want === host;
5261
- });
5610
+ function gitignoreOptions(opts) {
5611
+ const asked = opts.flag === true || opts.flag === void 0 && opts.configured === true;
5612
+ const notice = opts.onNotice;
5613
+ return {
5614
+ respectGitignore: opts.flag ?? opts.configured ?? true,
5615
+ ...asked && notice ? {
5616
+ onGitignoreUnavailable: () => {
5617
+ notice(GITIGNORE_UNAVAILABLE);
5618
+ }
5619
+ } : {}
5620
+ };
5262
5621
  }
5263
- function assertDocumentRefAllowed(ref, mode, params) {
5264
- const { kind } = classifyRef(ref);
5265
- if (kind === "builtin" || isPublishedBuiltinUrl(ref)) return;
5266
- if (kind === "url") {
5267
- if (mode === "local") {
5268
- throw new DocmetaError(
5269
- `Refusing the "${FILE_SCHEMA_KEY}" URL "${ref}": schemaTrust.documentRefs is "local", so a document may name a built-in id or a schema file inside the repository, but not a URL. Vendor it with \`manni meta schemas vendor ${ref}\` and reference the local copy, or put the URL in \`schemas:\` where an operator controls it.`
5270
- );
5622
+ async function resolveTargetSet(opts) {
5623
+ const cwd = opts.cwd ?? process.cwd();
5624
+ const exts = (opts.exts ?? supportedExtensions()).map(
5625
+ (e) => e.toLowerCase().startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`
5626
+ );
5627
+ const ignore = [...DEFAULT_IGNORE, ...opts.exclude ?? []];
5628
+ const named = /* @__PURE__ */ new Set();
5629
+ const walked = /* @__PURE__ */ new Set();
5630
+ const keepByExt = (file) => exts.includes(extname(file).toLowerCase());
5631
+ const missing = [];
5632
+ for (const input of opts.inputs) {
5633
+ if (input === STDIN_TOKEN) continue;
5634
+ const posixInput = toPosix(input);
5635
+ const abs = resolve4(cwd, posixInput);
5636
+ const st = await statOrNull(abs);
5637
+ if (st?.isFile()) {
5638
+ named.add(toPosix(relative3(cwd, abs)));
5639
+ continue;
5271
5640
  }
5272
- const hosts = params.config?.schemaTrust?.hosts;
5273
- if (!hosts || hosts.length === 0) return;
5274
- let url;
5275
- try {
5276
- url = new URL(ref);
5277
- } catch {
5278
- throw new DocmetaError(
5279
- `Refusing the "${FILE_SCHEMA_KEY}" URL "${ref}": it cannot be parsed as a URL, so it cannot be checked against schemaTrust.hosts.`
5280
- );
5641
+ if (st?.isDirectory()) {
5642
+ const found2 = await fg(`${posixInput}/**/*`, {
5643
+ cwd,
5644
+ ignore,
5645
+ onlyFiles: true,
5646
+ dot: false
5647
+ });
5648
+ for (const f of found2) if (keepByExt(f)) walked.add(f);
5649
+ continue;
5281
5650
  }
5282
- if (!hostAllowed(url, hosts)) {
5283
- throw new DocmetaError(
5284
- `Refusing the "${FILE_SCHEMA_KEY}" URL "${ref}": host "${url.host}" is not in schemaTrust.hosts (${hosts.join(", ")}). Add the host there, or reference a schema the config already names.`
5285
- );
5651
+ if (!picomatch2.scan(posixInput).isGlob) {
5652
+ missing.push(posixInput);
5653
+ continue;
5286
5654
  }
5287
- return;
5655
+ const found = await fg(posixInput, {
5656
+ cwd,
5657
+ ignore,
5658
+ onlyFiles: true,
5659
+ dot: false
5660
+ });
5661
+ for (const f of found) if (keepByExt(f)) walked.add(f);
5662
+ }
5663
+ if (missing.length > 0 && !opts.allowEmpty) {
5664
+ const names = missing.map((m) => `"${m}"`).join(", ");
5665
+ throw new DocmetaError(
5666
+ missing.length === 1 ? `File not found: ${names}.` : `Files not found: ${names}.`
5667
+ );
5668
+ }
5669
+ let gitignoreSkipped = 0;
5670
+ if (opts.respectGitignore !== false && walked.size > 0) {
5671
+ const candidates = [...walked].filter((f) => !named.has(f));
5672
+ const { ignored, available } = await gitIgnored(candidates, cwd);
5673
+ if (available) {
5674
+ for (const f of ignored) {
5675
+ if (walked.delete(f)) gitignoreSkipped += 1;
5676
+ }
5677
+ } else {
5678
+ opts.onGitignoreUnavailable?.();
5679
+ }
5680
+ }
5681
+ return {
5682
+ files: [.../* @__PURE__ */ new Set([...named, ...walked])].sort(),
5683
+ gitignoreSkipped
5684
+ };
5685
+ }
5686
+ function assertNonEmpty(p) {
5687
+ if (p.allowEmpty || p.usingStdin || p.files.length > 0) return;
5688
+ const tried = p.inputs.map((i) => `"${i}"`).join(", ");
5689
+ const notes = [];
5690
+ if (p.exts && p.exts.length > 0) {
5691
+ notes.push(`extensions: ${p.exts.join(", ")}`);
5692
+ }
5693
+ if (p.exclude && p.exclude.length > 0) {
5694
+ notes.push(`excludes: ${p.exclude.map((e) => `"${e}"`).join(", ")}`);
5695
+ }
5696
+ if (p.gitignoreSkipped && p.gitignoreSkipped > 0) {
5697
+ notes.push(
5698
+ `.gitignore skipped ${p.gitignoreSkipped} (pass --no-gitignore to check them)`
5699
+ );
5288
5700
  }
5289
- const root = params.trustRoot;
5290
- if (!root) return;
5291
- const abs = resolve(params.fileBase ?? process.cwd(), ref);
5292
- const within = relative(root.dir, abs);
5293
- if (within !== "" && !within.startsWith("..") && !isAbsolute(within)) return;
5294
- const boundary = root.source === "git" ? `the repository root (${root.dir})` : root.source === "config" ? `${root.dir} \u2014 no git repository was found, so the config file's own directory is the boundary` : `${root.dir} \u2014 no git repository and no config file were found, so the directory the command was run from is the boundary`;
5295
- const scope = root.source === "git" ? "the repository" : "the project";
5296
5701
  throw new DocmetaError(
5297
- `Refusing the "${FILE_SCHEMA_KEY}" path "${ref}": it resolves outside ${boundary}. A document may only name a schema inside ${scope}; a path in \`schemas:\` or on \`--schema\` is not restricted this way.`
5702
+ [
5703
+ `No files matched. Patterns tried: ${tried}.`,
5704
+ ...notes.length > 0 ? [`Filters applied \u2014 ${notes.join("; ")}.`] : [],
5705
+ `Nothing was ${p.action}, so this is an error rather than a pass.`,
5706
+ `Pass --allow-empty (or set allowEmpty: true) if matching nothing is expected.`
5707
+ ].join("\n")
5298
5708
  );
5299
5709
  }
5300
- function resolveSchemaSetWithSource(params) {
5301
- const { filePath, fileSchema, cliSchemas, config } = params;
5302
- if (cliSchemas && cliSchemas.length > 0) {
5303
- return { schemas: dedupe(cliSchemas), source: "cli" };
5304
- }
5305
- const fromFile = coerceFileSchema(fileSchema);
5306
- if (fromFile && fromFile.length > 0) {
5307
- const mode = config?.schemaTrust?.documentRefs ?? "any";
5308
- if (mode === "none") {
5309
- params.onNotice?.(
5310
- `${filePath}: "${FILE_SCHEMA_KEY}" is ignored (${fromFile.join(", ")}) \u2014 schemaTrust.documentRefs is "none", so the schema set comes from the config instead. Remove the key from the document, or set schemaTrust.documentRefs to "local" or "any" to honor it.`
5311
- );
5710
+
5711
+ // src/meta/core/sidecars.ts
5712
+ var SIDECAR_OWNED_SCHEMA = "sidecar:owned";
5713
+ var SIDECAR_DUPLICATE_SCHEMA = "sidecar:duplicate";
5714
+ var SIDECAR_KEYWORD = "sidecar";
5715
+ var PATH_JOIN = "path";
5716
+ var posix = (p) => p.split(sep2).join("/");
5717
+ function reportedPath(abs, base) {
5718
+ const rel = relative4(base, abs);
5719
+ return rel === "" ? "." : posix(rel);
5720
+ }
5721
+ function sidecarJoin(sidecar) {
5722
+ return sidecar.join ?? PATH_JOIN;
5723
+ }
5724
+ async function loadSidecars(config, opts) {
5725
+ const configured = config?.sidecars;
5726
+ if (!configured || configured.length === 0) return null;
5727
+ const owners = /* @__PURE__ */ new Map();
5728
+ const byPath = /* @__PURE__ */ new Map();
5729
+ const byField = /* @__PURE__ */ new Map();
5730
+ const entries = [];
5731
+ for (const sidecar of configured) {
5732
+ let text;
5733
+ let file;
5734
+ if (classifyRef(sidecar.file).kind === "url") {
5735
+ file = sidecar.file;
5736
+ text = await fetchSidecar(sidecar.file, {
5737
+ ...sidecar.tokenEnv !== void 0 ? { tokenEnv: sidecar.tokenEnv } : {},
5738
+ ...opts.offline !== void 0 ? { offline: opts.offline } : {},
5739
+ ...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
5740
+ ...opts.env !== void 0 ? { env: opts.env } : {}
5741
+ });
5312
5742
  } else {
5313
- for (const ref of fromFile) assertDocumentRefAllowed(ref, mode, params);
5314
- return { schemas: dedupe(fromFile), source: "document" };
5743
+ const abs = isAbsolute2(sidecar.file) ? sidecar.file : resolve5(opts.configDir, sidecar.file);
5744
+ file = reportedPath(abs, opts.base);
5745
+ text = await readManifest(abs, file);
5746
+ }
5747
+ for (const key of sidecar.keys) owners.set(key, file);
5748
+ const join7 = sidecarJoin(sidecar);
5749
+ let target;
5750
+ if (join7 === PATH_JOIN) {
5751
+ target = byPath;
5752
+ } else {
5753
+ const existing = byField.get(join7);
5754
+ if (existing) target = existing;
5755
+ else {
5756
+ target = /* @__PURE__ */ new Map();
5757
+ byField.set(join7, target);
5758
+ }
5315
5759
  }
5760
+ parseManifest(sidecar, text, file, opts.configDir, join7, target, entries);
5316
5761
  }
5317
- if (config?.overrides) {
5318
- for (const [i, ov] of config.overrides.entries()) {
5319
- if (matches(ov.files, filePath) && ov.schemas.length > 0) {
5320
- return { schemas: dedupe(ov.schemas), source: "override", overrideIndex: i };
5762
+ return { owners, byPath, byField, entries };
5763
+ }
5764
+ async function readManifest(abs, file) {
5765
+ try {
5766
+ return await readFile3(abs, "utf8");
5767
+ } catch (err) {
5768
+ const reason = err instanceof Error ? err.message : String(err);
5769
+ throw new DocmetaError(
5770
+ `Sidecar manifest ${file} could not be read: ${reason}`
5771
+ );
5772
+ }
5773
+ }
5774
+ function parseManifest(sidecar, text, file, configDir, join7, target, entries) {
5775
+ const lc = new LineCounter2();
5776
+ const doc = parseDocument3(text, { lineCounter: lc, uniqueKeys: false });
5777
+ const problem = doc.errors[0];
5778
+ if (problem) {
5779
+ throw new DocmetaError(
5780
+ `Sidecar manifest ${file} is not valid YAML: ${problem.message}`
5781
+ );
5782
+ }
5783
+ const root = doc.contents;
5784
+ if (root === null || isScalar2(root) && root.value === null) return;
5785
+ if (!isMap3(root)) {
5786
+ throw new DocmetaError(
5787
+ `Sidecar manifest ${file}: the manifest must be a mapping from document ${join7 === PATH_JOIN ? "path" : `"${join7}"`} to owned keys.`
5788
+ );
5789
+ }
5790
+ const owned = new Set(sidecar.keys);
5791
+ const lineAt = (node) => {
5792
+ const range = isNode(node) ? node.range : void 0;
5793
+ return range ? lc.linePos(range[0]).line : void 0;
5794
+ };
5795
+ const seen = /* @__PURE__ */ new Map();
5796
+ for (const pair of root.items) {
5797
+ const spelled = isScalar2(pair.key) ? String(pair.key.value) : String(pair.key);
5798
+ const entryLine = lineAt(pair.key);
5799
+ const where = entryLine === void 0 ? file : `${file}:${entryLine}`;
5800
+ if (seen.has(spelled)) {
5801
+ const first = seen.get(spelled);
5802
+ throw new DocmetaError(
5803
+ `Sidecar manifest ${where}: "${spelled}" is named twice (first at line ${first ?? "?"}). Merge the two entries into one.`
5804
+ );
5805
+ }
5806
+ seen.set(spelled, entryLine);
5807
+ if (!isMap3(pair.value)) {
5808
+ throw new DocmetaError(
5809
+ `Sidecar manifest ${where}: "${spelled}" must be a mapping of owned keys to values.`
5810
+ );
5811
+ }
5812
+ const indexKey = join7 === PATH_JOIN ? resolve5(configDir, spelled) : spelled;
5813
+ const values = target.get(indexKey) ?? /* @__PURE__ */ new Map();
5814
+ for (const kv of pair.value.items) {
5815
+ const key = isScalar2(kv.key) ? String(kv.key.value) : String(kv.key);
5816
+ if (key === FILE_SCHEMA_KEY) {
5817
+ throw new DocmetaError(
5818
+ `Sidecar manifest ${where}: "${spelled}" sets "${FILE_SCHEMA_KEY}" \u2014 a sidecar never chooses the schema a document is judged by; use "overrides" in the config.`
5819
+ );
5321
5820
  }
5821
+ if (!owned.has(key)) {
5822
+ throw new DocmetaError(
5823
+ `Sidecar manifest ${where}: "${spelled}" sets "${key}", which this sidecar does not own. Add it to the sidecar's "keys", or remove it from the entry.`
5824
+ );
5825
+ }
5826
+ const value = kv.value === null ? null : kv.value.toJS(doc, { maxAliasCount: 100 });
5827
+ const line = lineAt(kv.key);
5828
+ values.set(key, { value, file, ...line === void 0 ? {} : { line } });
5322
5829
  }
5830
+ target.set(indexKey, values);
5831
+ entries.push({
5832
+ join: join7,
5833
+ ...join7 === PATH_JOIN ? { abs: indexKey } : {},
5834
+ spelled,
5835
+ file,
5836
+ ...entryLine === void 0 ? {} : { line: entryLine }
5837
+ });
5323
5838
  }
5324
- if (config?.schemas && config.schemas.length > 0) {
5325
- return {
5326
- schemas: dedupe(config.schemas.map(schemaEntryRef)),
5327
- source: "config"
5328
- };
5329
- }
5330
- return { schemas: [...DEFAULT_SCHEMAS], source: "default" };
5331
5839
  }
5332
- function resolveSchemaSet(params) {
5333
- return resolveSchemaSetWithSource(params).schemas;
5840
+ function joinValue(raw) {
5841
+ if (typeof raw === "string") return raw;
5842
+ if (typeof raw === "number" || typeof raw === "bigint") return String(raw);
5843
+ if (typeof raw === "boolean") return String(raw);
5844
+ return void 0;
5334
5845
  }
5335
- function resolveElements(filePath, config) {
5336
- const out = [];
5337
- if (config?.elements) out.push(...config.elements);
5338
- for (const ov of config?.overrides ?? []) {
5339
- if (ov.elements && matches(ov.files, filePath)) out.push(...ov.elements);
5846
+ function mergeSidecars(label, extracted, index, base) {
5847
+ const none = () => void 0;
5848
+ if (!index) return { extracted, collisions: [], joins: [], locate: none };
5849
+ const collisions = [];
5850
+ for (const key of Object.keys(extracted.data)) {
5851
+ const file = index.owners.get(key);
5852
+ if (file !== void 0) collisions.push({ key, file });
5853
+ }
5854
+ const sources = [];
5855
+ const joins = [];
5856
+ if (label !== STDIN_LABEL) {
5857
+ const byPath = index.byPath.get(resolve5(base, label));
5858
+ if (byPath) sources.push(byPath);
5859
+ }
5860
+ for (const [field, byValue] of index.byField) {
5861
+ const value = joinValue(extracted.data[field]);
5862
+ if (value === void 0) continue;
5863
+ const hit = byValue.get(value);
5864
+ if (!hit) continue;
5865
+ sources.push(hit);
5866
+ const first = hit.values().next().value;
5867
+ joins.push({ field, value, file: first?.file ?? "manifest" });
5340
5868
  }
5341
- return dedupe(out);
5869
+ if (sources.length === 0) {
5870
+ return { extracted, collisions, joins, locate: none };
5871
+ }
5872
+ const data = { ...extracted.data };
5873
+ const merged = /* @__PURE__ */ new Map();
5874
+ for (const supplied of sources) {
5875
+ for (const [key, sv] of supplied) {
5876
+ if (key in extracted.data) continue;
5877
+ data[key] = sv.value;
5878
+ merged.set(key, sv);
5879
+ }
5880
+ }
5881
+ const locate = (pointer) => {
5882
+ const top = topLevelKey(pointer);
5883
+ if (top === void 0) return void 0;
5884
+ const sv = merged.get(top);
5885
+ if (!sv) return void 0;
5886
+ return { file: sv.file, ...sv.line === void 0 ? {} : { line: sv.line } };
5887
+ };
5888
+ return { extracted: { ...extracted, data }, collisions, joins, locate };
5889
+ }
5890
+ function topLevelKey(pointer) {
5891
+ if (pointer === "") return void 0;
5892
+ if (!pointer.startsWith("/")) return pointer;
5893
+ const seg = pointer.slice(1).split("/")[0] ?? "";
5894
+ return seg.replace(/~1/g, "/").replace(/~0/g, "~");
5895
+ }
5896
+ function sidecarPointer(key) {
5897
+ return `/${escapePointerSegment(key)}`;
5898
+ }
5899
+ function orphanEntries(index, loaded, base) {
5900
+ if (!index) return [];
5901
+ const have = new Set(loaded.map((l) => resolve5(base, l)));
5902
+ return index.entries.filter(
5903
+ (e) => e.join === PATH_JOIN && e.abs !== void 0 && !have.has(e.abs)
5904
+ );
5905
+ }
5906
+ function orphanJoins(index, matched) {
5907
+ if (!index) return [];
5908
+ return index.entries.filter(
5909
+ (e) => e.join !== PATH_JOIN && !(matched.get(e.join)?.has(e.spelled) ?? false)
5910
+ );
5911
+ }
5912
+ function orphanError(orphans) {
5913
+ const first = orphans[0];
5914
+ if (!first) throw new Error("orphanError called with no orphans");
5915
+ const where = first.line === void 0 ? first.file : `${first.file}:${first.line}`;
5916
+ const more = orphans.length > 1 ? ` (${orphans.length - 1} more)` : "";
5917
+ const what = first.join === PATH_JOIN ? `names "${first.spelled}", which this run did not load` : `names ${first.join} "${first.spelled}", which no loaded document carries`;
5918
+ return new DocmetaError(
5919
+ `Sidecar manifest ${where} ${what}${more}. Fix the entry, or remove it.`
5920
+ );
5342
5921
  }
5343
5922
 
5344
5923
  // src/meta/core/config.ts
5345
- import { resolve as resolve4 } from "node:path";
5924
+ import { resolve as resolve7 } from "node:path";
5346
5925
  import { parse as parseYaml2 } from "yaml";
5347
5926
 
5348
5927
  // src/shared/config-file.ts
5349
- import { readFile as readFile3 } from "node:fs/promises";
5350
- import { dirname as dirname3, join as join4, relative as relative2, resolve as resolve3 } from "node:path";
5928
+ import { readFile as readFile4 } from "node:fs/promises";
5929
+ import { dirname as dirname4, join as join4, relative as relative5, resolve as resolve6 } from "node:path";
5351
5930
  import { parse as parseYaml } from "yaml";
5352
5931
 
5353
- // src/shared/git-root.ts
5354
- import { existsSync } from "node:fs";
5355
- import { dirname as dirname2, join as join3, resolve as resolve2 } from "node:path";
5356
- function findGitRoot(cwd) {
5357
- let dir = resolve2(cwd);
5358
- for (; ; ) {
5359
- if (existsSync(join3(dir, ".git"))) return dir;
5360
- const parent = dirname2(dir);
5361
- if (parent === dir) return null;
5362
- dir = parent;
5363
- }
5364
- }
5365
- function searchPath(cwd) {
5366
- const start = resolve2(cwd);
5367
- const root = findGitRoot(start);
5368
- if (root === null) return [start];
5369
- const chain = [];
5370
- let dir = start;
5371
- for (; ; ) {
5372
- chain.push(dir);
5373
- if (dir === root) return chain;
5374
- const parent = dirname2(dir);
5375
- if (parent === dir) return chain;
5376
- dir = parent;
5377
- }
5378
- }
5379
-
5380
5932
  // src/shared/warn.ts
5381
5933
  var said = /* @__PURE__ */ new Set();
5382
5934
  function warn(message) {
@@ -5398,7 +5950,7 @@ var MOOSE_CONFIG_NAMES = [
5398
5950
  async function readDocument(path, source, opts) {
5399
5951
  let text;
5400
5952
  try {
5401
- text = await readFile3(path, "utf8");
5953
+ text = await readFile4(path, "utf8");
5402
5954
  } catch {
5403
5955
  return null;
5404
5956
  }
@@ -5425,10 +5977,10 @@ function slice(document, opts) {
5425
5977
  return null;
5426
5978
  }
5427
5979
  function relativeSource(cwd, path) {
5428
- return relative2(cwd, path).replace(/\\/g, "/");
5980
+ return relative5(cwd, path).replace(/\\/g, "/");
5429
5981
  }
5430
5982
  async function findConfigFile(cwd, opts) {
5431
- const start = resolve3(cwd);
5983
+ const start = resolve6(cwd);
5432
5984
  for (const dir of searchPath(start)) {
5433
5985
  const family = [
5434
5986
  { names: FAMILY_CONFIG_NAMES, kind: "manni" },
@@ -5472,7 +6024,7 @@ async function findConfigFile(cwd, opts) {
5472
6024
  return null;
5473
6025
  }
5474
6026
  async function readConfigFile(explicitPath, cwd, opts) {
5475
- const path = resolve3(cwd, explicitPath);
6027
+ const path = resolve6(cwd, explicitPath);
5476
6028
  const document = await readDocument(path, explicitPath, opts);
5477
6029
  if (document === null) {
5478
6030
  throw opts.toError(`Config file not found: "${explicitPath}".`);
@@ -5483,7 +6035,7 @@ async function readConfigFile(explicitPath, cwd, opts) {
5483
6035
  };
5484
6036
  return {
5485
6037
  path,
5486
- dir: dirname3(path),
6038
+ dir: dirname4(path),
5487
6039
  source: explicitPath,
5488
6040
  text: document.text,
5489
6041
  kind: "explicit",
@@ -5509,6 +6061,7 @@ function asElementPaths(value, where, source) {
5509
6061
  }
5510
6062
  var OVERRIDE_KEYS = ["name", "files", "schemas", "elements"];
5511
6063
  var CHECK_KEYS = ["name", "query"];
6064
+ var SIDECAR_KEYS = ["file", "keys", "tokenEnv", "join"];
5512
6065
  var CHECK_NAME = /^[a-z0-9][a-z0-9._-]*$/;
5513
6066
  var RESERVED_CHECK_NAMES = /* @__PURE__ */ new Set(["query"]);
5514
6067
  var FILL_KEYS = [
@@ -5525,6 +6078,7 @@ var CONFIG_KEYS = [
5525
6078
  "schemas",
5526
6079
  "overrides",
5527
6080
  "checks",
6081
+ "sidecars",
5528
6082
  "baseline",
5529
6083
  "allowEmpty",
5530
6084
  "respectGitignore",
@@ -5668,6 +6222,93 @@ function withSection(message, source, section) {
5668
6222
  if (/^[a-zA-Z]+(\[|\.| —)/.test(rest)) return `${head}${section}.${rest}`;
5669
6223
  return message;
5670
6224
  }
6225
+ function parseSidecars(raw, source) {
6226
+ if (!Array.isArray(raw)) {
6227
+ throw new DocmetaError(`${source}: "sidecars" must be a list.`);
6228
+ }
6229
+ const owners = /* @__PURE__ */ new Map();
6230
+ return raw.map((entry, i) => {
6231
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
6232
+ throw new DocmetaError(`${source}: sidecars[${i}] must be a mapping.`);
6233
+ }
6234
+ const e = entry;
6235
+ rejectUnknownKeys(e, SIDECAR_KEYS, `sidecars[${i}]`, source);
6236
+ if (typeof e.file !== "string" || e.file.trim() === "") {
6237
+ throw new DocmetaError(
6238
+ `${source}: sidecars[${i}].file must be a non-empty string naming the manifest, relative to the config file.`
6239
+ );
6240
+ }
6241
+ if (!Array.isArray(e.keys) || e.keys.length === 0 || e.keys.some((k) => typeof k !== "string" || k.trim() === "")) {
6242
+ throw new DocmetaError(
6243
+ `${source}: sidecars[${i}].keys must be a non-empty list of key names.`
6244
+ );
6245
+ }
6246
+ const isUrl = classifyRef(e.file).kind === "url";
6247
+ if (isUrl) {
6248
+ const problem = sidecarUrlProblem(e.file);
6249
+ if (problem !== null) {
6250
+ throw new DocmetaError(`${source}: sidecars[${i}].file ${problem}.`);
6251
+ }
6252
+ }
6253
+ if (e.tokenEnv !== void 0) {
6254
+ if (typeof e.tokenEnv !== "string" || e.tokenEnv.trim() === "") {
6255
+ throw new DocmetaError(
6256
+ `${source}: sidecars[${i}].tokenEnv must be the name of an environment variable.`
6257
+ );
6258
+ }
6259
+ if (!isUrl) {
6260
+ throw new DocmetaError(
6261
+ `${source}: sidecars[${i}].tokenEnv is set, but "file" is a path, so no request is made and the token would go nowhere. Remove it, or make "file" a URL.`
6262
+ );
6263
+ }
6264
+ }
6265
+ const keys = e.keys;
6266
+ if (e.join !== void 0) {
6267
+ if (typeof e.join !== "string" || e.join.trim() === "") {
6268
+ throw new DocmetaError(
6269
+ `${source}: sidecars[${i}].join must be "path" or the name of a top-level frontmatter field.`
6270
+ );
6271
+ }
6272
+ if (e.join === FILE_SCHEMA_KEY) {
6273
+ throw new DocmetaError(
6274
+ `${source}: sidecars[${i}].join may not be "${FILE_SCHEMA_KEY}".`
6275
+ );
6276
+ }
6277
+ if (keys.includes(e.join)) {
6278
+ throw new DocmetaError(
6279
+ `${source}: sidecars[${i}].join names "${e.join}", which the same entry owns \u2014 the value that selects an entry cannot come from the entry.`
6280
+ );
6281
+ }
6282
+ }
6283
+ const seen = /* @__PURE__ */ new Set();
6284
+ for (const key of keys) {
6285
+ if (key === FILE_SCHEMA_KEY) {
6286
+ throw new DocmetaError(
6287
+ `${source}: sidecars[${i}].keys may not include "${FILE_SCHEMA_KEY}" \u2014 a sidecar never chooses the schema a document is judged by; use "overrides".`
6288
+ );
6289
+ }
6290
+ if (seen.has(key)) {
6291
+ throw new DocmetaError(
6292
+ `${source}: sidecars[${i}].keys lists "${key}" twice.`
6293
+ );
6294
+ }
6295
+ seen.add(key);
6296
+ const owner = owners.get(key);
6297
+ if (owner !== void 0) {
6298
+ throw new DocmetaError(
6299
+ `${source}: sidecars[${i}].keys claims "${key}", which sidecars[${owner}] already owns \u2014 a key has exactly one sidecar.`
6300
+ );
6301
+ }
6302
+ owners.set(key, i);
6303
+ }
6304
+ return {
6305
+ file: e.file,
6306
+ keys,
6307
+ ...typeof e.tokenEnv === "string" ? { tokenEnv: e.tokenEnv } : {},
6308
+ ...typeof e.join === "string" ? { join: e.join } : {}
6309
+ };
6310
+ });
6311
+ }
5671
6312
  function parseConfigDocument(raw, source) {
5672
6313
  if (raw == null) return {};
5673
6314
  if (typeof raw !== "object" || Array.isArray(raw)) {
@@ -5741,6 +6382,9 @@ function parseConfigDocument(raw, source) {
5741
6382
  if (obj.checks !== void 0) {
5742
6383
  config.checks = parseChecks(obj.checks, source);
5743
6384
  }
6385
+ if (obj.sidecars !== void 0) {
6386
+ config.sidecars = parseSidecars(obj.sidecars, source);
6387
+ }
5744
6388
  if (obj.elements !== void 0) {
5745
6389
  config.elements = asElementPaths(obj.elements, "elements", source);
5746
6390
  }
@@ -5909,8 +6553,8 @@ function parseFill(value, source) {
5909
6553
  function schemaTrustRoot(cwd, configDir) {
5910
6554
  const git = findGitRoot(cwd);
5911
6555
  if (git !== null) return { dir: git, source: "git" };
5912
- if (configDir !== void 0) return { dir: resolve4(configDir), source: "config" };
5913
- return { dir: resolve4(cwd), source: "cwd" };
6556
+ if (configDir !== void 0) return { dir: resolve7(configDir), source: "config" };
6557
+ return { dir: resolve7(cwd), source: "cwd" };
5914
6558
  }
5915
6559
  async function loadConfig(explicitPath, cwd = process.cwd()) {
5916
6560
  const file = explicitPath ? await readConfigFile(explicitPath, cwd, CONFIG_FILE) : await findConfigFile(cwd, CONFIG_FILE);
@@ -6091,7 +6735,7 @@ var Validator = class {
6091
6735
  * Callers with an extractor that supplies no column pass nothing and get the
6092
6736
  * previous behavior exactly.
6093
6737
  */
6094
- async validate(data, refs, lineFor, colFor) {
6738
+ async validate(data, refs, lineFor, colFor, locate) {
6095
6739
  const { [FILE_SCHEMA_KEY]: _omit, ...subject } = data;
6096
6740
  void _omit;
6097
6741
  const errors = [];
@@ -6100,7 +6744,7 @@ var Validator = class {
6100
6744
  const ok = fn(subject);
6101
6745
  if (ok) continue;
6102
6746
  for (const e of fn.errors ?? []) {
6103
- errors.push(toFieldError(ref, e, lineFor, colFor));
6747
+ errors.push(toFieldError(ref, e, lineFor, colFor, locate));
6104
6748
  }
6105
6749
  }
6106
6750
  return errors;
@@ -6120,7 +6764,7 @@ function subjectOf(e) {
6120
6764
  return void 0;
6121
6765
  }
6122
6766
  }
6123
- function toFieldError(schema, e, lineFor, colFor) {
6767
+ function toFieldError(schema, e, lineFor, colFor, locate) {
6124
6768
  const defined = e;
6125
6769
  const instancePath = e.instancePath;
6126
6770
  const subject = subjectOf(defined);
@@ -6133,6 +6777,21 @@ function toFieldError(schema, e, lineFor, colFor) {
6133
6777
  message = `must NOT have additional property '${defined.params.additionalProperty}'`;
6134
6778
  wantsColumn = false;
6135
6779
  }
6780
+ const located = locate?.(instancePath) ?? (defined.keyword === "additionalProperties" ? locate?.(
6781
+ `${instancePath}/${escapePointerSegment(defined.params.additionalProperty)}`
6782
+ ) : void 0);
6783
+ if (located) {
6784
+ return {
6785
+ schema,
6786
+ instancePath,
6787
+ message,
6788
+ keyword: e.keyword,
6789
+ ...subject != null ? { subject } : {},
6790
+ file: located.file,
6791
+ ...located.line != null ? { line: located.line } : {},
6792
+ ...wantsColumn && located.col != null ? { col: located.col } : {}
6793
+ };
6794
+ }
6136
6795
  const line = lineFor(instancePath);
6137
6796
  const col = wantsColumn ? colFor?.(instancePath) : void 0;
6138
6797
  return {
@@ -6148,13 +6807,13 @@ function toFieldError(schema, e, lineFor, colFor) {
6148
6807
 
6149
6808
  // src/meta/commands/validate.ts
6150
6809
  import { existsSync as existsSync2 } from "node:fs";
6151
- import { readFile as readFile5 } from "node:fs/promises";
6152
- import { resolve as resolve7, extname as extname2 } from "node:path";
6810
+ import { readFile as readFile6 } from "node:fs/promises";
6811
+ import { resolve as resolve9, extname as extname2 } from "node:path";
6153
6812
 
6154
6813
  // src/meta/core/baseline.ts
6155
6814
  import { createHash as createHash3 } from "node:crypto";
6156
- import { readFile as readFile4 } from "node:fs/promises";
6157
- import { relative as relative3, resolve as resolve5 } from "node:path";
6815
+ import { readFile as readFile5 } from "node:fs/promises";
6816
+ import { relative as relative6, resolve as resolve8 } from "node:path";
6158
6817
  var DEFAULT_BASELINE_PATH = ".manni-baseline.json";
6159
6818
  var LEGACY_BASELINE_PATH = ".docmeta-baseline.json";
6160
6819
  var BASELINE_VERSION = 1;
@@ -6162,13 +6821,13 @@ var FINGERPRINT_RE = /^[0-9a-f]{16}$/;
6162
6821
  function canonicalFilePath(file, ctx) {
6163
6822
  if (!ctx) return file;
6164
6823
  const from = ctx.runBase ?? ctx.base;
6165
- return relative3(ctx.base, resolve5(from, file)).replace(/\\/g, "/");
6824
+ return relative6(ctx.base, resolve8(from, file)).replace(/\\/g, "/");
6166
6825
  }
6167
6826
  function canonicalSchemaRef(ref, ctx) {
6168
6827
  if (!ctx) return ref;
6169
6828
  if (classifyRef(ref).kind !== "file") return ref;
6170
- const abs = resolve5(ctx.cwd, ref);
6171
- return relative3(ctx.base, abs).replace(/\\/g, "/");
6829
+ const abs = resolve8(ctx.cwd, ref);
6830
+ return relative6(ctx.base, abs).replace(/\\/g, "/");
6172
6831
  }
6173
6832
  function fingerprint(e, ctx) {
6174
6833
  const parts = [
@@ -6235,265 +6894,91 @@ function parseBaseline(text, source) {
6235
6894
  );
6236
6895
  }
6237
6896
  entries[file] = value;
6238
- }
6239
- return { version: BASELINE_VERSION, generatedWith, entries };
6240
- }
6241
- async function readBaseline(absPath, source) {
6242
- let text;
6243
- try {
6244
- text = await readFile4(absPath, "utf8");
6245
- } catch (err) {
6246
- if (err.code === "ENOENT") return null;
6247
- throw new DocmetaError(
6248
- `Baseline "${source}" could not be read: ${err.message}`
6249
- );
6250
- }
6251
- return parseBaseline(text, source);
6252
- }
6253
- async function writeBaselineFile(absPath, baseline, source) {
6254
- try {
6255
- await writeFileAtomic(absPath, serializeBaseline(baseline));
6256
- } catch (err) {
6257
- throw new DocmetaError(
6258
- `Baseline "${source}" could not be written: ${err.message}`
6259
- );
6260
- }
6261
- }
6262
- function buildBaseline(results, generatedWith, ctx) {
6263
- const entries = /* @__PURE__ */ Object.create(null);
6264
- for (const r of results) {
6265
- if (r.errors.length === 0) continue;
6266
- const prints = [...new Set(r.errors.map((e) => fingerprint(e, ctx)))].sort();
6267
- entries[canonicalFilePath(r.file, ctx)] = prints;
6268
- }
6269
- return { version: BASELINE_VERSION, generatedWith, entries };
6270
- }
6271
- function applyBaseline(results, baseline, ctx) {
6272
- let recorded = 0;
6273
- let suppressed = 0;
6274
- let stale = 0;
6275
- const applied = results.map((r) => {
6276
- const known = baseline.entries[canonicalFilePath(r.file, ctx)];
6277
- if (!known) return r;
6278
- const recordedHere = new Set(known);
6279
- recorded += recordedHere.size;
6280
- const seen = /* @__PURE__ */ new Set();
6281
- const fresh = [];
6282
- for (const e of r.errors) {
6283
- const print = fingerprint(e, ctx);
6284
- if (recordedHere.has(print)) {
6285
- seen.add(print);
6286
- suppressed += 1;
6287
- } else {
6288
- fresh.push(e);
6289
- }
6290
- }
6291
- stale += recordedHere.size - seen.size;
6292
- const baselined = r.errors.length - fresh.length;
6293
- return {
6294
- ...r,
6295
- ok: fresh.length === 0,
6296
- errors: fresh,
6297
- ...baselined > 0 ? { baselined } : {}
6298
- };
6299
- });
6300
- return { results: applied, recorded, suppressed, stale };
6301
- }
6302
- function diffBaselines(previous, next) {
6303
- const flatten = (b) => {
6304
- const out = /* @__PURE__ */ new Set();
6305
- if (!b) return out;
6306
- for (const [file, prints] of Object.entries(b.entries)) {
6307
- for (const p of prints) out.add(`${file}\0${p}`);
6308
- }
6309
- return out;
6310
- };
6311
- const before = flatten(previous);
6312
- const after = flatten(next);
6313
- let added = 0;
6314
- let removed = 0;
6315
- for (const k of after) if (!before.has(k)) added += 1;
6316
- for (const k of before) if (!after.has(k)) removed += 1;
6317
- return { added, removed };
6318
- }
6319
- function countFingerprints(baseline) {
6320
- let n = 0;
6321
- for (const prints of Object.values(baseline.entries)) n += prints.length;
6322
- return n;
6323
- }
6324
-
6325
- // src/meta/core/load-files.ts
6326
- import { stat as stat3 } from "node:fs/promises";
6327
- import { extname, relative as relative4, resolve as resolve6 } from "node:path";
6328
- import fg from "fast-glob";
6329
- import picomatch2 from "picomatch";
6330
-
6331
- // src/meta/core/gitignore.ts
6332
- import { spawn } from "node:child_process";
6333
- var GITIGNORE_UNAVAILABLE = ".gitignore filtering was requested but git could not answer here (no repository, or no git on PATH); no files were skipped.";
6334
- var unavailable = () => ({
6335
- ignored: /* @__PURE__ */ new Set(),
6336
- available: false
6337
- });
6338
- function gitIgnored(paths, cwd) {
6339
- if (paths.length === 0) {
6340
- return Promise.resolve({ ignored: /* @__PURE__ */ new Set(), available: true });
6341
- }
6342
- return new Promise((settle) => {
6343
- let done = false;
6344
- const finish = (answer) => {
6345
- if (done) return;
6346
- done = true;
6347
- settle(answer);
6348
- };
6349
- let child;
6350
- try {
6351
- child = spawn("git", ["check-ignore", "--stdin", "-z"], {
6352
- cwd,
6353
- windowsHide: true
6354
- });
6355
- } catch {
6356
- finish(unavailable());
6357
- return;
6358
- }
6359
- let out = "";
6360
- child.stdout.setEncoding("utf8");
6361
- child.stdout.on("data", (chunk) => {
6362
- out += chunk;
6363
- });
6364
- child.stderr.resume();
6365
- child.on("error", () => {
6366
- finish(unavailable());
6367
- });
6368
- child.stdin.on("error", () => {
6369
- });
6370
- child.on("close", (code) => {
6371
- if (code !== 0 && code !== 1) {
6372
- finish(unavailable());
6373
- return;
6374
- }
6375
- finish({
6376
- ignored: new Set(out.split("\0").filter((p) => p !== "")),
6377
- available: true
6378
- });
6379
- });
6380
- child.stdin.end(paths.join("\0"));
6381
- });
6382
- }
6383
-
6384
- // src/meta/core/load-files.ts
6385
- var DEFAULT_IGNORE = ["**/node_modules/**", "**/.git/**"];
6386
- var STDIN_TOKEN = "-";
6387
- var STDIN_LABEL = "<stdin>";
6388
- function toPosix(p) {
6389
- return p.replace(/\\/g, "/");
6897
+ }
6898
+ return { version: BASELINE_VERSION, generatedWith, entries };
6390
6899
  }
6391
- async function statOrNull(p) {
6900
+ async function readBaseline(absPath, source) {
6901
+ let text;
6392
6902
  try {
6393
- return await stat3(p);
6394
- } catch {
6395
- return null;
6903
+ text = await readFile5(absPath, "utf8");
6904
+ } catch (err) {
6905
+ if (err.code === "ENOENT") return null;
6906
+ throw new DocmetaError(
6907
+ `Baseline "${source}" could not be read: ${err.message}`
6908
+ );
6396
6909
  }
6910
+ return parseBaseline(text, source);
6397
6911
  }
6398
- function gitignoreOptions(opts) {
6399
- const asked = opts.flag === true || opts.flag === void 0 && opts.configured === true;
6400
- const notice = opts.onNotice;
6401
- return {
6402
- respectGitignore: opts.flag ?? opts.configured ?? true,
6403
- ...asked && notice ? {
6404
- onGitignoreUnavailable: () => {
6405
- notice(GITIGNORE_UNAVAILABLE);
6406
- }
6407
- } : {}
6408
- };
6409
- }
6410
- async function resolveTargetSet(opts) {
6411
- const cwd = opts.cwd ?? process.cwd();
6412
- const exts = (opts.exts ?? supportedExtensions()).map(
6413
- (e) => e.toLowerCase().startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`
6414
- );
6415
- const ignore = [...DEFAULT_IGNORE, ...opts.exclude ?? []];
6416
- const named = /* @__PURE__ */ new Set();
6417
- const walked = /* @__PURE__ */ new Set();
6418
- const keepByExt = (file) => exts.includes(extname(file).toLowerCase());
6419
- const missing = [];
6420
- for (const input of opts.inputs) {
6421
- if (input === STDIN_TOKEN) continue;
6422
- const posixInput = toPosix(input);
6423
- const abs = resolve6(cwd, posixInput);
6424
- const st = await statOrNull(abs);
6425
- if (st?.isFile()) {
6426
- named.add(toPosix(relative4(cwd, abs)));
6427
- continue;
6428
- }
6429
- if (st?.isDirectory()) {
6430
- const found2 = await fg(`${posixInput}/**/*`, {
6431
- cwd,
6432
- ignore,
6433
- onlyFiles: true,
6434
- dot: false
6435
- });
6436
- for (const f of found2) if (keepByExt(f)) walked.add(f);
6437
- continue;
6438
- }
6439
- if (!picomatch2.scan(posixInput).isGlob) {
6440
- missing.push(posixInput);
6441
- continue;
6442
- }
6443
- const found = await fg(posixInput, {
6444
- cwd,
6445
- ignore,
6446
- onlyFiles: true,
6447
- dot: false
6448
- });
6449
- for (const f of found) if (keepByExt(f)) walked.add(f);
6450
- }
6451
- if (missing.length > 0 && !opts.allowEmpty) {
6452
- const names = missing.map((m) => `"${m}"`).join(", ");
6912
+ async function writeBaselineFile(absPath, baseline, source) {
6913
+ try {
6914
+ await writeFileAtomic(absPath, serializeBaseline(baseline));
6915
+ } catch (err) {
6453
6916
  throw new DocmetaError(
6454
- missing.length === 1 ? `File not found: ${names}.` : `Files not found: ${names}.`
6917
+ `Baseline "${source}" could not be written: ${err.message}`
6455
6918
  );
6456
6919
  }
6457
- let gitignoreSkipped = 0;
6458
- if (opts.respectGitignore !== false && walked.size > 0) {
6459
- const candidates = [...walked].filter((f) => !named.has(f));
6460
- const { ignored, available } = await gitIgnored(candidates, cwd);
6461
- if (available) {
6462
- for (const f of ignored) {
6463
- if (walked.delete(f)) gitignoreSkipped += 1;
6920
+ }
6921
+ function buildBaseline(results, generatedWith, ctx) {
6922
+ const entries = /* @__PURE__ */ Object.create(null);
6923
+ for (const r of results) {
6924
+ if (r.errors.length === 0) continue;
6925
+ const prints = [...new Set(r.errors.map((e) => fingerprint(e, ctx)))].sort();
6926
+ entries[canonicalFilePath(r.file, ctx)] = prints;
6927
+ }
6928
+ return { version: BASELINE_VERSION, generatedWith, entries };
6929
+ }
6930
+ function applyBaseline(results, baseline, ctx) {
6931
+ let recorded = 0;
6932
+ let suppressed = 0;
6933
+ let stale = 0;
6934
+ const applied = results.map((r) => {
6935
+ const known = baseline.entries[canonicalFilePath(r.file, ctx)];
6936
+ if (!known) return r;
6937
+ const recordedHere = new Set(known);
6938
+ recorded += recordedHere.size;
6939
+ const seen = /* @__PURE__ */ new Set();
6940
+ const fresh = [];
6941
+ for (const e of r.errors) {
6942
+ const print = fingerprint(e, ctx);
6943
+ if (recordedHere.has(print)) {
6944
+ seen.add(print);
6945
+ suppressed += 1;
6946
+ } else {
6947
+ fresh.push(e);
6464
6948
  }
6465
- } else {
6466
- opts.onGitignoreUnavailable?.();
6467
6949
  }
6468
- }
6469
- return {
6470
- files: [.../* @__PURE__ */ new Set([...named, ...walked])].sort(),
6471
- gitignoreSkipped
6950
+ stale += recordedHere.size - seen.size;
6951
+ const baselined = r.errors.length - fresh.length;
6952
+ return {
6953
+ ...r,
6954
+ ok: fresh.length === 0,
6955
+ errors: fresh,
6956
+ ...baselined > 0 ? { baselined } : {}
6957
+ };
6958
+ });
6959
+ return { results: applied, recorded, suppressed, stale };
6960
+ }
6961
+ function diffBaselines(previous, next) {
6962
+ const flatten = (b) => {
6963
+ const out = /* @__PURE__ */ new Set();
6964
+ if (!b) return out;
6965
+ for (const [file, prints] of Object.entries(b.entries)) {
6966
+ for (const p of prints) out.add(`${file}\0${p}`);
6967
+ }
6968
+ return out;
6472
6969
  };
6970
+ const before = flatten(previous);
6971
+ const after = flatten(next);
6972
+ let added = 0;
6973
+ let removed = 0;
6974
+ for (const k of after) if (!before.has(k)) added += 1;
6975
+ for (const k of before) if (!after.has(k)) removed += 1;
6976
+ return { added, removed };
6473
6977
  }
6474
- function assertNonEmpty(p) {
6475
- if (p.allowEmpty || p.usingStdin || p.files.length > 0) return;
6476
- const tried = p.inputs.map((i) => `"${i}"`).join(", ");
6477
- const notes = [];
6478
- if (p.exts && p.exts.length > 0) {
6479
- notes.push(`extensions: ${p.exts.join(", ")}`);
6480
- }
6481
- if (p.exclude && p.exclude.length > 0) {
6482
- notes.push(`excludes: ${p.exclude.map((e) => `"${e}"`).join(", ")}`);
6483
- }
6484
- if (p.gitignoreSkipped && p.gitignoreSkipped > 0) {
6485
- notes.push(
6486
- `.gitignore skipped ${p.gitignoreSkipped} (pass --no-gitignore to check them)`
6487
- );
6488
- }
6489
- throw new DocmetaError(
6490
- [
6491
- `No files matched. Patterns tried: ${tried}.`,
6492
- ...notes.length > 0 ? [`Filters applied \u2014 ${notes.join("; ")}.`] : [],
6493
- `Nothing was ${p.action}, so this is an error rather than a pass.`,
6494
- `Pass --allow-empty (or set allowEmpty: true) if matching nothing is expected.`
6495
- ].join("\n")
6496
- );
6978
+ function countFingerprints(baseline) {
6979
+ let n = 0;
6980
+ for (const prints of Object.values(baseline.entries)) n += prints.length;
6981
+ return n;
6497
6982
  }
6498
6983
 
6499
6984
  // src/meta/core/projection.ts
@@ -6900,16 +7385,16 @@ function parseErrorResult(file, format, message, keyword) {
6900
7385
  }
6901
7386
  function resolveBaselineRequest(opts, configured, configDir, cwd) {
6902
7387
  const named = (label) => ({
6903
- absPath: resolve7(cwd, label),
7388
+ absPath: resolve9(cwd, label),
6904
7389
  label
6905
7390
  });
6906
7391
  const implied = () => {
6907
7392
  const base = configDir ?? cwd;
6908
7393
  if (configured !== void 0) {
6909
- return { absPath: resolve7(base, configured), label: configured };
7394
+ return { absPath: resolve9(base, configured), label: configured };
6910
7395
  }
6911
- const current = resolve7(base, DEFAULT_BASELINE_PATH);
6912
- const legacy = resolve7(base, LEGACY_BASELINE_PATH);
7396
+ const current = resolve9(base, DEFAULT_BASELINE_PATH);
7397
+ const legacy = resolve9(base, LEGACY_BASELINE_PATH);
6913
7398
  if (!existsSync2(current) && existsSync2(legacy)) {
6914
7399
  warn(
6915
7400
  `"${LEGACY_BASELINE_PATH}" is the pre-rename baseline file name. Rename it to "${DEFAULT_BASELINE_PATH}".`
@@ -6977,6 +7462,11 @@ async function runValidate(opts) {
6977
7462
  action: "validated"
6978
7463
  });
6979
7464
  const trustRoot = schemaTrustRoot(cwd, configDir);
7465
+ const sidecars = await loadSidecars(config, {
7466
+ configDir: configDir ?? cwd,
7467
+ base,
7468
+ offline: opts.offline ?? config?.offline ?? false
7469
+ });
6980
7470
  const validator = new Validator(
6981
7471
  schemaLoadOptions({
6982
7472
  // The config's directory when a config governs the run, so one project
@@ -6992,9 +7482,14 @@ async function runValidate(opts) {
6992
7482
  })
6993
7483
  );
6994
7484
  const results = [];
7485
+ const joinHits = /* @__PURE__ */ new Map();
6995
7486
  const scoped = opts.inputs.length > 0 || // redundant with inputs.length (stdin is an input) — kept as belt-and-suspenders
6996
7487
  usingStdin || opts.as !== void 0 || opts.exts !== void 0 || opts.exclude !== void 0 && opts.exclude.length > 0 || opts.respectGitignore !== void 0 || (opts.cliSchemas?.length ?? 0) > 0;
6997
7488
  const configuredChecks = config?.checks ?? [];
7489
+ if (!scoped) {
7490
+ const orphans = orphanEntries(sidecars, files, base);
7491
+ if (orphans.length > 0) throw orphanError(orphans);
7492
+ }
6998
7493
  const checksWillRun = configuredChecks.length > 0 && opts.checks !== false && !scoped;
6999
7494
  const checkEntries = [];
7000
7495
  const checkResolved = /* @__PURE__ */ new Map();
@@ -7017,6 +7512,16 @@ async function runValidate(opts) {
7017
7512
  );
7018
7513
  return;
7019
7514
  }
7515
+ const merged = mergeSidecars(label, extracted, sidecars, base);
7516
+ extracted = merged.extracted;
7517
+ for (const j of merged.joins) {
7518
+ const byValue = joinHits.get(j.field) ?? /* @__PURE__ */ new Map();
7519
+ const line = extracted.lineFor(j.field);
7520
+ const hits = byValue.get(j.value) ?? [];
7521
+ hits.push({ label, file: j.file, ...line != null ? { line } : {} });
7522
+ byValue.set(j.value, hits);
7523
+ joinHits.set(j.field, byValue);
7524
+ }
7020
7525
  if (checksWillRun) checkEntries.push({ label, extracted });
7021
7526
  let resolved;
7022
7527
  try {
@@ -7046,7 +7551,8 @@ async function runValidate(opts) {
7046
7551
  extracted.data,
7047
7552
  schemaSet,
7048
7553
  extracted.lineFor,
7049
- extracted.colFor
7554
+ extracted.colFor,
7555
+ merged.locate
7050
7556
  );
7051
7557
  } catch (err) {
7052
7558
  if (!(err instanceof DocmetaError) || resolved.source !== "document") {
@@ -7057,6 +7563,17 @@ async function runValidate(opts) {
7057
7563
  );
7058
7564
  return;
7059
7565
  }
7566
+ for (const { key, file } of merged.collisions) {
7567
+ const line = extracted.lineFor(key);
7568
+ errors.push({
7569
+ schema: SIDECAR_OWNED_SCHEMA,
7570
+ keyword: SIDECAR_KEYWORD,
7571
+ subject: key,
7572
+ instancePath: sidecarPointer(key),
7573
+ message: `"${key}" is owned by sidecar ${file}; remove it from the document`,
7574
+ ...line != null ? { line } : {}
7575
+ });
7576
+ }
7060
7577
  results.push({
7061
7578
  file: label,
7062
7579
  format: extractor.name,
@@ -7075,9 +7592,37 @@ async function runValidate(opts) {
7075
7592
  await processOne(STDIN_LABEL, content, forcedExtractor.extensions[0] ?? "");
7076
7593
  }
7077
7594
  for (const file of files) {
7078
- const content = await readFile5(resolve7(base, file), "utf8");
7595
+ const content = await readFile6(resolve9(base, file), "utf8");
7079
7596
  await processOne(file, content, extname2(file));
7080
7597
  }
7598
+ if (sidecars) {
7599
+ const byLabel = new Map(results.map((r) => [r.file, r]));
7600
+ const matched = /* @__PURE__ */ new Map();
7601
+ for (const [field, byValue] of joinHits) {
7602
+ matched.set(field, new Set(byValue.keys()));
7603
+ for (const [value, hits] of byValue) {
7604
+ if (hits.length < 2) continue;
7605
+ for (const hit of hits) {
7606
+ const others = hits.filter((h) => h.label !== hit.label).map((h) => h.label).join(", ");
7607
+ const result = byLabel.get(hit.label);
7608
+ if (!result) continue;
7609
+ result.errors.push({
7610
+ schema: SIDECAR_DUPLICATE_SCHEMA,
7611
+ keyword: SIDECAR_KEYWORD,
7612
+ subject: value,
7613
+ instancePath: sidecarPointer(field),
7614
+ message: `${hits.length} documents carry ${field} "${value}"; ${hit.file} cannot tell them apart (${others})`,
7615
+ ...hit.line != null ? { line: hit.line } : {}
7616
+ });
7617
+ result.ok = false;
7618
+ }
7619
+ }
7620
+ }
7621
+ if (!scoped) {
7622
+ const orphans = orphanJoins(sidecars, matched);
7623
+ if (orphans.length > 0) throw orphanError(orphans);
7624
+ }
7625
+ }
7081
7626
  if (configuredChecks.length > 0 && opts.checks !== false) {
7082
7627
  if (scoped) {
7083
7628
  opts.onNotice?.("corpus checks skipped: run is scoped");
@@ -7167,14 +7712,14 @@ async function settleBaseline(results, request, ctx) {
7167
7712
  }
7168
7713
 
7169
7714
  // src/meta/commands/get.ts
7170
- import { readFile as readFile6 } from "node:fs/promises";
7171
- import { resolve as resolve8, extname as extname3 } from "node:path";
7715
+ import { readFile as readFile7 } from "node:fs/promises";
7716
+ import { resolve as resolve10, extname as extname3 } from "node:path";
7172
7717
  async function runGet(opts) {
7173
7718
  const cwd = opts.cwd ?? process.cwd();
7174
7719
  if (opts.fields.length === 0) {
7175
7720
  throw new DocmetaError("Specify at least one field to get.");
7176
7721
  }
7177
- const { config, inputs, base } = await resolveRunConfig({
7722
+ const { config, inputs, base, configDir } = await resolveRunConfig({
7178
7723
  cwd,
7179
7724
  configPath: opts.configPath,
7180
7725
  noConfig: opts.noConfig,
@@ -7220,6 +7765,11 @@ async function runGet(opts) {
7220
7765
  action: "read"
7221
7766
  });
7222
7767
  const out = [];
7768
+ const sidecars = await loadSidecars(config, {
7769
+ configDir: configDir ?? cwd,
7770
+ base,
7771
+ offline: opts.offline ?? config?.offline ?? false
7772
+ });
7223
7773
  const readOne = (label, content, extension) => {
7224
7774
  const extractor = forced ?? extractorForExtension(extension);
7225
7775
  if (!extractor) {
@@ -7229,9 +7779,14 @@ async function runGet(opts) {
7229
7779
  }
7230
7780
  let extracted;
7231
7781
  try {
7232
- extracted = extractor.extract(content, label, {
7233
- elements: resolveElements(label, config)
7234
- });
7782
+ extracted = mergeSidecars(
7783
+ label,
7784
+ extractor.extract(content, label, {
7785
+ elements: resolveElements(label, config)
7786
+ }),
7787
+ sidecars,
7788
+ base
7789
+ ).extracted;
7235
7790
  } catch (err) {
7236
7791
  if (err instanceof DocmetaError) throw err;
7237
7792
  const reason = err instanceof Error ? err.message : String(err);
@@ -7251,7 +7806,7 @@ async function runGet(opts) {
7251
7806
  readOne("<stdin>", opts.stdinContent ?? "", forced.extensions[0] ?? "");
7252
7807
  }
7253
7808
  for (const file of files) {
7254
- const content = await readFile6(resolve8(base, file), "utf8");
7809
+ const content = await readFile7(resolve10(base, file), "utf8");
7255
7810
  readOne(file, content, extname3(file));
7256
7811
  }
7257
7812
  return out;
@@ -7288,16 +7843,16 @@ function parseJsonPointer(pointer) {
7288
7843
 
7289
7844
  // src/meta/commands/schemas.ts
7290
7845
  import { existsSync as existsSync3 } from "node:fs";
7291
- import { mkdir as mkdir2, readFile as readFile7 } from "node:fs/promises";
7292
- import { dirname as dirname4, extname as extname4, isAbsolute as isAbsolute2, join as join5, relative as relative5, resolve as resolve9, sep } from "node:path";
7293
- import { parseDocument as parseDocument3, parse as parseYaml3, stringify } from "yaml";
7846
+ import { mkdir as mkdir2, readFile as readFile8 } from "node:fs/promises";
7847
+ import { dirname as dirname5, extname as extname4, isAbsolute as isAbsolute3, join as join5, relative as relative7, resolve as resolve11, sep as sep3 } from "node:path";
7848
+ import { parseDocument as parseDocument4, parse as parseYaml3, stringify } from "yaml";
7294
7849
  function getSchemasInfo() {
7295
7850
  return { builtins: listBuiltins(), formats: listFormats() };
7296
7851
  }
7297
7852
  var DEFAULT_VENDOR_DIR = "./schema";
7298
7853
  var DEFAULT_CONFIG_NAME = "manni.config.yaml";
7299
7854
  function posixRelative(from, to) {
7300
- return relative5(from, to).split(sep).join("/");
7855
+ return relative7(from, to).split(sep3).join("/");
7301
7856
  }
7302
7857
  function vendorFileName(url) {
7303
7858
  let parsed2;
@@ -7362,7 +7917,7 @@ function renderConfig(existing, entries, section) {
7362
7917
  if (existing === null) {
7363
7918
  return stringify({ [META_SECTION]: { schemas: entries } });
7364
7919
  }
7365
- const doc = parseDocument3(existing);
7920
+ const doc = parseDocument4(existing);
7366
7921
  doc.setIn(section === void 0 ? ["schemas"] : [section, "schemas"], entries);
7367
7922
  return doc.toString();
7368
7923
  }
@@ -7383,13 +7938,13 @@ async function runVendorSchema(opts) {
7383
7938
  );
7384
7939
  }
7385
7940
  const loaded = await loadConfig(opts.configPath, cwd);
7386
- const configPath = loaded?.path ?? resolve9(cwd, DEFAULT_CONFIG_NAME);
7941
+ const configPath = loaded?.path ?? resolve11(cwd, DEFAULT_CONFIG_NAME);
7387
7942
  const configDir = loaded?.dir ?? cwd;
7388
- const absDir = resolve9(cwd, opts.dir ?? DEFAULT_VENDOR_DIR);
7943
+ const absDir = resolve11(cwd, opts.dir ?? DEFAULT_VENDOR_DIR);
7389
7944
  const absFile = join5(absDir, vendorFileName(url));
7390
7945
  await assertNotIgnored(absFile, absDir, cwd, opts.onNotice);
7391
7946
  const fromConfig = posixRelative(configDir, absFile);
7392
- const ref = isAbsolute2(fromConfig) || fromConfig === "" ? absFile : fromConfig.startsWith(".") ? fromConfig : `./${fromConfig}`;
7947
+ const ref = isAbsolute3(fromConfig) || fromConfig === "" ? absFile : fromConfig.startsWith(".") ? fromConfig : `./${fromConfig}`;
7393
7948
  const { bytes } = await fetchSchemaBytes(url, {
7394
7949
  ...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
7395
7950
  ...opts.maxBytes !== void 0 ? { maxBytes: opts.maxBytes } : {}
@@ -7404,7 +7959,7 @@ async function runVendorSchema(opts) {
7404
7959
  }
7405
7960
  let unchanged = false;
7406
7961
  if (existsSync3(absFile)) {
7407
- const current = await readFile7(absFile);
7962
+ const current = await readFile8(absFile);
7408
7963
  unchanged = bytes.equals(current);
7409
7964
  if (!unchanged && !folded.replaced) {
7410
7965
  throw new DocmetaError(
@@ -7414,7 +7969,7 @@ async function runVendorSchema(opts) {
7414
7969
  }
7415
7970
  await mkdir2(absDir, { recursive: true });
7416
7971
  await writeFileAtomic(absFile, bytes);
7417
- const existing = loaded ? await readFile7(configPath, "utf8") : null;
7972
+ const existing = loaded ? await readFile8(configPath, "utf8") : null;
7418
7973
  const section = loaded ? loaded.section : META_SECTION;
7419
7974
  const text = renderConfig(existing, folded.entries, section);
7420
7975
  reparseConfig(text, posixRelative(cwd, configPath) || DEFAULT_CONFIG_NAME, section);
@@ -7533,7 +8088,7 @@ async function runInferSchema(opts) {
7533
8088
  `--min-coverage must be a percentage between 0 and 100, got ${minCoverage}.`
7534
8089
  );
7535
8090
  }
7536
- const { config, inputs, base } = await resolveRunConfig({
8091
+ const { config, inputs, base, configDir } = await resolveRunConfig({
7537
8092
  cwd,
7538
8093
  configPath: opts.configPath,
7539
8094
  noConfig: opts.noConfig,
@@ -7541,6 +8096,11 @@ async function runInferSchema(opts) {
7541
8096
  onConfigLoaded: opts.onConfigLoaded
7542
8097
  });
7543
8098
  const usingStdin = inputs.includes(STDIN_TOKEN);
8099
+ const sidecars = await loadSidecars(config, {
8100
+ configDir: configDir ?? cwd,
8101
+ base,
8102
+ offline: opts.offline ?? config?.offline ?? false
8103
+ });
7544
8104
  if (inputs.length === 0) {
7545
8105
  throw new DocmetaError(
7546
8106
  "No files to scan. Pass paths/globs, or add `paths:` under `meta:` in manni.config.yaml."
@@ -7559,10 +8119,10 @@ async function runInferSchema(opts) {
7559
8119
  }
7560
8120
  let absOut;
7561
8121
  if (opts.out !== void 0) {
7562
- absOut = resolve9(cwd, opts.out);
8122
+ absOut = resolve11(cwd, opts.out);
7563
8123
  await assertNotIgnored(
7564
8124
  absOut,
7565
- dirname4(absOut),
8125
+ dirname5(absOut),
7566
8126
  cwd,
7567
8127
  opts.onNotice,
7568
8128
  DRAFT_IGNORE_TEXT
@@ -7613,9 +8173,14 @@ async function runInferSchema(opts) {
7613
8173
  filesScanned += 1;
7614
8174
  let extracted;
7615
8175
  try {
7616
- extracted = extractor.extract(content, label, {
7617
- elements: resolveElements(label, config)
7618
- });
8176
+ extracted = mergeSidecars(
8177
+ label,
8178
+ extractor.extract(content, label, {
8179
+ elements: resolveElements(label, config)
8180
+ }),
8181
+ sidecars,
8182
+ base
8183
+ ).extracted;
7619
8184
  } catch (err) {
7620
8185
  unreadable.push({
7621
8186
  file: label,
@@ -7637,7 +8202,7 @@ async function runInferSchema(opts) {
7637
8202
  scanOne(STDIN_LABEL, opts.stdinContent ?? "", forced.extensions[0] ?? "");
7638
8203
  }
7639
8204
  for (const file of files) {
7640
- const content = await readFile7(resolve9(base, file), "utf8");
8205
+ const content = await readFile8(resolve11(base, file), "utf8");
7641
8206
  scanOne(file, content, extname4(file));
7642
8207
  }
7643
8208
  for (const u of unreadable) {
@@ -7679,7 +8244,7 @@ async function runInferSchema(opts) {
7679
8244
  };
7680
8245
  const keys = kept.map(({ sawEmptyString: _drop, ...rest }) => rest);
7681
8246
  if (absOut !== void 0) {
7682
- await mkdir2(dirname4(absOut), { recursive: true });
8247
+ await mkdir2(dirname5(absOut), { recursive: true });
7683
8248
  await writeFileAtomic(absOut, `${JSON.stringify(draft, null, 2)}
7684
8249
  `);
7685
8250
  }
@@ -7698,9 +8263,9 @@ async function runInferSchema(opts) {
7698
8263
  // src/meta/commands/query.ts
7699
8264
  import { randomBytes } from "node:crypto";
7700
8265
  import { existsSync as existsSync4 } from "node:fs";
7701
- import { mkdir as mkdir3, open, readFile as readFile8, rename as rename2, rm as rm2 } from "node:fs/promises";
7702
- import { dirname as dirname5, isAbsolute as isAbsolute3, resolve as resolve10, extname as extname5, sep as sep2 } from "node:path";
7703
- import { parseDocument as parseDocument4, isMap as isMap3, isSeq as isSeq2, isScalar as isScalar2 } from "yaml";
8266
+ import { mkdir as mkdir3, open, readFile as readFile9, rename as rename2, rm as rm2 } from "node:fs/promises";
8267
+ import { dirname as dirname6, isAbsolute as isAbsolute4, resolve as resolve12, extname as extname5, sep as sep4 } from "node:path";
8268
+ import { parseDocument as parseDocument5, isMap as isMap4, isSeq as isSeq2, isScalar as isScalar3 } from "yaml";
7704
8269
  async function runQuery(opts) {
7705
8270
  const cwd = opts.cwd ?? process.cwd();
7706
8271
  const sql = opts.sql.trim();
@@ -7780,6 +8345,11 @@ async function runQuery(opts) {
7780
8345
  action: "queried"
7781
8346
  });
7782
8347
  const entries = [];
8348
+ const sidecars = await loadSidecars(config, {
8349
+ configDir: configDir ?? cwd,
8350
+ base,
8351
+ offline: opts.offline ?? config?.offline ?? false
8352
+ });
7783
8353
  const readOne = (label, content, extension) => {
7784
8354
  const extractor = forced ?? extractorForExtension(extension);
7785
8355
  if (!extractor) {
@@ -7787,9 +8357,14 @@ async function runQuery(opts) {
7787
8357
  `Unsupported file type "${extension}" for "${label}". Supported: ${supportedExtensions().join(", ")}. Use --as to override.`
7788
8358
  );
7789
8359
  }
7790
- const extracted = extractor.extract(content, label, {
7791
- elements: resolveElements(label, config)
7792
- });
8360
+ const extracted = mergeSidecars(
8361
+ label,
8362
+ extractor.extract(content, label, {
8363
+ elements: resolveElements(label, config)
8364
+ }),
8365
+ sidecars,
8366
+ base
8367
+ ).extracted;
7793
8368
  entries.push({ label, extracted, extractor });
7794
8369
  };
7795
8370
  if (usingStdin) {
@@ -7807,10 +8382,10 @@ async function runQuery(opts) {
7807
8382
  readOne(STDIN_LABEL, opts.stdinContent ?? "", ext);
7808
8383
  }
7809
8384
  for (const file of files) {
7810
- const content = await readFile8(resolve10(base, file), "utf8");
8385
+ const content = await readFile9(resolve12(base, file), "utf8");
7811
8386
  readOne(file, content, extname5(file));
7812
8387
  }
7813
- const db = opts.db === void 0 ? void 0 : { resolved: resolve10(cwd, opts.db), display: opts.db };
8388
+ const db = opts.db === void 0 ? void 0 : { resolved: resolve12(cwd, opts.db), display: opts.db };
7814
8389
  const run = await runSql(sql, entries, {
7815
8390
  target: db,
7816
8391
  params: boundParams,
@@ -7822,6 +8397,7 @@ async function runQuery(opts) {
7822
8397
  configDir,
7823
8398
  configPath,
7824
8399
  trustRoot: schemaTrustRoot(cwd, configDir),
8400
+ sidecars,
7825
8401
  onNotice: opts.onNotice
7826
8402
  });
7827
8403
  return { ...run, frame: { cwd, base: configDir ?? cwd, runBase: base } };
@@ -7885,7 +8461,7 @@ async function runSql(sql, entries, ctx) {
7885
8461
  );
7886
8462
  const { DatabaseSync } = await loadSqlite();
7887
8463
  if (target) {
7888
- await mkdir3(dirname5(target.resolved), { recursive: true });
8464
+ await mkdir3(dirname6(target.resolved), { recursive: true });
7889
8465
  await prepareDbTarget(target.resolved, target.display);
7890
8466
  }
7891
8467
  let db;
@@ -8337,20 +8913,20 @@ function displayPath(ctx, abs) {
8337
8913
  }
8338
8914
  function assertSchemaWriteWithin(ctx, abs) {
8339
8915
  const within = posixRelative(ctx.trustRoot.dir, abs);
8340
- if (within !== "" && !within.startsWith("..") && !isAbsolute3(within)) return;
8916
+ if (within !== "" && !within.startsWith("..") && !isAbsolute4(within)) return;
8341
8917
  throw new DocmetaError(
8342
8918
  `The resolved schema "${abs}" lives outside ${ctx.trustRoot.dir}; DDL edits schemas inside the repository. Evolve it where it lives, or vendor a copy in.`
8343
8919
  );
8344
8920
  }
8345
8921
  function foldPath(p) {
8346
- const abs = resolve10(p);
8922
+ const abs = resolve12(p);
8347
8923
  return process.platform === "win32" ? abs.toLowerCase() : abs;
8348
8924
  }
8349
8925
  function filePinsByPath(ctx) {
8350
8926
  const byPath = /* @__PURE__ */ new Map();
8351
8927
  for (const [ref, pin] of collectSchemaPins(ctx.config)) {
8352
8928
  if (classifyRef(ref).kind !== "file") continue;
8353
- byPath.set(foldPath(resolve10(ctx.cwd, ref)), pin);
8929
+ byPath.set(foldPath(resolve12(ctx.cwd, ref)), pin);
8354
8930
  }
8355
8931
  return byPath;
8356
8932
  }
@@ -8378,13 +8954,13 @@ async function loadSetMembers(refs, ctx) {
8378
8954
  });
8379
8955
  continue;
8380
8956
  }
8381
- const abs = resolve10(ctx.cwd, ref);
8957
+ const abs = resolve12(ctx.cwd, ref);
8382
8958
  const canon = foldPath(abs);
8383
8959
  if (seenFiles.has(canon)) continue;
8384
8960
  seenFiles.add(canon);
8385
8961
  let text;
8386
8962
  try {
8387
- text = await readFile8(abs, "utf8");
8963
+ text = await readFile9(abs, "utf8");
8388
8964
  } catch {
8389
8965
  throw new DocmetaError(
8390
8966
  `Schema file not found: "${ref}" (looked at ${abs}).`
@@ -8578,7 +9154,7 @@ async function planSchemaMutation(ops, entries, ctx) {
8578
9154
  const [, name2, ver] = target.builtinId.split(":");
8579
9155
  const safe = (s) => s.replace(/[^A-Za-z0-9._-]/g, "-");
8580
9156
  const forkName = `${safe(name2 ?? "schema")}-${safe(ver ?? "0")}.local.json`;
8581
- schemaAbs = resolve10(ctx.configDir ?? ctx.cwd, "schemas", forkName);
9157
+ schemaAbs = resolve12(ctx.configDir ?? ctx.cwd, "schemas", forkName);
8582
9158
  assertSchemaWriteWithin(ctx, schemaAbs);
8583
9159
  if (existsSync4(schemaAbs)) {
8584
9160
  throw new DocmetaError(
@@ -8587,7 +9163,7 @@ async function planSchemaMutation(ops, entries, ctx) {
8587
9163
  }
8588
9164
  await assertNotIgnored(
8589
9165
  schemaAbs,
8590
- dirname5(schemaAbs),
9166
+ dirname6(schemaAbs),
8591
9167
  ctx.cwd,
8592
9168
  ctx.onNotice,
8593
9169
  FORK_IGNORE_TEXT
@@ -8734,18 +9310,18 @@ async function planConfigEdit(ctx, edit, runRefs) {
8734
9310
  if (configPath === void 0 || configDir === void 0) {
8735
9311
  return { changes: [], repointed: false };
8736
9312
  }
8737
- const text = await readFile8(configPath, "utf8");
8738
- const doc = parseDocument4(stripBom(text));
9313
+ const text = await readFile9(configPath, "utf8");
9314
+ const doc = parseDocument5(stripBom(text));
8739
9315
  const display = displayPath(ctx, configPath);
8740
9316
  const rawRefOf = (item) => {
8741
- if (isScalar2(item) && typeof item.value === "string") return item.value;
8742
- if (isMap3(item)) {
9317
+ if (isScalar3(item) && typeof item.value === "string") return item.value;
9318
+ if (isMap4(item)) {
8743
9319
  const ref = item.get("ref");
8744
9320
  if (typeof ref === "string") return ref;
8745
9321
  }
8746
9322
  return void 0;
8747
9323
  };
8748
- const rebaseRaw = (raw) => classifyRef(raw).kind === "file" && !isAbsolute3(raw) ? resolve10(configDir, raw) : raw;
9324
+ const rebaseRaw = (raw) => classifyRef(raw).kind === "file" && !isAbsolute4(raw) ? resolve12(configDir, raw) : raw;
8749
9325
  const wantSet = runRefs.map(rebaseRaw).sort().join("\n");
8750
9326
  const seqResolvesToRunSet = (node) => {
8751
9327
  if (!isSeq2(node)) return false;
@@ -8761,10 +9337,10 @@ async function planConfigEdit(ctx, edit, runRefs) {
8761
9337
  for (const item of node.items) {
8762
9338
  const raw = rawRefOf(item);
8763
9339
  if (raw === void 0 || !wants(raw)) continue;
8764
- if (isScalar2(item)) {
9340
+ if (isScalar3(item)) {
8765
9341
  item.value = spec.newRef;
8766
9342
  n += 1;
8767
- } else if (isMap3(item)) {
9343
+ } else if (isMap4(item)) {
8768
9344
  item.set("ref", spec.newRef);
8769
9345
  n += 1;
8770
9346
  }
@@ -8776,7 +9352,7 @@ async function planConfigEdit(ctx, edit, runRefs) {
8776
9352
  const overrides = doc.get("overrides", true);
8777
9353
  if (isSeq2(overrides)) {
8778
9354
  for (const entry of overrides.items) {
8779
- if (isMap3(entry)) repointCount += repoint(entry.get("schemas", true));
9355
+ if (isMap4(entry)) repointCount += repoint(entry.get("schemas", true));
8780
9356
  }
8781
9357
  }
8782
9358
  const repointed = repointCount > 0;
@@ -8785,10 +9361,10 @@ async function planConfigEdit(ctx, edit, runRefs) {
8785
9361
  if (edit.integrity && isSeq2(topSeq)) {
8786
9362
  const wantPath = foldPath(edit.integrity.path);
8787
9363
  for (const item of topSeq.items) {
8788
- if (!isMap3(item)) continue;
9364
+ if (!isMap4(item)) continue;
8789
9365
  const raw = rawRefOf(item);
8790
9366
  if (raw === void 0 || classifyRef(raw).kind !== "file") continue;
8791
- const abs = isAbsolute3(raw) ? raw : resolve10(configDir, raw);
9367
+ const abs = isAbsolute4(raw) ? raw : resolve12(configDir, raw);
8792
9368
  if (foldPath(abs) !== wantPath) continue;
8793
9369
  pinFrom = item.get("integrity");
8794
9370
  item.set("integrity", edit.integrity.value);
@@ -9009,7 +9585,7 @@ function buildChanges(diff, entries, sentinel, ctx, renameHints = [], booleanAdd
9009
9585
  `"${file}": no writable format for that extension.`
9010
9586
  );
9011
9587
  }
9012
- if (existsSync4(resolve10(ctx.base, file))) {
9588
+ if (existsSync4(resolve12(ctx.base, file))) {
9013
9589
  throw new DocmetaError(
9014
9590
  `"${file}" already exists; INSERT creates new files only.`
9015
9591
  );
@@ -9028,26 +9604,81 @@ function buildChanges(diff, entries, sentinel, ctx, renameHints = [], booleanAdd
9028
9604
  `"${from}" -> "${to}": a rename may not change the extension.`
9029
9605
  );
9030
9606
  }
9031
- if (existsSync4(resolve10(ctx.base, to))) {
9607
+ if (existsSync4(resolve12(ctx.base, to))) {
9032
9608
  throw new DocmetaError(
9033
9609
  `"${to}" already exists; the rename would overwrite it.`
9034
9610
  );
9035
9611
  }
9036
9612
  changes.push({ file: from, renamed: to, written: false });
9037
9613
  }
9614
+ refuseSidecarWrites(changes, entries, ctx);
9038
9615
  return changes;
9039
9616
  }
9040
9617
  function validateNewPath(p, base) {
9041
9618
  const refuse = (why) => {
9042
9619
  throw new DocmetaError(`"${p}" is not a usable path: ${why}.`);
9043
9620
  };
9044
- if (isAbsolute3(p)) refuse("it is absolute");
9621
+ if (isAbsolute4(p)) refuse("it is absolute");
9045
9622
  if (p.split(/[\\/]/).some((segment) => segment === "..")) {
9046
9623
  refuse("it traverses upward");
9047
9624
  }
9048
- const abs = resolve10(base, p);
9049
- if (abs === resolve10(base)) refuse("it points at the corpus root, not a file");
9050
- if (!abs.startsWith(resolve10(base) + sep2)) refuse("it escapes the corpus");
9625
+ const abs = resolve12(base, p);
9626
+ if (abs === resolve12(base)) refuse("it points at the corpus root, not a file");
9627
+ if (!abs.startsWith(resolve12(base) + sep4)) refuse("it escapes the corpus");
9628
+ }
9629
+ function refuseSidecarWrites(changes, entries, ctx) {
9630
+ const index = ctx.sidecars;
9631
+ if (!index) return;
9632
+ const owned = (key) => index.owners.get(key);
9633
+ const refuse = (file, key, owner) => {
9634
+ throw new DocmetaError(
9635
+ `"${file}": "${key}" is owned by sidecar ${owner}; edit the sidecar file instead.`
9636
+ );
9637
+ };
9638
+ const data = new Map(entries.map((e) => [e.label, e.extracted.data]));
9639
+ for (const c of changes) {
9640
+ if ("schema" in c || "config" in c) continue;
9641
+ if ("cleared" in c) {
9642
+ for (const key of Object.keys(data.get(c.file) ?? {})) {
9643
+ const owner = owned(key);
9644
+ if (owner !== void 0) refuse(c.file, key, owner);
9645
+ }
9646
+ continue;
9647
+ }
9648
+ if ("created" in c) {
9649
+ for (const key of Object.keys(c.to)) {
9650
+ const owner = owned(key);
9651
+ if (owner !== void 0) refuse(c.file, key, owner);
9652
+ }
9653
+ continue;
9654
+ }
9655
+ if ("renamed" in c) {
9656
+ const entry = index.byPath.get(resolve12(ctx.base, c.file));
9657
+ if (entry !== void 0 && entry.size > 0) {
9658
+ const manifest = [...entry.values()][0]?.file ?? "manifest";
9659
+ throw new DocmetaError(
9660
+ `"${c.file}": sidecar ${manifest} names it; rename the manifest entry first.`
9661
+ );
9662
+ }
9663
+ continue;
9664
+ }
9665
+ const keys = "renamedFrom" in c ? [c.key, c.renamedFrom] : [c.key];
9666
+ for (const key of keys) {
9667
+ const owner = owned(key);
9668
+ if (owner !== void 0) refuse(c.file, key, owner);
9669
+ const byValue = index.byField.get(key);
9670
+ if (byValue === void 0) continue;
9671
+ const raw = data.get(c.file)?.[key];
9672
+ const value = typeof raw === "string" ? raw : typeof raw === "number" || typeof raw === "boolean" ? String(raw) : void 0;
9673
+ const entry = value === void 0 ? void 0 : byValue.get(value);
9674
+ if (entry !== void 0) {
9675
+ const manifest = entry.values().next().value?.file ?? "manifest";
9676
+ throw new DocmetaError(
9677
+ `"${c.file}": "${key}" is the field sidecar ${manifest} joins on, and this document has an entry; change the manifest first.`
9678
+ );
9679
+ }
9680
+ }
9681
+ }
9051
9682
  }
9052
9683
  async function applyChanges(changes, entries, ctx, schemaPlan) {
9053
9684
  const schemaWrites = schemaPlan?.writes ?? [];
@@ -9075,14 +9706,14 @@ async function applyChanges(changes, entries, ctx, schemaPlan) {
9075
9706
  const pendingWrites = [];
9076
9707
  const pendingRenames = [];
9077
9708
  for (const [label, ops] of grouped) {
9078
- const path = resolve10(ctx.base, label);
9709
+ const path = resolve12(ctx.base, label);
9079
9710
  if (ops.renamedTo !== void 0) {
9080
9711
  if (!existsSync4(path)) {
9081
9712
  throw new DocmetaError(
9082
9713
  `"${label}" is gone from disk since it was read; re-run the query.`
9083
9714
  );
9084
9715
  }
9085
- const toDisk = resolve10(ctx.base, ops.renamedTo);
9716
+ const toDisk = resolve12(ctx.base, ops.renamedTo);
9086
9717
  if (existsSync4(toDisk)) {
9087
9718
  throw new DocmetaError(
9088
9719
  `"${ops.renamedTo}" appeared on disk since the plan; re-run the query.`
@@ -9115,10 +9746,15 @@ async function applyChanges(changes, entries, ctx, schemaPlan) {
9115
9746
  }
9116
9747
  const entry = byLabel.get(label);
9117
9748
  if (!entry) throw new DocmetaError(`No loaded entry for "${label}".`);
9118
- const content = await readFile8(path, "utf8");
9119
- const current = entry.extractor.extract(content, label, {
9120
- elements: resolveElements(label, ctx.config)
9121
- });
9749
+ const content = await readFile9(path, "utf8");
9750
+ const current = mergeSidecars(
9751
+ label,
9752
+ entry.extractor.extract(content, label, {
9753
+ elements: resolveElements(label, ctx.config)
9754
+ }),
9755
+ ctx.sidecars,
9756
+ ctx.base
9757
+ ).extracted;
9122
9758
  if (ops.cleared) {
9123
9759
  if (!deepEqual(current.data, entry.extracted.data)) {
9124
9760
  throw new DocmetaError(
@@ -9152,9 +9788,14 @@ async function applyChanges(changes, entries, ctx, schemaPlan) {
9152
9788
  deletions: ops.deletions
9153
9789
  });
9154
9790
  if (ops.deletions.length > 0) {
9155
- const check = entry.extractor.extract(applied, label, {
9156
- elements: resolveElements(label, ctx.config)
9157
- });
9791
+ const check = mergeSidecars(
9792
+ label,
9793
+ entry.extractor.extract(applied, label, {
9794
+ elements: resolveElements(label, ctx.config)
9795
+ }),
9796
+ ctx.sidecars,
9797
+ ctx.base
9798
+ ).extracted;
9158
9799
  for (const key of ops.deletions) {
9159
9800
  if (check.data[key] !== void 0) {
9160
9801
  throw new DocmetaError(
@@ -9177,7 +9818,7 @@ async function applyChanges(changes, entries, ctx, schemaPlan) {
9177
9818
  }
9178
9819
  let now;
9179
9820
  try {
9180
- now = await readFile8(w.path, "utf8");
9821
+ now = await readFile9(w.path, "utf8");
9181
9822
  } catch {
9182
9823
  now = void 0;
9183
9824
  }
@@ -9188,10 +9829,10 @@ async function applyChanges(changes, entries, ctx, schemaPlan) {
9188
9829
  }
9189
9830
  }
9190
9831
  const dirs = /* @__PURE__ */ new Set();
9191
- for (const w of schemaWrites) dirs.add(dirname5(w.path));
9192
- for (const r of pendingRenames) dirs.add(dirname5(r.to));
9832
+ for (const w of schemaWrites) dirs.add(dirname6(w.path));
9833
+ for (const r of pendingRenames) dirs.add(dirname6(r.to));
9193
9834
  for (const p of pendingWrites) {
9194
- if (p.ensureDir) dirs.add(dirname5(p.path));
9835
+ if (p.ensureDir) dirs.add(dirname6(p.path));
9195
9836
  }
9196
9837
  for (const d of dirs) await mkdir3(d, { recursive: true });
9197
9838
  const writeSchema = async () => {
@@ -9306,8 +9947,8 @@ function skipExpression(sql, from) {
9306
9947
  }
9307
9948
 
9308
9949
  // src/meta/commands/fill.ts
9309
- import { readFile as readFile9 } from "node:fs/promises";
9310
- import { resolve as resolve11, extname as extname6, join as join6 } from "node:path";
9950
+ import { readFile as readFile10 } from "node:fs/promises";
9951
+ import { resolve as resolve13, extname as extname6, join as join6 } from "node:path";
9311
9952
  import {
9312
9953
  completeValidatedJSON,
9313
9954
  makeProvider,
@@ -9580,6 +10221,11 @@ async function runFill(opts) {
9580
10221
  let turnsUsed = 0;
9581
10222
  let inFlight = 0;
9582
10223
  const turnsExhausted = () => maxTurns != null && turnsUsed + inFlight >= maxTurns;
10224
+ const sidecars = await loadSidecars(config, {
10225
+ configDir: configDir ?? cwd,
10226
+ base,
10227
+ offline: opts.offline ?? config?.offline ?? false
10228
+ });
9583
10229
  const processOne = async (label, content, extension) => {
9584
10230
  const extractor = forcedExtractor ?? extractorForExtension(extension);
9585
10231
  if (!extractor) {
@@ -9598,8 +10244,15 @@ async function runFill(opts) {
9598
10244
  }
9599
10245
  const elements = resolveElements(label, config);
9600
10246
  let extracted;
10247
+ let merged;
9601
10248
  try {
9602
- extracted = extractor.extract(content, label, { elements });
10249
+ merged = mergeSidecars(
10250
+ label,
10251
+ extractor.extract(content, label, { elements }),
10252
+ sidecars,
10253
+ base
10254
+ );
10255
+ extracted = merged.extracted;
9603
10256
  } catch (err) {
9604
10257
  return errorResult(label, extractor.name, err.message);
9605
10258
  }
@@ -9631,7 +10284,8 @@ async function runFill(opts) {
9631
10284
  extracted.data,
9632
10285
  schemaSet,
9633
10286
  extracted.lineFor,
9634
- extracted.colFor
10287
+ extracted.colFor,
10288
+ merged.locate
9635
10289
  );
9636
10290
  } catch (err) {
9637
10291
  if (!(err instanceof DocmetaError) || resolved.source !== "document") {
@@ -9645,6 +10299,19 @@ async function runFill(opts) {
9645
10299
  existingErrors,
9646
10300
  only
9647
10301
  );
10302
+ const ownedCandidates = sidecars ? candidates.flatMap((c) => {
10303
+ const file = sidecars.owners.get(c.key);
10304
+ return file === void 0 ? [] : [{ key: c.key, file }];
10305
+ }) : [];
10306
+ const firstOwned = ownedCandidates[0];
10307
+ if (firstOwned) {
10308
+ return errorResult(
10309
+ label,
10310
+ extractor.name,
10311
+ `"${firstOwned.key}" is owned by sidecar ${firstOwned.file}; manni meta fill cannot write a sidecar key. Add it to the manifest instead.`,
10312
+ schemaSet
10313
+ );
10314
+ }
9648
10315
  if (candidates.length === 0) {
9649
10316
  return {
9650
10317
  file: label,
@@ -9785,9 +10452,9 @@ async function runFill(opts) {
9785
10452
  const fields = gate(candidates, proposals, threshold);
9786
10453
  const accepted = fields.filter((f) => f.written);
9787
10454
  if (accepted.length > 0) {
9788
- const merged = { ...extracted.data, ...patchOf(accepted) };
10455
+ const merged2 = { ...extracted.data, ...patchOf(accepted) };
9789
10456
  const after = await validator.validate(
9790
- merged,
10457
+ merged2,
9791
10458
  schemaSet,
9792
10459
  extracted.lineFor,
9793
10460
  extracted.colFor
@@ -9831,7 +10498,7 @@ async function runFill(opts) {
9831
10498
  }
9832
10499
  const changed = next !== content;
9833
10500
  if (changed && !dryRun && label !== "<stdin>") {
9834
- await writeFileAtomic(resolve11(base, label), next);
10501
+ await writeFileAtomic(resolve13(base, label), next);
9835
10502
  }
9836
10503
  return {
9837
10504
  file: label,
@@ -9853,7 +10520,7 @@ async function runFill(opts) {
9853
10520
  );
9854
10521
  }
9855
10522
  const fileResults = await mapConcurrent(files, concurrency, async (file) => {
9856
- const content = await readFile9(resolve11(base, file), "utf8");
10523
+ const content = await readFile10(resolve13(base, file), "utf8");
9857
10524
  return processOne(file, content, extname6(file));
9858
10525
  });
9859
10526
  results.push(...fileResults);
@@ -10171,15 +10838,19 @@ async function mapConcurrent(items, limit, fn) {
10171
10838
  }
10172
10839
 
10173
10840
  // src/meta/reporters/sarif.ts
10174
- import { isAbsolute as isAbsolute4 } from "node:path";
10841
+ import { isAbsolute as isAbsolute5 } from "node:path";
10175
10842
 
10176
10843
  // src/meta/reporters/rule-id.ts
10177
10844
  var SYNTHETIC_SCHEMA = "(parse)";
10178
10845
  var PARSE_ERROR_RULE = "manni/parse-error";
10179
10846
  var SCHEMA_ERROR_RULE = "manni/schema-error";
10847
+ var SIDECAR_OWNED_RULE = "sidecar:owned/sidecar";
10848
+ var SIDECAR_DUPLICATE_RULE = "sidecar:duplicate/sidecar";
10180
10849
  var RESERVED_RULES = {
10181
10850
  [PARSE_ERROR_RULE]: "The document's metadata block could not be parsed.",
10182
- [SCHEMA_ERROR_RULE]: "No schema set could be resolved for the document."
10851
+ [SCHEMA_ERROR_RULE]: "No schema set could be resolved for the document.",
10852
+ [SIDECAR_OWNED_RULE]: "The document carries a key a sidecar manifest owns; the manifest is the only place that key may be set.",
10853
+ [SIDECAR_DUPLICATE_RULE]: "Two documents carry the same value of a sidecar's join field, so one manifest entry matched both."
10183
10854
  };
10184
10855
  function ruleIdFor(e, frame) {
10185
10856
  if (e.schema === SYNTHETIC_SCHEMA) {
@@ -10207,7 +10878,7 @@ function uriFrame(frame) {
10207
10878
  }
10208
10879
  function artifactUri(file, frame) {
10209
10880
  const uri = canonicalFilePath(file, frame);
10210
- if (uri === "" || uri.startsWith("../") || isAbsolute4(uri)) return null;
10881
+ if (uri === "" || uri.startsWith("../") || isAbsolute5(uri)) return null;
10211
10882
  return uri;
10212
10883
  }
10213
10884
  function renderSarif(results, opts = {}) {
@@ -10221,12 +10892,13 @@ function renderSarif(results, opts = {}) {
10221
10892
  for (const r of results) {
10222
10893
  if (r.errors.length === 0) continue;
10223
10894
  if (r.file === STDIN_LABEL) continue;
10224
- const uri = artifactUri(r.file, pathFrame);
10225
- if (uri === null) {
10226
- dropped += r.errors.length;
10227
- continue;
10228
- }
10895
+ const docUri = artifactUri(r.file, pathFrame);
10229
10896
  for (const e of r.errors) {
10897
+ const uri = e.file != null ? artifactUri(e.file, pathFrame) : docUri;
10898
+ if (uri === null) {
10899
+ dropped += 1;
10900
+ continue;
10901
+ }
10230
10902
  const ruleId = ruleIdFor(e, frame);
10231
10903
  if (!rules.has(ruleId)) {
10232
10904
  rules.set(ruleId, {
@@ -10334,7 +11006,7 @@ function renderJunit(results, opts = {}) {
10334
11006
  }
10335
11007
  lines.push(`${open2}>`);
10336
11008
  for (const e of r.errors) {
10337
- const where = e.line != null ? ` (line ${e.line})` : "";
11009
+ const where = e.file != null ? ` (${e.file}${e.line != null ? `:${e.line}` : ""})` : e.line != null ? ` (line ${e.line})` : "";
10338
11010
  lines.push(
10339
11011
  ` <failure${attr("type", ruleIdFor(e, opts.frame))}${attr(
10340
11012
  "message",
@@ -10443,7 +11115,7 @@ function renderPretty(results, summary, opts = {}) {
10443
11115
  }
10444
11116
  lines.push(`${c.red("\u2717")} ${r.file}${forgiven}`);
10445
11117
  for (const e of r.errors) {
10446
- const loc = e.line != null ? c.dim(` (line ${e.line})`) : "";
11118
+ const loc = e.file != null ? c.dim(` (${e.file}${e.line != null ? `:${e.line}` : ""})`) : e.line != null ? c.dim(` (line ${e.line})`) : "";
10447
11119
  lines.push(
10448
11120
  ` ${c.cyan(fieldLabel(e.instancePath))} ${e.message}${loc} ${c.dim(
10449
11121
  `[${e.schema}]`
@@ -10467,7 +11139,7 @@ function renderGithub(results) {
10467
11139
  const lines = [];
10468
11140
  for (const r of results) {
10469
11141
  for (const e of r.errors) {
10470
- const params = [`file=${escapeWorkflowCommandProperty(r.file)}`];
11142
+ const params = [`file=${escapeWorkflowCommandProperty(e.file ?? r.file)}`];
10471
11143
  if (e.line != null) params.push(`line=${e.line}`);
10472
11144
  if (e.col != null) params.push(`col=${e.col}`);
10473
11145
  const msg = escapeWorkflowCommandMessage(
@@ -10766,17 +11438,32 @@ export {
10766
11438
  fetchSchemaBytes,
10767
11439
  schemaLoadOptions,
10768
11440
  loadSchema,
11441
+ DEFAULT_SCHEMAS,
11442
+ schemaEntryRef,
11443
+ collectSchemaPins,
11444
+ rebaseConfigSchemaRefs,
11445
+ resolveSchemaSet,
11446
+ SIDECAR_FETCH_TIMEOUT_MS,
11447
+ SIDECAR_FETCH_MAX_BYTES,
11448
+ sidecarUrlProblem,
11449
+ fetchSidecar,
10769
11450
  locateFrontmatter,
10770
11451
  frontmatterInnerText,
10771
11452
  extractFrontmatter,
10772
11453
  applyFrontmatter,
10773
11454
  extractorForExtension,
10774
11455
  supportedExtensions,
10775
- DEFAULT_SCHEMAS,
10776
- schemaEntryRef,
10777
- collectSchemaPins,
10778
- rebaseConfigSchemaRefs,
10779
- resolveSchemaSet,
11456
+ SIDECAR_OWNED_SCHEMA,
11457
+ SIDECAR_DUPLICATE_SCHEMA,
11458
+ SIDECAR_KEYWORD,
11459
+ PATH_JOIN,
11460
+ sidecarJoin,
11461
+ loadSidecars,
11462
+ mergeSidecars,
11463
+ sidecarPointer,
11464
+ orphanEntries,
11465
+ orphanJoins,
11466
+ orphanError,
10780
11467
  parseConfig,
10781
11468
  loadConfig,
10782
11469
  resolveRunConfig,
@@ -10817,4 +11504,4 @@ export {
10817
11504
  renderQuery,
10818
11505
  renderQueryCsv
10819
11506
  };
10820
- //# sourceMappingURL=chunk-2OALUJV2.js.map
11507
+ //# sourceMappingURL=chunk-7M2YOXVG.js.map