diff --git a/src/oc-path/tests/jsonl/resolve.test.ts b/src/oc-path/tests/jsonl/resolve.test.ts index 9fccd944bf6..c24d7511db2 100644 --- a/src/oc-path/tests/jsonl/resolve.test.ts +++ b/src/oc-path/tests/jsonl/resolve.test.ts @@ -1,9 +1,9 @@ -import { describe, expect, it } from 'vitest'; -import { parseJsonl } from '../../jsonl/parse.js'; -import { resolveJsonlOcPath } from '../../jsonl/resolve.js'; -import { parseOcPath } from '../../oc-path.js'; -import { resolveOcPath } from '../../universal.js'; -import { findOcPaths } from '../../find.js'; +import { describe, expect, it } from "vitest"; +import { findOcPaths } from "../../find.js"; +import { parseJsonl } from "../../jsonl/parse.js"; +import { resolveJsonlOcPath } from "../../jsonl/resolve.js"; +import { parseOcPath } from "../../oc-path.js"; +import { resolveOcPath } from "../../universal.js"; const log = `{"event":"start","ts":1} {"event":"step","n":1,"result":{"ok":true,"detail":"a"}} @@ -16,51 +16,51 @@ function rs(ocPath: string) { return resolveJsonlOcPath(ast, parseOcPath(ocPath)); } -describe('resolveJsonlOcPath', () => { - it('returns root when no segments are given', () => { - expect(rs('oc://session-events')?.kind).toBe('root'); +describe("resolveJsonlOcPath", () => { + it("returns root when no segments are given", () => { + expect(rs("oc://session-events")?.kind).toBe("root"); }); - it('addresses an entire line by line number', () => { - const m = rs('oc://session-events/L1'); - expect(m?.kind).toBe('line'); + it("addresses an entire line by line number", () => { + const m = rs("oc://session-events/L1"); + expect(m?.kind).toBe("line"); }); - it('addresses fields under a line via item segment', () => { - const m = rs('oc://session-events/L2/event'); - expect(m?.kind).toBe('object-entry'); - if (m?.kind === 'object-entry') { - expect(m.node.value).toMatchObject({ kind: 'string', value: 'step' }); + it("addresses fields under a line via item segment", () => { + const m = rs("oc://session-events/L2/event"); + expect(m?.kind).toBe("object-entry"); + if (m?.kind === "object-entry") { + expect(m.node.value).toMatchObject({ kind: "string", value: "step" }); } }); - it('descends via dotted item paths', () => { - const m = rs('oc://session-events/L2/result.ok'); - expect(m?.kind).toBe('object-entry'); - if (m?.kind === 'object-entry') { - expect(m.node.value).toMatchObject({ kind: 'boolean', value: true }); + it("descends via dotted item paths", () => { + const m = rs("oc://session-events/L2/result.ok"); + expect(m?.kind).toBe("object-entry"); + if (m?.kind === "object-entry") { + expect(m.node.value).toMatchObject({ kind: "boolean", value: true }); } }); - it('resolves $last to the most recent value line', () => { - const m = rs('oc://session-events/$last/event'); - expect(m?.kind).toBe('object-entry'); - if (m?.kind === 'object-entry') { - expect(m.node.value).toMatchObject({ kind: 'string', value: 'end' }); + it("resolves $last to the most recent value line", () => { + const m = rs("oc://session-events/$last/event"); + expect(m?.kind).toBe("object-entry"); + if (m?.kind === "object-entry") { + expect(m.node.value).toMatchObject({ kind: "string", value: "end" }); } }); - it('returns null for unknown line addresses', () => { - expect(rs('oc://session-events/L99')).toBeNull(); - expect(rs('oc://session-events/garbage')).toBeNull(); + it("returns null for unknown line addresses", () => { + expect(rs("oc://session-events/L99")).toBeNull(); + expect(rs("oc://session-events/garbage")).toBeNull(); }); - it('returns null when descending into a blank line', () => { - expect(rs('oc://session-events/L3/anything')).toBeNull(); + it("returns null when descending into a blank line", () => { + expect(rs("oc://session-events/L3/anything")).toBeNull(); }); }); -describe('resolveJsonlToUniversal — file-relative line metadata (regression)', () => { +describe("resolveJsonlToUniversal — file-relative line metadata (regression)", () => { // Regression: surfaced via the openclaw-path CLI scenario run on // a multi-line session.jsonl. Every match returned `line: 1` // because the inside-line jsonc parser numbers from 1 within each @@ -68,30 +68,28 @@ describe('resolveJsonlToUniversal — file-relative line metadata (regression)', // number over the JsonlLine's file-relative line. const log = [ - '{"event":"start"}', // line 1 - '{"event":"step","n":1}', // line 2 - '{"event":"step","n":2}', // line 3 - '{"event":"end"}', // line 4 - '', // line 5 (blank) - ].join('\n'); + '{"event":"start"}', // line 1 + '{"event":"step","n":1}', // line 2 + '{"event":"step","n":2}', // line 3 + '{"event":"end"}', // line 4 + "", // line 5 (blank) + ].join("\n"); - it('resolves L2/event with line=2 (not 1)', () => { + it("resolves L2/event with line=2 (not 1)", () => { const { ast } = parseJsonl(log); - const m = resolveOcPath(ast, parseOcPath('oc://session.jsonl/L2/event')); - expect(m).not.toBeNull(); - if (m !== null) {expect(m.line).toBe(2);} + const m = resolveOcPath(ast, parseOcPath("oc://session.jsonl/L2/event")); + expect(m).toEqual(expect.objectContaining({ line: 2 })); }); - it('resolves L4/event with line=4', () => { + it("resolves L4/event with line=4", () => { const { ast } = parseJsonl(log); - const m = resolveOcPath(ast, parseOcPath('oc://session.jsonl/L4/event')); - expect(m).not.toBeNull(); - if (m !== null) {expect(m.line).toBe(4);} + const m = resolveOcPath(ast, parseOcPath("oc://session.jsonl/L4/event")); + expect(m).toEqual(expect.objectContaining({ line: 4 })); }); - it('findOcPaths over wildcard surfaces correct file-relative lines', () => { + it("findOcPaths over wildcard surfaces correct file-relative lines", () => { const { ast } = parseJsonl(log); - const matches = findOcPaths(ast, parseOcPath('oc://session.jsonl/*/event')); + const matches = findOcPaths(ast, parseOcPath("oc://session.jsonl/*/event")); expect(matches).toHaveLength(4); const lines = matches.map((m) => m.match.line); expect(lines).toEqual([1, 2, 3, 4]); diff --git a/src/oc-path/tests/scenarios/cross-cutting.test.ts b/src/oc-path/tests/scenarios/cross-cutting.test.ts index ab8ab5a93c7..a53f489ed2b 100644 --- a/src/oc-path/tests/scenarios/cross-cutting.test.ts +++ b/src/oc-path/tests/scenarios/cross-cutting.test.ts @@ -5,11 +5,11 @@ * across re-parses. OcPath round-trip via the AST (slugs in OcPath * must round-trip back to the resolved node). */ -import { describe, expect, it } from 'vitest'; -import { emitMd } from '../../emit.js'; -import { formatOcPath, parseOcPath } from '../../oc-path.js'; -import { parseMd } from '../../parse.js'; -import { resolveMdOcPath as resolveOcPath } from '../../resolve.js'; +import { describe, expect, it } from "vitest"; +import { emitMd } from "../../emit.js"; +import { formatOcPath, parseOcPath } from "../../oc-path.js"; +import { parseMd } from "../../parse.js"; +import { resolveMdOcPath as resolveOcPath } from "../../resolve.js"; const SAMPLE = `--- name: github @@ -29,60 +29,60 @@ Preamble. - curl: HTTP client `; -describe('wave-13 cross-cutting', () => { - it('CC-01 parse → resolve → emit pipeline (block)', () => { +describe("wave-13 cross-cutting", () => { + it("CC-01 parse → resolve → emit pipeline (block)", () => { const { ast } = parseMd(SAMPLE); - const m = resolveOcPath(ast, { file: 'AGENTS.md', section: 'boundaries' }); - expect(m?.kind).toBe('block'); + const m = resolveOcPath(ast, { file: "AGENTS.md", section: "boundaries" }); + expect(m?.kind).toBe("block"); expect(emitMd(ast)).toBe(SAMPLE); }); - it('CC-02 OcPath round-trip via AST: parse + resolve + format', () => { + it("CC-02 OcPath round-trip via AST: parse + resolve + format", () => { const { ast } = parseMd(SAMPLE); for (const block of ast.blocks) { const path = parseOcPath(`oc://AGENTS.md/${block.slug}`); const m = resolveOcPath(ast, path); - expect(m?.kind, `block ${block.slug} should resolve`).toBe('block'); + expect(m?.kind, `block ${block.slug} should resolve`).toBe("block"); // Format the same path back; slug → URI shape should be stable. expect(formatOcPath(path)).toBe(`oc://AGENTS.md/${block.slug}`); } }); - it('CC-03 every item in every block is OcPath-addressable', () => { + it("CC-03 every item in every block is OcPath-addressable", () => { const { ast } = parseMd(SAMPLE); for (const block of ast.blocks) { for (const item of block.items) { const path = parseOcPath(`oc://AGENTS.md/${block.slug}/${item.slug}`); const m = resolveOcPath(ast, path); - expect(m?.kind, `${block.slug}/${item.slug} should resolve`).toBe('item'); + expect(m?.kind, `${block.slug}/${item.slug} should resolve`).toBe("item"); } } }); - it('CC-04 every kv item field is OcPath-addressable', () => { + it("CC-04 every kv item field is OcPath-addressable", () => { const { ast } = parseMd(SAMPLE); for (const block of ast.blocks) { for (const item of block.items) { - if (!item.kv) {continue;} - const path = parseOcPath( - `oc://AGENTS.md/${block.slug}/${item.slug}/${item.kv.key}`, - ); + if (!item.kv) { + continue; + } + const path = parseOcPath(`oc://AGENTS.md/${block.slug}/${item.slug}/${item.kv.key}`); const m = resolveOcPath(ast, path); - expect(m?.kind).toBe('item-field'); + expect(m?.kind).toBe("item-field"); } } }); - it('CC-05 every frontmatter entry is OcPath-addressable', () => { + it("CC-05 every frontmatter entry is OcPath-addressable", () => { const { ast } = parseMd(SAMPLE); for (const fm of ast.frontmatter) { const path = parseOcPath(`oc://AGENTS.md/[frontmatter]/${fm.key}`); const m = resolveOcPath(ast, path); - expect(m?.kind).toBe('frontmatter'); + expect(m?.kind).toBe("frontmatter"); } }); - it('CC-06 slugs are stable across re-parses (deterministic)', () => { + it("CC-06 slugs are stable across re-parses (deterministic)", () => { const a1 = parseMd(SAMPLE).ast; const a2 = parseMd(SAMPLE).ast; expect(a1.blocks.map((b) => b.slug)).toEqual(a2.blocks.map((b) => b.slug)); @@ -91,49 +91,49 @@ describe('wave-13 cross-cutting', () => { ); }); - it('CC-07 modifying raw + re-parse produces consistent AST shape', () => { + it("CC-07 modifying raw + re-parse produces consistent AST shape", () => { const a1 = parseMd(SAMPLE).ast; - const modified = SAMPLE.replace('GitHub CLI', 'GitHub command-line interface'); + const modified = SAMPLE.replace("GitHub CLI", "GitHub command-line interface"); const a2 = parseMd(modified).ast; // Block + item count + slugs unchanged. expect(a2.blocks.length).toBe(a1.blocks.length); - const a1Tools = a1.blocks.find((b) => b.slug === 'tools'); - const a2Tools = a2.blocks.find((b) => b.slug === 'tools'); + const a1Tools = a1.blocks.find((b) => b.slug === "tools"); + const a2Tools = a2.blocks.find((b) => b.slug === "tools"); expect(a2Tools?.items.length).toBe(a1Tools?.items.length); // KV value reflects the change. - const ghItem = a2Tools?.items.find((i) => i.kv?.key === 'gh'); - expect(ghItem?.kv?.value).toBe('GitHub command-line interface'); + const ghItem = a2Tools?.items.find((i) => i.kv?.key === "gh"); + expect(ghItem?.kv?.value).toBe("GitHub command-line interface"); }); - it('CC-08 unknown OcPath returns null without affecting subsequent valid resolves', () => { + it("CC-08 unknown OcPath returns null without affecting subsequent valid resolves", () => { const { ast } = parseMd(SAMPLE); - expect(resolveOcPath(ast, { file: 'X.md', section: 'nonexistent' })).toBeNull(); - expect(resolveOcPath(ast, { file: 'X.md', section: 'tools' })?.kind).toBe('block'); + expect(resolveOcPath(ast, { file: "X.md", section: "nonexistent" })).toBeNull(); + expect(resolveOcPath(ast, { file: "X.md", section: "tools" })?.kind).toBe("block"); }); - it('CC-09 resolve does not depend on file segment matching', () => { + it("CC-09 resolve does not depend on file segment matching", () => { const { ast } = parseMd(SAMPLE); - const a = resolveOcPath(ast, { file: 'A.md', section: 'tools' }); - const b = resolveOcPath(ast, { file: 'B.md', section: 'tools' }); + const a = resolveOcPath(ast, { file: "A.md", section: "tools" }); + const b = resolveOcPath(ast, { file: "B.md", section: "tools" }); expect(a?.kind).toBe(b?.kind); }); - it('CC-10 round-trip across all 9 valid OcPath shapes', () => { + it("CC-10 round-trip across all 9 valid OcPath shapes", () => { const { ast } = parseMd(SAMPLE); const cases = [ - { file: 'X.md' }, - { file: 'X.md', section: 'tools' }, - { file: 'X.md', section: 'tools', item: 'gh' }, - { file: 'X.md', section: 'tools', item: 'gh', field: 'gh' }, - { file: 'X.md', section: '[frontmatter]', field: 'name' }, - { file: 'X.md', section: 'boundaries' }, - { file: 'X.md', section: 'boundaries', item: 'never-write-to-etc' }, - { file: 'X.md', section: 'boundaries', item: 'always-confirm' }, - { file: 'X.md', section: '[frontmatter]', field: 'description' }, + { file: "X.md" }, + { file: "X.md", section: "tools" }, + { file: "X.md", section: "tools", item: "gh" }, + { file: "X.md", section: "tools", item: "gh", field: "gh" }, + { file: "X.md", section: "[frontmatter]", field: "name" }, + { file: "X.md", section: "boundaries" }, + { file: "X.md", section: "boundaries", item: "never-write-to-etc" }, + { file: "X.md", section: "boundaries", item: "always-confirm" }, + { file: "X.md", section: "[frontmatter]", field: "description" }, ]; for (const path of cases) { const m = resolveOcPath(ast, path); - expect(m, `failed for ${JSON.stringify(path)}`).not.toBeNull(); + expect(m, `failed for ${JSON.stringify(path)}`).toEqual(expect.any(Object)); } }); }); diff --git a/src/oc-path/tests/scenarios/jsonc-byte-fidelity.test.ts b/src/oc-path/tests/scenarios/jsonc-byte-fidelity.test.ts index dd47ebd80d9..f81b5691989 100644 --- a/src/oc-path/tests/scenarios/jsonc-byte-fidelity.test.ts +++ b/src/oc-path/tests/scenarios/jsonc-byte-fidelity.test.ts @@ -34,8 +34,11 @@ function rt(raw: string): string { */ function assertParseable(raw: string): JsoncValue { const result = parseJsonc(raw); - expect(result.ast.root).not.toBeNull(); - return result.ast.root as JsoncValue; + expect(result.ast.root).toEqual(expect.any(Object)); + if (result.ast.root === null) { + throw new Error("Expected parseable JSONC root"); + } + return result.ast.root; } /** diff --git a/src/oc-path/tests/scenarios/oc-path-resolver-edges.test.ts b/src/oc-path/tests/scenarios/oc-path-resolver-edges.test.ts index 1f0381a8e6c..6cf5b8e1f3e 100644 --- a/src/oc-path/tests/scenarios/oc-path-resolver-edges.test.ts +++ b/src/oc-path/tests/scenarios/oc-path-resolver-edges.test.ts @@ -6,9 +6,9 @@ * item returns `null` (not a guess). Frontmatter via the `[frontmatter]` * sentinel section. */ -import { describe, expect, it } from 'vitest'; -import { parseMd } from '../../parse.js'; -import { resolveMdOcPath as resolveOcPath } from '../../resolve.js'; +import { describe, expect, it } from "vitest"; +import { parseMd } from "../../parse.js"; +import { resolveMdOcPath as resolveOcPath } from "../../resolve.js"; const SAMPLE = `--- name: github @@ -34,37 +34,39 @@ Preamble prose. - item one `; -describe('wave-08 oc-path-resolver-edges', () => { +describe("wave-08 oc-path-resolver-edges", () => { const { ast } = parseMd(SAMPLE); - it('R-01 root resolves to AST', () => { - const m = resolveOcPath(ast, { file: 'X.md' }); - expect(m?.kind).toBe('root'); + it("R-01 root resolves to AST", () => { + const m = resolveOcPath(ast, { file: "X.md" }); + expect(m?.kind).toBe("root"); }); - it('R-02 block by exact slug', () => { - const m = resolveOcPath(ast, { file: 'X.md', section: 'boundaries' }); - expect(m?.kind).toBe('block'); + it("R-02 block by exact slug", () => { + const m = resolveOcPath(ast, { file: "X.md", section: "boundaries" }); + expect(m?.kind).toBe("block"); }); - it('R-03 block by case-mismatched slug (Boundaries → boundaries)', () => { - const m = resolveOcPath(ast, { file: 'X.md', section: 'Boundaries' }); - expect(m?.kind).toBe('block'); + it("R-03 block by case-mismatched slug (Boundaries → boundaries)", () => { + const m = resolveOcPath(ast, { file: "X.md", section: "Boundaries" }); + expect(m?.kind).toBe("block"); }); - it('R-04 block by uppercased slug', () => { - const m = resolveOcPath(ast, { file: 'X.md', section: 'BOUNDARIES' }); - expect(m?.kind).toBe('block'); + it("R-04 block by uppercased slug", () => { + const m = resolveOcPath(ast, { file: "X.md", section: "BOUNDARIES" }); + expect(m?.kind).toBe("block"); }); - it('R-05 multi-word section by slug', () => { - const m = resolveOcPath(ast, { file: 'X.md', section: 'multi-word-section' }); - expect(m?.kind).toBe('block'); - if (m?.kind === 'block') {expect(m.node.heading).toBe('Multi-Word Section');} + it("R-05 multi-word section by slug", () => { + const m = resolveOcPath(ast, { file: "X.md", section: "multi-word-section" }); + expect(m?.kind).toBe("block"); + if (m?.kind === "block") { + expect(m.node.heading).toBe("Multi-Word Section"); + } }); - it('R-06 multi-word section by exact heading text (case-folded)', () => { - const m = resolveOcPath(ast, { file: 'X.md', section: 'Multi-Word Section' }); + it("R-06 multi-word section by exact heading text (case-folded)", () => { + const m = resolveOcPath(ast, { file: "X.md", section: "Multi-Word Section" }); // The OcPath section is matched case-insensitively against block.slug. // Block.slug for "Multi-Word Section" is "multi-word-section", and // path.section.toLowerCase() = "multi-word section" which does NOT @@ -73,163 +75,172 @@ describe('wave-08 oc-path-resolver-edges', () => { expect(m).toBeNull(); }); - it('R-07 unknown section returns null', () => { - const m = resolveOcPath(ast, { file: 'X.md', section: 'unknown' }); + it("R-07 unknown section returns null", () => { + const m = resolveOcPath(ast, { file: "X.md", section: "unknown" }); expect(m).toBeNull(); }); - it('R-08 item by slug under known section', () => { + it("R-08 item by slug under known section", () => { const m = resolveOcPath(ast, { - file: 'X.md', - section: 'tools', - item: 'gh', + file: "X.md", + section: "tools", + item: "gh", }); - expect(m?.kind).toBe('item'); + expect(m?.kind).toBe("item"); }); it('R-09 item slug for KV uses kv.key (gh, not "gh-github-cli")', () => { const m = resolveOcPath(ast, { - file: 'X.md', - section: 'tools', - item: 'gh', + file: "X.md", + section: "tools", + item: "gh", }); - expect(m).not.toBeNull(); - if (m?.kind === 'item') {expect(m.node.kv?.value).toBe('GitHub CLI');} + expect(m).toEqual(expect.objectContaining({ kind: "item" })); + if (m?.kind !== "item") { + throw new Error("Expected item match for gh"); + } + expect(m.node.kv?.value).toBe("GitHub CLI"); }); - it('R-10 item slug for plain bullet uses text', () => { + it("R-10 item slug for plain bullet uses text", () => { const m = resolveOcPath(ast, { - file: 'X.md', - section: 'boundaries', - item: 'never-write-to-etc', + file: "X.md", + section: "boundaries", + item: "never-write-to-etc", }); - expect(m?.kind).toBe('item'); + expect(m?.kind).toBe("item"); }); - it('R-11 item slug case-insensitive', () => { + it("R-11 item slug case-insensitive", () => { const m = resolveOcPath(ast, { - file: 'X.md', - section: 'tools', - item: 'GH', + file: "X.md", + section: "tools", + item: "GH", }); - expect(m?.kind).toBe('item'); + expect(m?.kind).toBe("item"); }); - it('R-12 item with spaces in key (slugified)', () => { + it("R-12 item with spaces in key (slugified)", () => { const m = resolveOcPath(ast, { - file: 'X.md', - section: 'tools', - item: 'the-tool', + file: "X.md", + section: "tools", + item: "the-tool", }); - expect(m?.kind).toBe('item'); - if (m?.kind === 'item') {expect(m.node.kv?.value).toBe('with caps and spaces');} + expect(m?.kind).toBe("item"); + if (m?.kind === "item") { + expect(m.node.kv?.value).toBe("with caps and spaces"); + } }); - it('R-13 unknown item returns null', () => { + it("R-13 unknown item returns null", () => { const m = resolveOcPath(ast, { - file: 'X.md', - section: 'tools', - item: 'nonexistent', + file: "X.md", + section: "tools", + item: "nonexistent", }); expect(m).toBeNull(); }); - it('R-14 item-field matches kv.key (case-insensitive)', () => { + it("R-14 item-field matches kv.key (case-insensitive)", () => { const m = resolveOcPath(ast, { - file: 'X.md', - section: 'tools', - item: 'gh', - field: 'gh', + file: "X.md", + section: "tools", + item: "gh", + field: "gh", }); - expect(m?.kind).toBe('item-field'); + expect(m?.kind).toBe("item-field"); }); - it('R-15 field on plain (non-kv) item returns null', () => { + it("R-15 field on plain (non-kv) item returns null", () => { const m = resolveOcPath(ast, { - file: 'X.md', - section: 'boundaries', - item: 'never-write-to-etc', - field: 'risk', + file: "X.md", + section: "boundaries", + item: "never-write-to-etc", + field: "risk", }); expect(m).toBeNull(); }); - it('R-16 field that does not match kv.key returns null', () => { + it("R-16 field that does not match kv.key returns null", () => { const m = resolveOcPath(ast, { - file: 'X.md', - section: 'tools', - item: 'gh', - field: 'nonexistent', + file: "X.md", + section: "tools", + item: "gh", + field: "nonexistent", }); expect(m).toBeNull(); }); - it('R-17 frontmatter via [frontmatter] sentinel section', () => { + it("R-17 frontmatter via [frontmatter] sentinel section", () => { const m = resolveOcPath(ast, { - file: 'X.md', - section: '[frontmatter]', - field: 'name', + file: "X.md", + section: "[frontmatter]", + field: "name", }); - expect(m?.kind).toBe('frontmatter'); - if (m?.kind === 'frontmatter') {expect(m.node.value).toBe('github');} + expect(m?.kind).toBe("frontmatter"); + if (m?.kind === "frontmatter") { + expect(m.node.value).toBe("github"); + } }); - it('R-18 frontmatter unknown key returns null', () => { + it("R-18 frontmatter unknown key returns null", () => { const m = resolveOcPath(ast, { - file: 'X.md', - section: '[frontmatter]', - field: 'nonexistent', + file: "X.md", + section: "[frontmatter]", + field: "nonexistent", }); expect(m).toBeNull(); }); - it('R-19 frontmatter without field returns null', () => { + it("R-19 frontmatter without field returns null", () => { const m = resolveOcPath(ast, { - file: 'X.md', - section: '[frontmatter]', + file: "X.md", + section: "[frontmatter]", }); expect(m).toBeNull(); }); - it('R-20 multiple frontmatter keys with same name — first match wins', () => { + it("R-20 multiple frontmatter keys with same name — first match wins", () => { // Build an AST manually to test const dupeAst = { - kind: 'md' as const, - raw: '', + kind: "md" as const, + raw: "", frontmatter: [ - { key: 'k', value: 'first', line: 2 }, - { key: 'k', value: 'second', line: 3 }, + { key: "k", value: "first", line: 2 }, + { key: "k", value: "second", line: 3 }, ], - preamble: '', + preamble: "", blocks: [], }; const m = resolveOcPath(dupeAst, { - file: 'X.md', - section: '[frontmatter]', - field: 'k', + file: "X.md", + section: "[frontmatter]", + field: "k", }); - expect(m?.kind).toBe('frontmatter'); - if (m?.kind === 'frontmatter') {expect(m.node.value).toBe('first');} + expect(m?.kind).toBe("frontmatter"); + if (m?.kind === "frontmatter") { + expect(m.node.value).toBe("first"); + } }); - it('R-21 empty AST resolves root only', () => { - const empty = { kind: 'md' as const, raw: '', frontmatter: [], preamble: '', blocks: [] }; - expect(resolveOcPath(empty, { file: 'X.md' })?.kind).toBe('root'); - expect(resolveOcPath(empty, { file: 'X.md', section: 'any' })).toBeNull(); + it("R-21 empty AST resolves root only", () => { + const empty = { kind: "md" as const, raw: "", frontmatter: [], preamble: "", blocks: [] }; + expect(resolveOcPath(empty, { file: "X.md" })?.kind).toBe("root"); + expect(resolveOcPath(empty, { file: "X.md", section: "any" })).toBeNull(); }); - it('R-22 resolver does not mutate the AST', () => { + it("R-22 resolver does not mutate the AST", () => { const before = JSON.stringify(ast); - resolveOcPath(ast, { file: 'X.md', section: 'tools', item: 'gh', field: 'gh' }); + resolveOcPath(ast, { file: "X.md", section: "tools", item: "gh", field: "gh" }); const after = JSON.stringify(ast); expect(after).toBe(before); }); - it('R-23 file segment is informational — resolver doesn\'t check it', () => { + it("R-23 file segment is informational — resolver doesn't check it", () => { // The file name in OcPath is metadata; resolver assumes the AST // matches. Callers verify file mapping before passing the AST. - const m1 = resolveOcPath(ast, { file: 'SOUL.md', section: 'tools' }); - const m2 = resolveOcPath(ast, { file: 'AGENTS.md', section: 'tools' }); + const m1 = resolveOcPath(ast, { file: "SOUL.md", section: "tools" }); + const m2 = resolveOcPath(ast, { file: "AGENTS.md", section: "tools" }); expect(m1?.kind).toBe(m2?.kind); }); });