@skill-map/cli 0.64.1 → 0.66.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.
@@ -1,10 +1,10 @@
1
1
  // conformance/index.ts
2
2
 
3
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="cc85d6cc-8968-5fbd-b7b7-eab3d298263d")}catch(e){}}();
3
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="bc1fc06c-0be1-5b35-9ac6-cedcc9367962")}catch(e){}}();
4
4
  import { spawnSync } from "child_process";
5
5
  import { cpSync, existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync } from "fs";
6
6
  import { tmpdir } from "os";
7
- import { isAbsolute, join as join2, relative, resolve } from "path";
7
+ import { isAbsolute, join as join2, relative, resolve as resolve2 } from "path";
8
8
 
9
9
  // kernel/util/format-error.ts
10
10
  function formatErrorMessage(err) {
@@ -12,7 +12,7 @@ function formatErrorMessage(err) {
12
12
  }
13
13
 
14
14
  // kernel/util/skill-map-paths.ts
15
- import { join } from "path";
15
+ import { dirname, join, resolve } from "path";
16
16
  var SKILL_MAP_DIR = ".skill-map";
17
17
  var KERNEL_SKILL_MAP_DIR = SKILL_MAP_DIR;
18
18
 
@@ -200,7 +200,7 @@ function assertContained(root, rel, label) {
200
200
  tx(CONFORMANCE_RUNNER_TEXTS.pathMustBeRelative, { label, path: rel, anchor: root })
201
201
  );
202
202
  }
203
- const abs = resolve(root, rel);
203
+ const abs = resolve2(root, rel);
204
204
  const r = relative(root, abs);
205
205
  if (r.startsWith("..") || isAbsolute(r)) {
206
206
  throw new Error(
@@ -227,7 +227,7 @@ function evaluateAssertion(a, ctx) {
227
227
  } catch (err) {
228
228
  return { ok: false, type: a.type, reason: formatErrorMessage(err) };
229
229
  }
230
- const abs = resolve(ctx.scope, a.path);
230
+ const abs = resolve2(ctx.scope, a.path);
231
231
  return existsSync(abs) ? { ok: true, type: a.type } : {
232
232
  ok: false,
233
233
  type: a.type,
@@ -242,7 +242,7 @@ function evaluateAssertion(a, ctx) {
242
242
  return { ok: false, type: a.type, reason: formatErrorMessage(err) };
243
243
  }
244
244
  const fixturePath = join2(ctx.fixturesRoot, a.fixture);
245
- const targetPath = resolve(ctx.scope, a.path);
245
+ const targetPath = resolve2(ctx.scope, a.path);
246
246
  if (!existsSync(targetPath)) {
247
247
  return {
248
248
  ok: false,
@@ -413,4 +413,4 @@ export {
413
413
  runConformanceCase
414
414
  };
415
415
  //# sourceMappingURL=index.js.map
416
- //# debugId=cc85d6cc-8968-5fbd-b7b7-eab3d298263d
416
+ //# debugId=bc1fc06c-0be1-5b35-9ac6-cedcc9367962
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // kernel/i18n/registry.texts.ts
2
2
 
3
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="e05c9678-1319-58b6-9a51-61818bac9305")}catch(e){}}();
3
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="27ef1dc5-0da7-5a76-9fa8-d447025bd0d4")}catch(e){}}();
4
4
  var REGISTRY_TEXTS = {
5
5
  duplicateExtension: "Extension already registered: {{kind}}:{{qualifiedId}}",
6
6
  unknownKind: "Unknown extension kind: {{kind}}",
@@ -102,7 +102,7 @@ import { Tiktoken as Tiktoken2 } from "js-tiktoken/lite";
102
102
  // package.json
103
103
  var package_default = {
104
104
  name: "@skill-map/cli",
105
- version: "0.64.1",
105
+ version: "0.66.0",
106
106
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
107
107
  license: "MIT",
108
108
  type: "module",
@@ -2349,24 +2349,31 @@ async function* walkContent(roots, options) {
2349
2349
  const filter = options.ignoreFilter ?? buildIgnoreFilter();
2350
2350
  const extensions = options.extensions;
2351
2351
  const sizeLimit = buildSizeLimit(options);
2352
+ const bodyField = options.bodyField;
2352
2353
  if (options.scopedPaths !== void 0) {
2353
- yield* walkScoped(roots, options.scopedPaths, extensions, sizeLimit, parser);
2354
+ yield* walkScoped(roots, options.scopedPaths, extensions, sizeLimit, parser, bodyField);
2354
2355
  return;
2355
2356
  }
2356
2357
  for (const root of roots) {
2357
2358
  for await (const entry of walkRoot(root, root, filter, extensions, sizeLimit)) {
2358
2359
  const relPath = relative2(root, entry.full).split(sep).join("/");
2359
- const rec = await traversedEntryToNode(entry, relPath, options.priorMtimes, parser);
2360
+ const rec = await traversedEntryToNode(
2361
+ entry,
2362
+ relPath,
2363
+ options.priorMtimes,
2364
+ parser,
2365
+ bodyField
2366
+ );
2360
2367
  if (rec !== null) yield rec;
2361
2368
  }
2362
2369
  }
2363
2370
  }
2364
- async function traversedEntryToNode(entry, relPath, priorMtimes, parser) {
2371
+ async function traversedEntryToNode(entry, relPath, priorMtimes, parser, bodyField) {
2365
2372
  const priorMtime = priorMtimes?.get(relPath);
2366
2373
  if (priorMtime !== void 0 && priorMtime === entry.modifiedAtMs) {
2367
- return buildUnchangedRecord(entry.full, relPath, entry.modifiedAtMs, parser);
2374
+ return buildUnchangedRecord(entry.full, relPath, entry.modifiedAtMs, parser, bodyField);
2368
2375
  }
2369
- const parsed = await readAndParse(entry.full, relPath, parser);
2376
+ const parsed = await readAndParse(entry.full, relPath, parser, bodyField);
2370
2377
  if (parsed === null) return null;
2371
2378
  return {
2372
2379
  path: relPath,
@@ -2383,7 +2390,7 @@ async function traversedEntryToNode(entry, relPath, priorMtimes, parser) {
2383
2390
  ...parsed.parseIssues ? { parseIssues: parsed.parseIssues } : {}
2384
2391
  };
2385
2392
  }
2386
- function buildUnchangedRecord(full, relPath, modifiedAtMs, parser) {
2393
+ function buildUnchangedRecord(full, relPath, modifiedAtMs, parser, bodyField) {
2387
2394
  return {
2388
2395
  path: relPath,
2389
2396
  body: "",
@@ -2392,26 +2399,26 @@ function buildUnchangedRecord(full, relPath, modifiedAtMs, parser) {
2392
2399
  modifiedAtMs,
2393
2400
  unchanged: true,
2394
2401
  reread: async () => {
2395
- const re = await readAndParse(full, relPath, parser);
2402
+ const re = await readAndParse(full, relPath, parser, bodyField);
2396
2403
  return re ?? { body: "", frontmatterRaw: "", frontmatter: {} };
2397
2404
  }
2398
2405
  };
2399
2406
  }
2400
- async function* walkScoped(roots, scopedPaths, extensions, sizeLimit, parser) {
2407
+ async function* walkScoped(roots, scopedPaths, extensions, sizeLimit, parser, bodyField) {
2401
2408
  const absRoots = roots.map((r) => isAbsolute2(r) ? r : resolve7(r));
2402
2409
  for (const scoped of scopedPaths) {
2403
- const rec = await scopedPathToNode(scoped, absRoots, extensions, sizeLimit, parser);
2410
+ const rec = await scopedPathToNode(scoped, absRoots, extensions, sizeLimit, parser, bodyField);
2404
2411
  if (rec !== null) yield rec;
2405
2412
  }
2406
2413
  }
2407
- async function scopedPathToNode(scoped, absRoots, extensions, sizeLimit, parser) {
2414
+ async function scopedPathToNode(scoped, absRoots, extensions, sizeLimit, parser, bodyField) {
2408
2415
  const full = isAbsolute2(scoped) ? scoped : resolve7(scoped);
2409
2416
  const relPath = relativeFromRoots(full, absRoots);
2410
2417
  if (relPath === null) return null;
2411
2418
  if (!hasMatchingExtension(full, extensions)) return null;
2412
2419
  const s = await statRegularFile(full, relPath, sizeLimit);
2413
2420
  if (s === null) return null;
2414
- const parsed = await readAndParse(full, relPath, parser);
2421
+ const parsed = await readAndParse(full, relPath, parser, bodyField);
2415
2422
  if (parsed === null) return null;
2416
2423
  return {
2417
2424
  path: relPath,
@@ -2444,7 +2451,7 @@ function relativeFromRoots(full, absRoots) {
2444
2451
  }
2445
2452
  return null;
2446
2453
  }
2447
- async function readAndParse(full, relPath, parser) {
2454
+ async function readAndParse(full, relPath, parser, bodyField) {
2448
2455
  let raw;
2449
2456
  try {
2450
2457
  raw = await readFile(full, "utf8");
@@ -2453,12 +2460,19 @@ async function readAndParse(full, relPath, parser) {
2453
2460
  }
2454
2461
  const parsed = parser.parse(raw, relPath);
2455
2462
  return {
2456
- body: parsed.body,
2463
+ body: resolveEffectiveBody(parsed.body, parsed.frontmatter, bodyField),
2457
2464
  frontmatterRaw: parsed.frontmatterRaw,
2458
2465
  frontmatter: parsed.frontmatter,
2459
2466
  ...parsed.issues && parsed.issues.length > 0 ? { parseIssues: parsed.issues } : {}
2460
2467
  };
2461
2468
  }
2469
+ function resolveEffectiveBody(parsedBody, frontmatter, bodyField) {
2470
+ if (bodyField !== void 0) {
2471
+ const candidate = frontmatter[bodyField];
2472
+ if (typeof candidate === "string") return candidate;
2473
+ }
2474
+ return parsedBody;
2475
+ }
2462
2476
  function buildSizeLimit(options) {
2463
2477
  const sizeLimit = {};
2464
2478
  if (options.maxFileSizeBytes !== void 0) {
@@ -2514,13 +2528,19 @@ function resolveProviderWalk(provider) {
2514
2528
  return walk2;
2515
2529
  }
2516
2530
  const read = provider.read ?? DEFAULT_READ_CONFIG;
2517
- return (roots, options) => walkContent(roots, buildWalkContentOptions(read, options));
2531
+ const rules = Array.isArray(read) ? read : [read];
2532
+ return async function* walkRules(roots, options) {
2533
+ for (const rule of rules) {
2534
+ yield* walkContent(roots, buildWalkContentOptions(rule, options));
2535
+ }
2536
+ };
2518
2537
  }
2519
2538
  function buildWalkContentOptions(read, options) {
2520
2539
  const walkOptions = {
2521
2540
  extensions: read.extensions,
2522
2541
  parser: read.parser
2523
2542
  };
2543
+ if (read.bodyField !== void 0) walkOptions.bodyField = read.bodyField;
2524
2544
  if (options) copyOptionalWalkOptions(walkOptions, options);
2525
2545
  return walkOptions;
2526
2546
  }
@@ -4130,4 +4150,4 @@ export {
4130
4150
  runScanWithRenames
4131
4151
  };
4132
4152
  //# sourceMappingURL=index.js.map
4133
- //# debugId=e05c9678-1319-58b6-9a51-61818bac9305
4153
+ //# debugId=27ef1dc5-0da7-5a76-9fa8-d447025bd0d4
@@ -2541,8 +2541,12 @@ type TProviderKindIcon = {
2541
2541
  interface IProviderUi {
2542
2542
  /**
2543
2543
  * Human-readable Provider name shown in the lens dropdown, the topbar
2544
- * lens chip, and the per-node provider chip (e.g. `'Claude'`,
2545
- * `'OpenAI Codex'`, `'Antigravity'`, `'Open Skills'`, `'Markdown'`).
2544
+ * lens chip, and the per-node provider chip. Vendor lenses use a
2545
+ * possessive `<Vendor>'s <product>` form (`"Anthropic's Claude"`,
2546
+ * `"OpenAI's Codex"`, `"Google's Antigravity"`); the vendor-neutral open
2547
+ * standard uses a `'Standard: <name>'` prefix (`'Standard: Agent skills'`).
2548
+ * The non-gated `'Markdown'` base keeps a label for internal lookups but
2549
+ * is never a selectable lens.
2546
2550
  */
2547
2551
  label: string;
2548
2552
  /** Base hex color (`#RRGGBB`) for the light-theme provider chip. */
@@ -2598,7 +2602,7 @@ interface IProviderScaffold {
2598
2602
  * Display-only hints naming the agents that consume this scaffold
2599
2603
  * territory, rendered in parentheses next to the Provider label in the
2600
2604
  * `sm tutorial` destination prompt (e.g. `.agents/skills` is read by
2601
- * Antigravity and OpenAI Codex). Purely presentational: NOT matched by
2605
+ * Google's Antigravity and OpenAI's Codex). Purely presentational: NOT matched by
2602
2606
  * `--for` (only registered Provider ids are) and has no runtime effect.
2603
2607
  */
2604
2608
  aka?: readonly string[];
@@ -2631,7 +2635,7 @@ interface IProvider extends IExtensionBase {
2631
2635
  * When present, the Provider is offered as a destination for newly
2632
2636
  * generated content (a skill folder dropped under `scaffold.skillDir`).
2633
2637
  * Absent means a materialising verb never offers this Provider, e.g.
2634
- * `openai` until Codex skills land, `antigravity` (skills live under
2638
+ * `codex` until Codex skills land, `antigravity` (skills live under
2635
2639
  * the open-standard `agent-skills` territory), `core/markdown` (owns
2636
2640
  * no authoring convention).
2637
2641
  */
@@ -2686,19 +2690,31 @@ interface IProvider extends IExtensionBase {
2686
2690
  * applies the default `{ extensions: ['.md'], parser: 'frontmatter-yaml' }`
2687
2691
  * so the most common Provider shape needs zero configuration.
2688
2692
  *
2693
+ * Either a SINGLE rule (the common case) or an ARRAY of rules. A
2694
+ * Provider that reads several file families with different parsers
2695
+ * declares an array, and `resolveProviderWalk` runs one walk pass per
2696
+ * rule (each filtering by its own `extensions`). The codex provider
2697
+ * uses this to read `.toml` sub-agents (`parser: 'toml'`,
2698
+ * `bodyField: 'developer_instructions'`) AND `.md` open-standard skills
2699
+ * (`parser: 'frontmatter-yaml'`) declaratively, without an escape-hatch
2700
+ * `walk()`. Rules SHOULD use disjoint extensions; overlaps are
2701
+ * tolerated because the orchestrator's first-wins `claimedPaths` dedup
2702
+ * drops a path already claimed on an earlier pass.
2703
+ *
2689
2704
  * Precedence: when both `walk()` (runtime field) and `read` are
2690
2705
  * declared, `walk()` wins, `read` is ignored. The escape-hatch
2691
- * relationship is intentional: most Providers should use `read`;
2692
- * Providers with non-standard discovery requirements (custom file
2693
- * naming, multi-pass walks, dynamic ignore logic) implement `walk()`
2694
- * directly and accept the duplication of audit-cleared defences.
2706
+ * relationship is intentional: most Providers should use `read` (single
2707
+ * or multi-rule); Providers with genuinely non-standard discovery
2708
+ * requirements (custom file naming, dynamic ignore logic) implement
2709
+ * `walk()` directly and accept the duplication of audit-cleared defences.
2695
2710
  *
2696
2711
  * Built-in parsers: `'frontmatter-yaml'` (markdown with `--- … ---`
2697
2712
  * YAML frontmatter; pollution-strip + JSON_SCHEMA-pinned), `'plain'`
2698
- * (entire body, empty frontmatter). The set is closed; user plugins
2699
- * cannot register their own.
2713
+ * (entire body, empty frontmatter), `'toml'` (whole-file TOML as
2714
+ * structured frontmatter). The set is closed; user plugins cannot
2715
+ * register their own.
2700
2716
  */
2701
- read?: IProviderReadConfig;
2717
+ read?: IProviderReadConfig | IProviderReadConfig[];
2702
2718
  /**
2703
2719
  * Walk the given roots and yield every node the Provider recognises.
2704
2720
  * Non-matching files are silently skipped. Unreadable files produce
@@ -2766,27 +2782,29 @@ interface IProvider extends IExtensionBase {
2766
2782
  */
2767
2783
  resolution?: Record<string, string[]>;
2768
2784
  /**
2769
- * Lens gating flag for vendor providers. When `true`, this Provider's
2785
+ * Lens gating flag. When `true`, this Provider is a LENS: its
2770
2786
  * `classify()` only runs (and the walker only iterates its territory)
2771
- * if `provider.id === activeProvider` (the project's active lens).
2772
- * When `false` or omitted (default), the Provider is universal and
2773
- * classifies unconditionally, regardless of the active lens.
2787
+ * if `provider.id === activeProvider` (the project's active lens), and
2788
+ * it is offered as a selectable lens (the BFF projects `isLens: true`
2789
+ * from this flag). When `false` or omitted (default), the Provider is a
2790
+ * non-gated universal BASE and classifies unconditionally.
2774
2791
  *
2775
- * Vendor providers (`claude`, `openai`, `antigravity`) MUST set this
2776
- * to `true`: the actual runtimes never read each other's on-disk
2777
- * formats (Claude Code does not consume `.codex/`; Codex CLI does not
2778
- * consume `.claude/`), and offering every file to every provider
2779
- * fabricates cross-vendor graph edges the runtimes themselves reject.
2792
+ * Vendor providers (`claude`, `codex`, `antigravity`) and the
2793
+ * open-standard `agent-skills` provider MUST set this `true`: the actual
2794
+ * runtimes never read each other's on-disk formats (Claude Code does not
2795
+ * consume `.codex/`; Codex CLI does not consume `.claude/`), and offering
2796
+ * every file to every provider fabricates cross-vendor graph edges the
2797
+ * runtimes themselves reject.
2780
2798
  *
2781
- * Universal providers (the open-standard `agent-skills`, the markdown
2782
- * fallback `core/markdown`, any future format-based fallback) keep
2783
- * this `false`: their territory is consumed by every vendor and they
2784
- * MUST run on every scan, regardless of the active lens.
2799
+ * Only the markdown fallback `core/markdown` (and any future
2800
+ * format-based fallback) keeps this `false`: the single non-gated base,
2801
+ * consumed by every lens and run on every scan. It is the substrate, NOT
2802
+ * a selectable lens (`isLens: false`).
2785
2803
  *
2786
- * There is no unlensed state: a project with no provider markers
2787
- * resolves to the universal `core/markdown` lens (id `markdown`),
2788
- * under which every gated Provider stays off (only the universal
2789
- * Providers run). The resolver never yields a null lens.
2804
+ * There is no unlensed state: a project with no vendor marker resolves
2805
+ * to the open-standard `agent-skills` default lens, under which the
2806
+ * open-standard classifier plus the universal base run. The resolver
2807
+ * never yields a null lens.
2790
2808
  *
2791
2809
  * Default `undefined` ≡ `false` ≡ universal. The field affects
2792
2810
  * classification ONLY; extractors continue to filter via their own
@@ -2932,6 +2950,23 @@ interface IProviderReadConfig {
2932
2950
  * the error into a Provider issue with status `invalid-manifest`.
2933
2951
  */
2934
2952
  parser: string;
2953
+ /**
2954
+ * Name of a parsed-frontmatter field that carries the node's markdown
2955
+ * body. When set, the walker feeds `frontmatter[bodyField]` (when it is
2956
+ * a string) to every downstream consumer as the node `body` instead of
2957
+ * the parser's own `body` output: the body hash, byte counts, and every
2958
+ * body-scoped extractor (markdown-link, at-directive, slash, ...) then
2959
+ * see this prose. For formats where the prompt lives inside structured
2960
+ * frontmatter rather than after a fence: OpenAI Codex sub-agents are
2961
+ * pure TOML (`read.parser: 'toml'`) whose markdown prompt is the
2962
+ * triple-quoted `developer_instructions` field, so the codex provider
2963
+ * declares `bodyField: 'developer_instructions'`. When the field is
2964
+ * absent or not a string,
2965
+ * the parser's own `body` is used unchanged (the default for `.md`
2966
+ * providers). The field stays in `frontmatter` too, so frontmatter-scoped
2967
+ * extractors (e.g. `core/mcp-tools` reading `tools`) are unaffected.
2968
+ */
2969
+ bodyField?: string;
2935
2970
  }
2936
2971
 
2937
2972
  /**
@@ -1,6 +1,6 @@
1
1
  // kernel/i18n/registry.texts.ts
2
2
 
3
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="264b9eb8-acaa-57eb-af6d-7127c5145395")}catch(e){}}();
3
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="ffec428d-8810-5e29-bc67-ad4ec917ce6f")}catch(e){}}();
4
4
  var REGISTRY_TEXTS = {
5
5
  duplicateExtension: "Extension already registered: {{kind}}:{{qualifiedId}}",
6
6
  unknownKind: "Unknown extension kind: {{kind}}",
@@ -102,7 +102,7 @@ import { Tiktoken as Tiktoken2 } from "js-tiktoken/lite";
102
102
  // package.json
103
103
  var package_default = {
104
104
  name: "@skill-map/cli",
105
- version: "0.64.1",
105
+ version: "0.66.0",
106
106
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
107
107
  license: "MIT",
108
108
  type: "module",
@@ -2349,24 +2349,31 @@ async function* walkContent(roots, options) {
2349
2349
  const filter = options.ignoreFilter ?? buildIgnoreFilter();
2350
2350
  const extensions = options.extensions;
2351
2351
  const sizeLimit = buildSizeLimit(options);
2352
+ const bodyField = options.bodyField;
2352
2353
  if (options.scopedPaths !== void 0) {
2353
- yield* walkScoped(roots, options.scopedPaths, extensions, sizeLimit, parser);
2354
+ yield* walkScoped(roots, options.scopedPaths, extensions, sizeLimit, parser, bodyField);
2354
2355
  return;
2355
2356
  }
2356
2357
  for (const root of roots) {
2357
2358
  for await (const entry of walkRoot(root, root, filter, extensions, sizeLimit)) {
2358
2359
  const relPath = relative2(root, entry.full).split(sep).join("/");
2359
- const rec = await traversedEntryToNode(entry, relPath, options.priorMtimes, parser);
2360
+ const rec = await traversedEntryToNode(
2361
+ entry,
2362
+ relPath,
2363
+ options.priorMtimes,
2364
+ parser,
2365
+ bodyField
2366
+ );
2360
2367
  if (rec !== null) yield rec;
2361
2368
  }
2362
2369
  }
2363
2370
  }
2364
- async function traversedEntryToNode(entry, relPath, priorMtimes, parser) {
2371
+ async function traversedEntryToNode(entry, relPath, priorMtimes, parser, bodyField) {
2365
2372
  const priorMtime = priorMtimes?.get(relPath);
2366
2373
  if (priorMtime !== void 0 && priorMtime === entry.modifiedAtMs) {
2367
- return buildUnchangedRecord(entry.full, relPath, entry.modifiedAtMs, parser);
2374
+ return buildUnchangedRecord(entry.full, relPath, entry.modifiedAtMs, parser, bodyField);
2368
2375
  }
2369
- const parsed = await readAndParse(entry.full, relPath, parser);
2376
+ const parsed = await readAndParse(entry.full, relPath, parser, bodyField);
2370
2377
  if (parsed === null) return null;
2371
2378
  return {
2372
2379
  path: relPath,
@@ -2383,7 +2390,7 @@ async function traversedEntryToNode(entry, relPath, priorMtimes, parser) {
2383
2390
  ...parsed.parseIssues ? { parseIssues: parsed.parseIssues } : {}
2384
2391
  };
2385
2392
  }
2386
- function buildUnchangedRecord(full, relPath, modifiedAtMs, parser) {
2393
+ function buildUnchangedRecord(full, relPath, modifiedAtMs, parser, bodyField) {
2387
2394
  return {
2388
2395
  path: relPath,
2389
2396
  body: "",
@@ -2392,26 +2399,26 @@ function buildUnchangedRecord(full, relPath, modifiedAtMs, parser) {
2392
2399
  modifiedAtMs,
2393
2400
  unchanged: true,
2394
2401
  reread: async () => {
2395
- const re = await readAndParse(full, relPath, parser);
2402
+ const re = await readAndParse(full, relPath, parser, bodyField);
2396
2403
  return re ?? { body: "", frontmatterRaw: "", frontmatter: {} };
2397
2404
  }
2398
2405
  };
2399
2406
  }
2400
- async function* walkScoped(roots, scopedPaths, extensions, sizeLimit, parser) {
2407
+ async function* walkScoped(roots, scopedPaths, extensions, sizeLimit, parser, bodyField) {
2401
2408
  const absRoots = roots.map((r) => isAbsolute2(r) ? r : resolve7(r));
2402
2409
  for (const scoped of scopedPaths) {
2403
- const rec = await scopedPathToNode(scoped, absRoots, extensions, sizeLimit, parser);
2410
+ const rec = await scopedPathToNode(scoped, absRoots, extensions, sizeLimit, parser, bodyField);
2404
2411
  if (rec !== null) yield rec;
2405
2412
  }
2406
2413
  }
2407
- async function scopedPathToNode(scoped, absRoots, extensions, sizeLimit, parser) {
2414
+ async function scopedPathToNode(scoped, absRoots, extensions, sizeLimit, parser, bodyField) {
2408
2415
  const full = isAbsolute2(scoped) ? scoped : resolve7(scoped);
2409
2416
  const relPath = relativeFromRoots(full, absRoots);
2410
2417
  if (relPath === null) return null;
2411
2418
  if (!hasMatchingExtension(full, extensions)) return null;
2412
2419
  const s = await statRegularFile(full, relPath, sizeLimit);
2413
2420
  if (s === null) return null;
2414
- const parsed = await readAndParse(full, relPath, parser);
2421
+ const parsed = await readAndParse(full, relPath, parser, bodyField);
2415
2422
  if (parsed === null) return null;
2416
2423
  return {
2417
2424
  path: relPath,
@@ -2444,7 +2451,7 @@ function relativeFromRoots(full, absRoots) {
2444
2451
  }
2445
2452
  return null;
2446
2453
  }
2447
- async function readAndParse(full, relPath, parser) {
2454
+ async function readAndParse(full, relPath, parser, bodyField) {
2448
2455
  let raw;
2449
2456
  try {
2450
2457
  raw = await readFile(full, "utf8");
@@ -2453,12 +2460,19 @@ async function readAndParse(full, relPath, parser) {
2453
2460
  }
2454
2461
  const parsed = parser.parse(raw, relPath);
2455
2462
  return {
2456
- body: parsed.body,
2463
+ body: resolveEffectiveBody(parsed.body, parsed.frontmatter, bodyField),
2457
2464
  frontmatterRaw: parsed.frontmatterRaw,
2458
2465
  frontmatter: parsed.frontmatter,
2459
2466
  ...parsed.issues && parsed.issues.length > 0 ? { parseIssues: parsed.issues } : {}
2460
2467
  };
2461
2468
  }
2469
+ function resolveEffectiveBody(parsedBody, frontmatter, bodyField) {
2470
+ if (bodyField !== void 0) {
2471
+ const candidate = frontmatter[bodyField];
2472
+ if (typeof candidate === "string") return candidate;
2473
+ }
2474
+ return parsedBody;
2475
+ }
2462
2476
  function buildSizeLimit(options) {
2463
2477
  const sizeLimit = {};
2464
2478
  if (options.maxFileSizeBytes !== void 0) {
@@ -2514,13 +2528,19 @@ function resolveProviderWalk(provider) {
2514
2528
  return walk2;
2515
2529
  }
2516
2530
  const read = provider.read ?? DEFAULT_READ_CONFIG;
2517
- return (roots, options) => walkContent(roots, buildWalkContentOptions(read, options));
2531
+ const rules = Array.isArray(read) ? read : [read];
2532
+ return async function* walkRules(roots, options) {
2533
+ for (const rule of rules) {
2534
+ yield* walkContent(roots, buildWalkContentOptions(rule, options));
2535
+ }
2536
+ };
2518
2537
  }
2519
2538
  function buildWalkContentOptions(read, options) {
2520
2539
  const walkOptions = {
2521
2540
  extensions: read.extensions,
2522
2541
  parser: read.parser
2523
2542
  };
2543
+ if (read.bodyField !== void 0) walkOptions.bodyField = read.bodyField;
2524
2544
  if (options) copyOptionalWalkOptions(walkOptions, options);
2525
2545
  return walkOptions;
2526
2546
  }
@@ -4130,4 +4150,4 @@ export {
4130
4150
  runScanWithRenames
4131
4151
  };
4132
4152
  //# sourceMappingURL=index.js.map
4133
- //# debugId=264b9eb8-acaa-57eb-af6d-7127c5145395
4153
+ //# debugId=ffec428d-8810-5e29-bc67-ad4ec917ce6f