superbee 0.4.4-pre.9 → 0.4.4

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.
@@ -59,8 +59,8 @@ var require_common = __commonJS({
59
59
  return target;
60
60
  }
61
61
  function repeat(string, count) {
62
- var result = "", cycle;
63
- for (cycle = 0; cycle < count; cycle += 1) {
62
+ var result = "", cycle2;
63
+ for (cycle2 = 0; cycle2 < count; cycle2 += 1) {
64
64
  result += string;
65
65
  }
66
66
  return result;
@@ -2881,6 +2881,14 @@ var InvalidInputError = class extends Error {
2881
2881
  this.name = "InvalidInputError";
2882
2882
  }
2883
2883
  };
2884
+ var FrontmatterRefusedError = class extends InvalidInputError {
2885
+ code;
2886
+ constructor(code, detail) {
2887
+ super(`frontmatter cannot be stored: ${detail}`);
2888
+ this.name = "FrontmatterRefusedError";
2889
+ this.code = code;
2890
+ }
2891
+ };
2884
2892
  var FilesystemIdentityAliasError = class extends InvalidInputError {
2885
2893
  rel;
2886
2894
  segment;
@@ -2905,12 +2913,21 @@ var ConcurrentReplacementError = class extends Error {
2905
2913
  }
2906
2914
  };
2907
2915
 
2908
- // ../core/src/portable-frontmatter.ts
2916
+ // ../core/src/bounded-yaml.ts
2909
2917
  var import_js_yaml = __toESM(require_js_yaml2(), 1);
2910
2918
 
2911
2919
  // ../core/src/frontmatter-contract.ts
2920
+ var FrontmatterLimitError = class extends Error {
2921
+ name = "FrontmatterLimitError";
2922
+ code;
2923
+ constructor(code, message) {
2924
+ super(message);
2925
+ this.code = code;
2926
+ }
2927
+ };
2912
2928
  var MalformedDocumentError = class extends Error {
2913
2929
  name = "MalformedDocumentError";
2930
+ code;
2914
2931
  context;
2915
2932
  detail;
2916
2933
  constructor(context, cause) {
@@ -2918,12 +2935,165 @@ var MalformedDocumentError = class extends Error {
2918
2935
  super(
2919
2936
  `malformed frontmatter${context ? ` in '${context}'` : ""}: ${detail} \u2014 fix the YAML or remove the file`
2920
2937
  );
2938
+ this.code = cause instanceof FrontmatterLimitError ? cause.code : "FRONTMATTER_MALFORMED";
2921
2939
  if (context !== void 0) this.context = context;
2922
2940
  this.detail = detail;
2923
2941
  if (cause !== void 0) this.cause = cause;
2924
2942
  }
2925
2943
  };
2926
2944
 
2945
+ // ../core/src/bounded-yaml.ts
2946
+ var FRONTMATTER_LIMITS = Object.freeze({
2947
+ sourceChars: 256 * 1024,
2948
+ depth: 32,
2949
+ aliases: 1e3,
2950
+ expandedNodes: 1e5,
2951
+ expandedChars: 1024 * 1024
2952
+ });
2953
+ var PARSE_NESTING_GUARD = FRONTMATTER_LIMITS.depth * 4;
2954
+ function tooDeep() {
2955
+ return new FrontmatterLimitError("FRONTMATTER_TOO_DEEP", `YAML nesting is deeper than ${FRONTMATTER_LIMITS.depth} levels`);
2956
+ }
2957
+ function tooLarge() {
2958
+ return new FrontmatterLimitError(
2959
+ "FRONTMATTER_ALIAS_LIMIT",
2960
+ `YAML aliases expand to more than ${FRONTMATTER_LIMITS.expandedNodes} nodes or ${FRONTMATTER_LIMITS.expandedChars} characters`
2961
+ );
2962
+ }
2963
+ function cycle() {
2964
+ return new FrontmatterLimitError("FRONTMATTER_CYCLE", "YAML aliases form a cycle");
2965
+ }
2966
+ function scalarExtent(value) {
2967
+ const chars = typeof value === "string" ? value.length : ArrayBuffer.isView(value) ? value.byteLength : 0;
2968
+ return { nodes: 1, chars, depth: 0 };
2969
+ }
2970
+ function isCollection(value) {
2971
+ if (Array.isArray(value)) return true;
2972
+ if (value === null || typeof value !== "object") return false;
2973
+ const prototype = Object.getPrototypeOf(value);
2974
+ return prototype === Object.prototype || prototype === null;
2975
+ }
2976
+ var ExtentMeter = class {
2977
+ known = /* @__PURE__ */ new Map();
2978
+ path = /* @__PURE__ */ new Set();
2979
+ unfinished;
2980
+ constructor(unfinished = /* @__PURE__ */ new Set()) {
2981
+ this.unfinished = unfinished;
2982
+ }
2983
+ measure(value) {
2984
+ if (!isCollection(value)) return scalarExtent(value);
2985
+ const known = this.known.get(value);
2986
+ if (known !== void 0) return known;
2987
+ if (this.path.has(value) || this.unfinished.has(value)) throw cycle();
2988
+ return this.walk(value);
2989
+ }
2990
+ walk(value) {
2991
+ if (this.path.size >= FRONTMATTER_LIMITS.depth) throw tooDeep();
2992
+ this.path.add(value);
2993
+ const keyed = !Array.isArray(value);
2994
+ const extent = { nodes: 1, chars: 0, depth: 1 };
2995
+ for (const [key, child] of Object.entries(value)) {
2996
+ const inner = this.measure(child);
2997
+ extent.nodes += inner.nodes;
2998
+ extent.chars += inner.chars + (keyed ? key.length : 0);
2999
+ extent.depth = Math.max(extent.depth, inner.depth + 1);
3000
+ if (extent.nodes > FRONTMATTER_LIMITS.expandedNodes || extent.chars > FRONTMATTER_LIMITS.expandedChars) throw tooLarge();
3001
+ if (extent.depth > FRONTMATTER_LIMITS.depth) throw tooDeep();
3002
+ }
3003
+ this.path.delete(value);
3004
+ this.known.set(value, extent);
3005
+ return extent;
3006
+ }
3007
+ };
3008
+ var AliasBudget = class {
3009
+ unfinished = /* @__PURE__ */ new Set();
3010
+ finished = /* @__PURE__ */ new Set();
3011
+ meter = new ExtentMeter(this.unfinished);
3012
+ aliases = 0;
3013
+ nodes = 0;
3014
+ chars = 0;
3015
+ /** Wrap the parser's anchor table; the parser reads it only to resolve an alias. */
3016
+ watch(state) {
3017
+ state.anchorMap = new Proxy(state.anchorMap, {
3018
+ get: (target, name, receiver) => {
3019
+ const value = Reflect.get(target, name, receiver);
3020
+ if (typeof name === "string") this.charge(name, value);
3021
+ return value;
3022
+ },
3023
+ set: (target, name, value, receiver) => {
3024
+ if (typeof name === "string") {
3025
+ this.finished.delete(name);
3026
+ if (isCollection(value)) this.unfinished.add(value);
3027
+ }
3028
+ return Reflect.set(target, name, value, receiver);
3029
+ }
3030
+ });
3031
+ }
3032
+ /** Called when an anchored node closes: its anchor now names a complete value. */
3033
+ close(anchor, value) {
3034
+ this.finished.add(anchor);
3035
+ if (isCollection(value)) this.unfinished.delete(value);
3036
+ }
3037
+ charge(name, value) {
3038
+ this.aliases += 1;
3039
+ if (this.aliases > FRONTMATTER_LIMITS.aliases) {
3040
+ throw new FrontmatterLimitError(
3041
+ "FRONTMATTER_ALIAS_LIMIT",
3042
+ `YAML aliases are used more than ${FRONTMATTER_LIMITS.aliases} times`
3043
+ );
3044
+ }
3045
+ if (!this.finished.has(name) && isCollection(value)) throw cycle();
3046
+ const extent = this.meter.measure(value);
3047
+ this.nodes += extent.nodes;
3048
+ this.chars += extent.chars;
3049
+ if (this.nodes > FRONTMATTER_LIMITS.expandedNodes || this.chars > FRONTMATTER_LIMITS.expandedChars) throw tooLarge();
3050
+ }
3051
+ };
3052
+ var MERGE_KEY_LIMIT_REASON = "merge keys exceeded maxTotalMergeKeys";
3053
+ function loadBoundedYaml(source, options = {}) {
3054
+ if (source.length > FRONTMATTER_LIMITS.sourceChars) {
3055
+ throw new FrontmatterLimitError(
3056
+ "FRONTMATTER_TOO_LARGE",
3057
+ `YAML source is longer than ${FRONTMATTER_LIMITS.sourceChars} characters`
3058
+ );
3059
+ }
3060
+ const schema = options.schema === "json" ? import_js_yaml.default.JSON_SCHEMA : options.schema;
3061
+ const budget = new AliasBudget();
3062
+ let watched;
3063
+ let nesting = 0;
3064
+ let value;
3065
+ try {
3066
+ value = import_js_yaml.default.safeLoad(source, {
3067
+ ...schema ? { schema } : {},
3068
+ listener(event, rawState) {
3069
+ const state = rawState;
3070
+ if (state.anchorMap !== watched) {
3071
+ budget.watch(state);
3072
+ watched = state.anchorMap;
3073
+ }
3074
+ if (event === "open") {
3075
+ nesting += 1;
3076
+ if (nesting > PARSE_NESTING_GUARD) throw tooDeep();
3077
+ } else {
3078
+ nesting -= 1;
3079
+ if (state.anchor !== null) budget.close(state.anchor, state.result);
3080
+ }
3081
+ }
3082
+ });
3083
+ } catch (error) {
3084
+ const reason = error.reason;
3085
+ if (typeof reason === "string" && reason.startsWith(MERGE_KEY_LIMIT_REASON)) {
3086
+ throw new FrontmatterLimitError("FRONTMATTER_ALIAS_LIMIT", reason);
3087
+ }
3088
+ throw error;
3089
+ }
3090
+ new ExtentMeter().measure(value);
3091
+ return value;
3092
+ }
3093
+
3094
+ // ../core/src/frontmatter.ts
3095
+ var import_js_yaml2 = __toESM(require_js_yaml2(), 1);
3096
+
2927
3097
  // ../core/src/frontmatter-splitter.ts
2928
3098
  function delimiterLineEnd(input, offset) {
2929
3099
  if (!input.startsWith("---", offset)) return null;
@@ -2953,12 +3123,106 @@ function splitLeadingFrontmatter(raw, context) {
2953
3123
  throw new MalformedDocumentError(context, new Error("unterminated YAML frontmatter delimiter"));
2954
3124
  }
2955
3125
 
3126
+ // ../core/src/frontmatter.ts
3127
+ var YAML_TIMESTAMP_TAG = "tag:yaml.org,2002:timestamp";
3128
+ function requireYamlTimestampType() {
3129
+ const type = import_js_yaml2.default.DEFAULT_SAFE_SCHEMA.implicit.find(
3130
+ (candidate) => candidate.tag === YAML_TIMESTAMP_TAG
3131
+ );
3132
+ if (!type) throw new Error("js-yaml safe schema is missing its timestamp type");
3133
+ return type;
3134
+ }
3135
+ var yamlTimestampType = requireYamlTimestampType();
3136
+ var stringTimestampType = new import_js_yaml2.default.Type(YAML_TIMESTAMP_TAG, {
3137
+ kind: "scalar",
3138
+ resolve: (value) => yamlTimestampType.resolve(value),
3139
+ construct: (value) => value
3140
+ });
3141
+ var losslessDateSchema = new import_js_yaml2.default.Schema({
3142
+ include: [import_js_yaml2.default.DEFAULT_SAFE_SCHEMA],
3143
+ implicit: [stringTimestampType]
3144
+ });
3145
+ var yamlEngine = {
3146
+ parse(input) {
3147
+ return loadBoundedYaml(input, { schema: losslessDateSchema }) ?? {};
3148
+ }
3149
+ };
3150
+ function normalizeFrontmatter(data, options) {
3151
+ const out = { ...data };
3152
+ if (options.preserveTimestamp || options.okfVersion === "0.2") return out;
3153
+ const value = out.timestamp;
3154
+ if (value instanceof Date) {
3155
+ out.timestamp = value.toISOString();
3156
+ } else if (typeof value === "number" && Number.isFinite(value)) {
3157
+ out.timestamp = new Date(value).toISOString();
3158
+ } else if (typeof value === "string" && yamlTimestampType.resolve(value)) {
3159
+ const parsed = yamlTimestampType.construct(value);
3160
+ if (parsed instanceof Date) out.timestamp = parsed.toISOString();
3161
+ }
3162
+ return out;
3163
+ }
3164
+ function parseMarkdown(raw, context, options = {}) {
3165
+ const split = splitLeadingFrontmatter(raw, context);
3166
+ if (!("yamlSource" in split)) return { frontmatter: {}, body: split.body };
3167
+ return { frontmatter: parseFrontmatter(split.yamlSource, context, options), body: split.body };
3168
+ }
3169
+ function parseFrontmatter(source, context, options = {}) {
3170
+ let parsed;
3171
+ try {
3172
+ parsed = yamlEngine.parse(source);
3173
+ } catch (err) {
3174
+ throw new MalformedDocumentError(context, err);
3175
+ }
3176
+ return normalizeFrontmatter(parsed, options);
3177
+ }
3178
+ function isStackExhaustion(error) {
3179
+ if (error instanceof RangeError) return /call stack/i.test(error.message);
3180
+ return error?.name === "InternalError" && /recursion/i.test(error.message);
3181
+ }
3182
+ function stringifyFrontmatter(data) {
3183
+ let dumped;
3184
+ try {
3185
+ dumped = import_js_yaml2.default.safeDump(data).trim();
3186
+ } catch (error) {
3187
+ if (isStackExhaustion(error)) throw new FrontmatterRefusedError("FRONTMATTER_TOO_DEEP", `YAML nesting is deeper than ${FRONTMATTER_LIMITS.depth} levels`);
3188
+ throw error;
3189
+ }
3190
+ try {
3191
+ yamlEngine.parse(`${dumped}
3192
+ `);
3193
+ } catch (error) {
3194
+ if (error instanceof FrontmatterLimitError) throw new FrontmatterRefusedError(error.code, error.message);
3195
+ const detail = (error instanceof Error ? error.message : String(error)).split("\n")[0] ?? "";
3196
+ throw new FrontmatterRefusedError("FRONTMATTER_MALFORMED", detail);
3197
+ }
3198
+ return dumped;
3199
+ }
3200
+ function normalizeDocumentBodyForStorage(body) {
3201
+ return body.endsWith("\n") ? body : `${body}
3202
+ `;
3203
+ }
3204
+ function stringifyWithData(data, body) {
3205
+ return stringifyWithSerializedFrontmatter(stringifyFrontmatter(data), body);
3206
+ }
3207
+ function stringifyWithSerializedFrontmatter(dumped, body) {
3208
+ const content = body ?? "";
3209
+ const newline = (value) => value.endsWith("\n") ? value : `${value}
3210
+ `;
3211
+ if (dumped === "{}") return normalizeDocumentBodyForStorage(content);
3212
+ return `---
3213
+ ${newline(dumped)}---
3214
+ ${normalizeDocumentBodyForStorage(content)}`;
3215
+ }
3216
+ function stringifyDoc(frontmatter2, body) {
3217
+ return stringifyWithData(frontmatter2, body);
3218
+ }
3219
+
2956
3220
  // ../core/src/portable-frontmatter.ts
2957
3221
  function parseLeadingFrontmatter(raw, context) {
2958
3222
  const split = splitLeadingFrontmatter(raw, context);
2959
3223
  if (!("yamlSource" in split)) return {};
2960
3224
  try {
2961
- const parsed = import_js_yaml.default.safeLoad(split.yamlSource);
3225
+ const parsed = loadBoundedYaml(split.yamlSource);
2962
3226
  if (parsed === void 0 || parsed === null) return {};
2963
3227
  if (typeof parsed !== "object" || Array.isArray(parsed)) {
2964
3228
  throw new TypeError("YAML frontmatter must be a mapping");
@@ -2969,6 +3233,18 @@ function parseLeadingFrontmatter(raw, context) {
2969
3233
  throw new MalformedDocumentError(context, error);
2970
3234
  }
2971
3235
  }
3236
+ function assertStorableFrontmatter(raw) {
3237
+ try {
3238
+ parseLeadingFrontmatter(raw);
3239
+ const split = splitLeadingFrontmatter(raw);
3240
+ if ("yamlSource" in split) parseFrontmatter(split.yamlSource);
3241
+ } catch (error) {
3242
+ if (error instanceof MalformedDocumentError && error.code !== "FRONTMATTER_MALFORMED") {
3243
+ throw new FrontmatterRefusedError(error.code, error.detail);
3244
+ }
3245
+ if (!(error instanceof MalformedDocumentError)) throw error;
3246
+ }
3247
+ }
2972
3248
 
2973
3249
  // ../core/src/paths.ts
2974
3250
  var RESERVED_FILENAMES = ["index.md", "log.md"];
@@ -3002,6 +3278,17 @@ function isReservedFile(relPath) {
3002
3278
  const base = toPosix(relPath).split("/").pop() ?? "";
3003
3279
  return RESERVED_FILENAMES.includes(base);
3004
3280
  }
3281
+ function isHiddenWalkEntry(name) {
3282
+ return name.startsWith(".");
3283
+ }
3284
+ function hasMarkdownExtension(name) {
3285
+ return name.toLowerCase().endsWith(".md");
3286
+ }
3287
+ function classifyWalkPath(relPath) {
3288
+ const segments = toPosix(relPath).split("/").filter((segment) => segment !== "");
3289
+ if (segments.some(isHiddenWalkEntry)) return "hidden";
3290
+ return hasMarkdownExtension(segments.at(-1) ?? "") ? "markdown" : "other";
3291
+ }
3005
3292
  function conceptIdFromPath(relPath) {
3006
3293
  const norm = toPosix(relPath).replace(/^\.?\//, "").split("/").filter((segment) => segment !== ".").join("/");
3007
3294
  return norm.endsWith(".md") ? norm.slice(0, -3) : norm;
@@ -3321,82 +3608,6 @@ function inventoryDigest(inventory) {
3321
3608
  return `sha256:${sha256HexOfUtf8(JSON.stringify(inventory.map((row) => [row.id, row.version])))}`;
3322
3609
  }
3323
3610
 
3324
- // ../core/src/frontmatter.ts
3325
- var import_js_yaml2 = __toESM(require_js_yaml2(), 1);
3326
- var YAML_TIMESTAMP_TAG = "tag:yaml.org,2002:timestamp";
3327
- function requireYamlTimestampType() {
3328
- const type = import_js_yaml2.default.DEFAULT_SAFE_SCHEMA.implicit.find(
3329
- (candidate) => candidate.tag === YAML_TIMESTAMP_TAG
3330
- );
3331
- if (!type) throw new Error("js-yaml safe schema is missing its timestamp type");
3332
- return type;
3333
- }
3334
- var yamlTimestampType = requireYamlTimestampType();
3335
- var stringTimestampType = new import_js_yaml2.default.Type(YAML_TIMESTAMP_TAG, {
3336
- kind: "scalar",
3337
- resolve: (value) => yamlTimestampType.resolve(value),
3338
- construct: (value) => value
3339
- });
3340
- var losslessDateSchema = new import_js_yaml2.default.Schema({
3341
- include: [import_js_yaml2.default.DEFAULT_SAFE_SCHEMA],
3342
- implicit: [stringTimestampType]
3343
- });
3344
- var yamlEngine = {
3345
- parse(input) {
3346
- return import_js_yaml2.default.safeLoad(input, { schema: losslessDateSchema }) ?? {};
3347
- }
3348
- };
3349
- function normalizeFrontmatter(data, options) {
3350
- const out = { ...data };
3351
- if (options.preserveTimestamp || options.okfVersion === "0.2") return out;
3352
- const value = out.timestamp;
3353
- if (value instanceof Date) {
3354
- out.timestamp = value.toISOString();
3355
- } else if (typeof value === "number" && Number.isFinite(value)) {
3356
- out.timestamp = new Date(value).toISOString();
3357
- } else if (typeof value === "string" && yamlTimestampType.resolve(value)) {
3358
- const parsed = yamlTimestampType.construct(value);
3359
- if (parsed instanceof Date) out.timestamp = parsed.toISOString();
3360
- }
3361
- return out;
3362
- }
3363
- function parseMarkdown(raw, context, options = {}) {
3364
- const split = splitLeadingFrontmatter(raw, context);
3365
- if (!("yamlSource" in split)) return { frontmatter: {}, body: split.body };
3366
- return { frontmatter: parseFrontmatter(split.yamlSource, context, options), body: split.body };
3367
- }
3368
- function parseFrontmatter(source, context, options = {}) {
3369
- let parsed;
3370
- try {
3371
- parsed = yamlEngine.parse(source);
3372
- } catch (err) {
3373
- throw new MalformedDocumentError(context, err);
3374
- }
3375
- return normalizeFrontmatter(parsed, options);
3376
- }
3377
- function stringifyFrontmatter(data) {
3378
- return import_js_yaml2.default.safeDump(data).trim();
3379
- }
3380
- function normalizeDocumentBodyForStorage(body) {
3381
- return body.endsWith("\n") ? body : `${body}
3382
- `;
3383
- }
3384
- function stringifyWithData(data, body) {
3385
- return stringifyWithSerializedFrontmatter(stringifyFrontmatter(data), body);
3386
- }
3387
- function stringifyWithSerializedFrontmatter(dumped, body) {
3388
- const content = body ?? "";
3389
- const newline = (value) => value.endsWith("\n") ? value : `${value}
3390
- `;
3391
- if (dumped === "{}") return normalizeDocumentBodyForStorage(content);
3392
- return `---
3393
- ${newline(dumped)}---
3394
- ${normalizeDocumentBodyForStorage(content)}`;
3395
- }
3396
- function stringifyDoc(frontmatter2, body) {
3397
- return stringifyWithData(frontmatter2, body);
3398
- }
3399
-
3400
3611
  // ../core/src/code-unit-order.ts
3401
3612
  function compareCodeUnits(a, b) {
3402
3613
  return a < b ? -1 : a > b ? 1 : 0;
@@ -3808,24 +4019,7 @@ function hasSourceIdentity(sources, identity) {
3808
4019
  return Array.isArray(sources) && sources.some((entry) => matchesSourceIdentity(entry, identity));
3809
4020
  }
3810
4021
 
3811
- // ../core/src/engine.ts
3812
- async function readBundleOkfVersion(backend) {
3813
- const index = await backend.readReserved("", "index.md");
3814
- if (!index) return void 0;
3815
- const value = parseLeadingFrontmatter(index.content, "index.md").okf_version;
3816
- return typeof value === "string" && value.trim() !== "" ? value : void 0;
3817
- }
3818
- function assertReadableConceptId(id) {
3819
- assertSafeConceptId(id);
3820
- const rel = pathFromConceptId(id);
3821
- if (isReservedFile(rel)) {
3822
- throw new InvalidInputError(`'${id}' is a reserved file (index.md / log.md), not a concept document.`);
3823
- }
3824
- }
3825
- async function readDocVersioned(backend, id) {
3826
- assertReadableConceptId(id);
3827
- return backend.read(id);
3828
- }
4022
+ // ../core/src/document-scan.ts
3829
4023
  function isEnoent(err) {
3830
4024
  return typeof err === "object" && err !== null && err.code === "ENOENT";
3831
4025
  }
@@ -3856,6 +4050,25 @@ async function readManyExisting(backend, ids, onMalformed, onAliased) {
3856
4050
  return out;
3857
4051
  }
3858
4052
  }
4053
+
4054
+ // ../core/src/engine.ts
4055
+ async function readBundleOkfVersion(backend) {
4056
+ const index = await backend.readReserved("", "index.md");
4057
+ if (!index) return void 0;
4058
+ const value = parseLeadingFrontmatter(index.content, "index.md").okf_version;
4059
+ return typeof value === "string" && value.trim() !== "" ? value : void 0;
4060
+ }
4061
+ function assertReadableConceptId(id) {
4062
+ assertSafeConceptId(id);
4063
+ const rel = pathFromConceptId(id);
4064
+ if (isReservedFile(rel)) {
4065
+ throw new InvalidInputError(`'${id}' is a reserved file (index.md / log.md), not a concept document.`);
4066
+ }
4067
+ }
4068
+ async function readDocVersioned(backend, id) {
4069
+ assertReadableConceptId(id);
4070
+ return backend.read(id);
4071
+ }
3859
4072
  async function scanMatching(backend, filter, onSkip) {
3860
4073
  const ids = await backend.list(filter.prefix);
3861
4074
  const results = [];
@@ -3974,6 +4187,13 @@ function resolveContentType(key, override) {
3974
4187
  return inferContentTypeFromDocKey(key) ?? DEFAULT_BLOB_CONTENT_TYPE;
3975
4188
  }
3976
4189
 
4190
+ // ../core/src/reserved-write.ts
4191
+ function assertStorableReservedWrite(dir, name, content) {
4192
+ assertSafeReservedDir(dir);
4193
+ assertSafeReservedFilename(name);
4194
+ if (name.endsWith(".md")) assertStorableFrontmatter(content);
4195
+ }
4196
+
3977
4197
  // ../core/src/filesystem-identity.ts
3978
4198
  import { promises as fs2 } from "node:fs";
3979
4199
  import path2 from "node:path";
@@ -5124,7 +5344,7 @@ async function walkFiles(port, root, keep, sub = "") {
5124
5344
  const entries = await port.entries(path3.join(root, sub)) ?? [];
5125
5345
  const out = [];
5126
5346
  for (const entry of entries) {
5127
- if (entry.name.startsWith(".")) continue;
5347
+ if (isHiddenWalkEntry(entry.name)) continue;
5128
5348
  const rel = sub === "" ? entry.name : `${sub}/${entry.name}`;
5129
5349
  if (entry.kind === "directory") {
5130
5350
  out.push(...await walkFiles(port, root, keep, rel));
@@ -5328,8 +5548,7 @@ var FilesystemBackend = class _FilesystemBackend {
5328
5548
  return { content, version: versionOfBytes(content) };
5329
5549
  }
5330
5550
  async writeReserved(dir, name, content, options = {}) {
5331
- assertSafeReservedDir(dir);
5332
- assertSafeReservedFilename(name);
5551
+ assertStorableReservedWrite(dir, name, content);
5333
5552
  const rel = reservedPath(dir, name);
5334
5553
  return this.#mutateExact(rel, async (target) => {
5335
5554
  if (options.expectedVersion !== void 0) {
@@ -5384,12 +5603,15 @@ var FilesystemBackend = class _FilesystemBackend {
5384
5603
  return observed.state === "exact" && observed.value.kind === "file";
5385
5604
  }
5386
5605
  async listBlobs(prefix) {
5387
- const files = await walkFiles(this.#port, this.#root, (name) => !name.toLowerCase().endsWith(".md"));
5606
+ const files = await walkFiles(this.#port, this.#root, (name) => !hasMarkdownExtension(name));
5388
5607
  const keys2 = files.map(toPosix);
5389
5608
  const filtered = prefix ? keys2.filter((k) => k.startsWith(prefix)) : keys2;
5390
5609
  filtered.sort(compareStorageKeys);
5391
5610
  return filtered;
5392
5611
  }
5612
+ classifyChangedPath(relPath) {
5613
+ return classifyWalkPath(relPath);
5614
+ }
5393
5615
  capabilities() {
5394
5616
  return {
5395
5617
  history: false,