/** * The one frontmatter reader. * * A `SKILL.md`, a command file, and anything else this kernel reads from a * markdown file with a `---` fence all come through here. There were three * readers before this one and two of them disagreed on the same input: this * one **threw** on malformed frontmatter, and a second **silently returned no * metadata**, so the same file was a hard error in one code path and a skill * named after its own directory with "(no description)" in the other. Refuse * versus degrade, on one file shape, is the divergence this module exists to * end — see "refuse do not degrade". * * **Deliberately not a YAML parser.** It is a flat key/value splitter with one * level of nesting, and it refuses the constructs in {@link UNSUPPORTED_YAML} * rather than mangling them. That refusal is the design: a reader that * half-understands YAML produces a value that passes validation and means * nothing. * * That refusal is now total for lists. A *block* sequence * * ```yaml * allowed-tools: * - Read * ``` * * used to be silently dropped — its lines carry no `:` and were skipped, so the * key came back **absent** — while the flow form `[Read, Grep]` threw. One * spelling of a list was a hard error and the other was silence, and the silent * one is the shape an author actually writes, because the block form is the * natural YAML for a list. * * It matters most for a key like `allowed-tools`: a skill that asked for `Bash` * and silently did not get it is indistinguishable from one that never asked, * which is a capability quietly not granted rather than a formatting nicety. * Both readers this replaced behaved that way, so it was inherited rather than * introduced; it is now refused, naming the key. * * **Vocabulary belongs to the caller.** This returns the parsed map; it does * not know what a skill needs or what a command needs, and it validates no * field names. Widening one caller's metadata type to cover another's is how a * skill-shaped API comes to mean something it does not. */ /** * What one frontmatter key holds: a scalar, or a block of indented pairs. * * A discriminated union rather than two parallel maps, because the source * format cannot express both at once. The first shape of this type had * `data: Record` beside `blocks: Record>`, * which let one key sit in both — a state no YAML file can produce. Every * caller would then have had to decide a precedence for a case that cannot * arrive, and the ones who did not would be carrying a latent bug against a * shape that told them the case existed. Removing the state beats documenting * it. */ export type FrontmatterValue = { readonly kind: 'scalar'; readonly value: string; } | { readonly kind: 'mapping'; readonly entries: Readonly>; }; export interface ParsedFrontmatter { /** * Every top-level key, in the order the file declared it. * * A key whose value is empty and which has no indented lines under it is * absent: it declared nothing. Narrow on `kind` to read it — * * ```ts * const d = values.description * if (d?.kind !== 'scalar') throw new Error('description must be a scalar') * use(d.value) * ``` */ readonly values: Readonly>; /** Everything after the closing fence, trimmed. */ readonly body: string; } /** * Parse a markdown file's `---` frontmatter. * * @param raw The file's full contents. LF and CRLF both parse. * @param source A label for error messages — a path, or a phrase naming the * file. Used verbatim, so the caller controls how its own errors read. * @throws If the frontmatter is absent, unclosed, or uses YAML this reader * does not implement. It never returns a partial or empty result to stand in * for a file it could not read. */ export declare function parseFrontmatter(raw: string, source: string): ParsedFrontmatter; //# sourceMappingURL=frontmatter.d.ts.map