From 45845ea080eb29b14b08fd210f6c04cec3d7f07d Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Fri, 2 Oct 2026 18:44:03 +0200 Subject: [PATCH 1/7] feat(rules): add TOML reader to rule context Signed-off-by: Rhuan Barreto --- docs/src/content/docs/concepts/rules.mdx | 1 + docs/src/content/docs/nb/concepts/rules.mdx | 1 + .../content/docs/nb/reference/rule-api.mdx | 21 ++ .../src/content/docs/pt-br/concepts/rules.mdx | 1 + .../content/docs/pt-br/reference/rule-api.mdx | 21 ++ docs/src/content/docs/reference/rule-api.mdx | 21 ++ src/engine/runner.ts | 11 + src/engine/toml-utils.ts | 12 + src/formats/rules.ts | 9 + tests/engine/runner-toml.test.ts | 260 ++++++++++++++++++ .../__snapshots__/rules-shim.test.ts.snap | 9 + 11 files changed, 367 insertions(+) create mode 100644 src/engine/toml-utils.ts create mode 100644 tests/engine/runner-toml.test.ts diff --git a/docs/src/content/docs/concepts/rules.mdx b/docs/src/content/docs/concepts/rules.mdx index 12ec7de5..a2401050 100644 --- a/docs/src/content/docs/concepts/rules.mdx +++ b/docs/src/content/docs/concepts/rules.mdx @@ -70,6 +70,7 @@ The `check` function receives a `RuleContext` object that provides everything a | `ctx.glob(pattern)` | `Promise` | Find files matching a glob pattern | | `ctx.readFile(path)` | `Promise` | Read a file's content as a string | | `ctx.readJSON(path)` | `Promise` | Read and parse a JSON file | +| `ctx.readTOML(path)` | `Promise` | Read and parse a TOML file | ### Search Operations diff --git a/docs/src/content/docs/nb/concepts/rules.mdx b/docs/src/content/docs/nb/concepts/rules.mdx index 50036650..7a2d7415 100644 --- a/docs/src/content/docs/nb/concepts/rules.mdx +++ b/docs/src/content/docs/nb/concepts/rules.mdx @@ -70,6 +70,7 @@ Når `archgate check` kjøres uten `--strict`, betyr exit code 1 at minst ett br | `ctx.glob(pattern)` | `Promise` | Finn filer som matcher et glob-mønster | | `ctx.readFile(path)` | `Promise` | Les en fils innhold som streng | | `ctx.readJSON(path)` | `Promise` | Les og parse en JSON-fil | +| `ctx.readTOML(path)` | `Promise` | Les og parse en TOML-fil | ### Søkeoperasjoner diff --git a/docs/src/content/docs/nb/reference/rule-api.mdx b/docs/src/content/docs/nb/reference/rule-api.mdx index 95b99390..a08fc81b 100644 --- a/docs/src/content/docs/nb/reference/rule-api.mdx +++ b/docs/src/content/docs/nb/reference/rule-api.mdx @@ -229,6 +229,27 @@ const tsconfig = (await ctx.readJSON("tsconfig.json")) as { }; ``` +#### readTOML + +```typescript +readTOML(path: string): Promise; +``` + +Les og parse en TOML-fil med Buns innebygde parser. Stien er relativ til prosjektroten og bruker samme sandkasse som `readFile`. Alle filnavn godtas, inkludert `.prototools`. Returverdien er `unknown`; snevre inn eller konverter typen til strukturen regelen forventer. + +Hvert kall returnerer et nytt objekt, slik at mutasjoner ikke påvirker andre kall eller regler. Ugyldig TOML og filer som ikke kan leses, kaster en feil som gir en regelkjøringsfeil med avslutningskode 2. + +```typescript +const doc = (await ctx.readTOML("pyproject.toml")) as { + project?: { dependencies?: string[] }; + "dependency-groups"?: Record; +}; +const dependencies = doc.project?.dependencies ?? []; +const workerDependencies = doc["dependency-groups"]?.worker ?? []; +``` + +De parsede verdiene inkluderer ikke posisjoner i kildefilen. Bruk `ctx.grep()` når et regelbrudd trenger et linjenummer; treffene inkluderer linje og kolonne. Et regex-søk finner tekstforekomster, så begrens søket til den relevante tabellen når nøkler gjentas. + #### readYAML ```typescript diff --git a/docs/src/content/docs/pt-br/concepts/rules.mdx b/docs/src/content/docs/pt-br/concepts/rules.mdx index 84e1fb97..d1486d33 100644 --- a/docs/src/content/docs/pt-br/concepts/rules.mdx +++ b/docs/src/content/docs/pt-br/concepts/rules.mdx @@ -70,6 +70,7 @@ A função `check` recebe um objeto `RuleContext` que fornece tudo que uma regra | `ctx.glob(pattern)` | `Promise` | Encontra arquivos que correspondem a um padrão glob | | `ctx.readFile(path)` | `Promise` | Lê o conteúdo de um arquivo como string | | `ctx.readJSON(path)` | `Promise` | Lê e faz parse de um arquivo JSON | +| `ctx.readTOML(path)` | `Promise` | Lê e faz parse de um arquivo TOML | ### Operações de Busca diff --git a/docs/src/content/docs/pt-br/reference/rule-api.mdx b/docs/src/content/docs/pt-br/reference/rule-api.mdx index ce766639..8604ab59 100644 --- a/docs/src/content/docs/pt-br/reference/rule-api.mdx +++ b/docs/src/content/docs/pt-br/reference/rule-api.mdx @@ -229,6 +229,27 @@ const tsconfig = (await ctx.readJSON("tsconfig.json")) as { }; ``` +#### readTOML + +```typescript +readTOML(path: string): Promise; +``` + +Lê e faz parse de um arquivo TOML com o parser integrado do Bun. O caminho é relativo à raiz do projeto e usa o mesmo sandbox de `readFile`. Qualquer nome de arquivo é aceito, incluindo `.prototools`. O valor retornado é `unknown`; restrinja ou converta o tipo para a estrutura esperada. + +Cada chamada retorna um novo objeto, de modo que mutações não afetam outra chamada ou regra. TOML inválido e arquivos que não podem ser lidos lançam uma exceção, resultando em um erro de execução de regra com código de saída 2. + +```typescript +const doc = (await ctx.readTOML("pyproject.toml")) as { + project?: { dependencies?: string[] }; + "dependency-groups"?: Record; +}; +const dependencies = doc.project?.dependencies ?? []; +const workerDependencies = doc["dependency-groups"]?.worker ?? []; +``` + +Os valores retornados não incluem posições no arquivo. Use `ctx.grep()` quando uma violação precisar de um número de linha; os resultados incluem linha e coluna. Uma busca por regex encontra ocorrências de texto, então limite a busca à tabela relevante quando houver chaves repetidas. + #### readYAML ```typescript diff --git a/docs/src/content/docs/reference/rule-api.mdx b/docs/src/content/docs/reference/rule-api.mdx index a6247e72..fe45cf91 100644 --- a/docs/src/content/docs/reference/rule-api.mdx +++ b/docs/src/content/docs/reference/rule-api.mdx @@ -229,6 +229,27 @@ const tsconfig = (await ctx.readJSON("tsconfig.json")) as { }; ``` +#### readTOML + +```typescript +readTOML(path: string): Promise; +``` + +Read and parse a TOML file with Bun's built-in parser. The path is relative to the project root and uses the same sandbox as `readFile`. Any filename is accepted, including `.prototools`. The parsed value is `unknown`; narrow or cast it to your expected shape. + +Each call returns a fresh parsed object, so mutations cannot leak into another call or rule. Invalid TOML and unreadable files throw, surfacing as a rule execution error with exit code 2. + +```typescript +const doc = (await ctx.readTOML("pyproject.toml")) as { + project?: { dependencies?: string[] }; + "dependency-groups"?: Record; +}; +const dependencies = doc.project?.dependencies ?? []; +const workerDependencies = doc["dependency-groups"]?.worker ?? []; +``` + +Parsed values do not include source locations. Use `ctx.grep()` when a violation needs a line number; its matches include the line and column. A regex search finds text occurrences, so scope the search to the relevant table when keys repeat. + #### readYAML ```typescript diff --git a/src/engine/runner.ts b/src/engine/runner.ts index c4674e8a..d580c9f1 100644 --- a/src/engine/runner.ts +++ b/src/engine/runner.ts @@ -1,5 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2026 Archgate +/* oxlint-disable max-lines -- RuleContext implementations are kept together. */ import { relative, resolve } from "node:path"; import type { @@ -54,6 +55,7 @@ import { parseTsOrJsSource } from "./js-parser"; import { type LoadResult, blockedToRuleResult } from "./loader"; import { isWithinRoot, resolveUserPath, safePath } from "./safe-path"; import { applySuppressions, type SuppressionWarning } from "./suppressions"; +import { parseTomlDocument } from "./toml-utils"; import { parseYamlDocument } from "./yaml-utils"; const RULE_TIMEOUT_MS = 30_000; @@ -382,6 +384,15 @@ function createRuleContext( return Bun.file(safePath(resolvedRoot, path)).json(); }, + async readTOML(path: string): Promise { + const absPath = safePath(resolvedRoot, path); + const text = await cachedFileText(absPath); + return parseTomlDocument( + text, + relative(resolvedRoot, absPath).replaceAll("\\", "/") + ); + }, + // YAML counterpart of readJSON: nullable `frontmatter` plus `content` // in one object (see parseYamlDocument). The text read shares the // per-run file cache (strings are immutable); the PARSED value is diff --git a/src/engine/toml-utils.ts b/src/engine/toml-utils.ts new file mode 100644 index 00000000..8f7e8505 --- /dev/null +++ b/src/engine/toml-utils.ts @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2026 Archgate + +/** Parse TOML and add the project-relative path to parser failures. */ +export function parseTomlDocument(source: string, relPath: string): unknown { + try { + return Bun.TOML.parse(source); + } catch (err) { + const detail = err instanceof Error ? err.message : String(err); + throw new Error(`Failed to parse "${relPath}" as TOML: ${detail}`); + } +} diff --git a/src/formats/rules.ts b/src/formats/rules.ts index 418f9402..4f525208 100644 --- a/src/formats/rules.ts +++ b/src/formats/rules.ts @@ -259,6 +259,15 @@ export interface RuleContext { fileAtBase(path: string): Promise; readJSON(path: "package.json"): Promise; readJSON(path: string): Promise; + /** + * Read and parse a TOML file at a project-relative path, using the same + * sandbox as `readFile`. Each call returns a fresh parsed value as + * `unknown`; narrow or cast it to the expected shape in your rule. + * Accepts any filename, including `.prototools`. + * + * @throws {Error} On invalid TOML, unreadable files, or sandbox violations. + */ + readTOML(path: string): Promise; /** * Read a YAML file, or a Markdown file with YAML frontmatter, as one * {@link ReadYamlResult}. Dispatch is extension-based, so a multi-document diff --git a/tests/engine/runner-toml.test.ts b/tests/engine/runner-toml.test.ts new file mode 100644 index 00000000..ad27a858 --- /dev/null +++ b/tests/engine/runner-toml.test.ts @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2026 Archgate +import { afterEach, beforeEach, describe, expect, test } from "bun:test"; +import { mkdirSync, mkdtempSync, symlinkSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +import { loadRuleAdrs, type LoadResult } from "../../src/engine/loader"; +import { runChecks } from "../../src/engine/runner"; +import type { RuleSet } from "../../src/formats/rules"; +import { safeRmSync } from "../test-utils"; + +describe("runChecks ctx.readTOML()", () => { + let tempDir: string; + let outsideDir: string; + + beforeEach(() => { + tempDir = mkdtempSync(join(tmpdir(), "archgate-runner-toml-")); + outsideDir = mkdtempSync(join(tmpdir(), "archgate-toml-outside-")); + }); + + afterEach(() => { + safeRmSync(tempDir); + safeRmSync(outsideDir); + }); + + function makeLoadedAdr(ruleSet: RuleSet): LoadResult { + return { + type: "loaded", + value: { + adr: { + frontmatter: { + id: "TOML-001", + title: "TOML Test", + domain: "general", + rules: true, + }, + body: "", + filePath: join(tempDir, ".archgate", "adrs", "TOML-001.md"), + }, + ruleSet, + }, + }; + } + + test.each([ + { + path: "pyproject.toml", + text: '[project]\ndependencies = ["requests==2.32.0", "pydantic==2.9.0"]\n\n[dependency-groups]\nworker = ["requests==2.32.0"] # matching pin\n', + expected: { + project: { dependencies: ["requests==2.32.0", "pydantic==2.9.0"] }, + "dependency-groups": { worker: ["requests==2.32.0"] }, + }, + }, + { + path: "Cargo.toml", + text: '[package]\nname = "demo"\n[dependencies.serde]\nversion = "1"\nfeatures = [\n "derive", # comment\n]\n', + expected: { + package: { name: "demo" }, + dependencies: { serde: { version: "1", features: ["derive"] } }, + }, + }, + { + path: ".prototools", + text: 'bun = "1.4.2"\n[settings]\nauto-install = true\n', + expected: { bun: "1.4.2", settings: { "auto-install": true } }, + }, + { + path: "config/data.toml", + text: '"quoted.key" = "# literal"\ntool.ruff.line-length = 88\n[[targets]]\nname = "first"\n[[targets]]\nname = "second"\n', + expected: { + "quoted.key": "# literal", + tool: { ruff: { "line-length": 88 } }, + targets: [{ name: "first" }, { name: "second" }], + }, + }, + { path: "empty.toml", text: "# Only a comment\n", expected: {} }, + ])("parses $path", async ({ path, text, expected }) => { + await Bun.write(join(tempDir, path), text); + let parsed: unknown; + const loaded = makeLoadedAdr({ + rules: { + read: { + description: "Read TOML config", + async check(ctx) { + parsed = await ctx.readTOML(path); + }, + }, + }, + }); + + const result = await runChecks(tempDir, [loaded]); + expect(result.results[0].error).toBeUndefined(); + expect(parsed).toEqual(expected); + }); + + test.each(['key = ["unclosed"\n', 'key = "first"\nkey = "second"\n'])( + "invalid TOML surfaces a rule execution error: %s", + async (text) => { + await Bun.write(join(tempDir, "invalid.toml"), text); + const loaded = makeLoadedAdr({ + rules: { + read: { + description: "Read invalid TOML", + async check(ctx) { + await ctx.readTOML("invalid.toml"); + }, + }, + }, + }); + + const result = await runChecks(tempDir, [loaded]); + expect(result.results[0].error).toMatch( + /Failed to parse "invalid\.toml" as TOML: .+/u + ); + expect(result.results[0].violations).toEqual([]); + } + ); + + test("missing files surface a rule execution error", async () => { + const loaded = makeLoadedAdr({ + rules: { + read: { + description: "Read missing TOML", + async check(ctx) { + await ctx.readTOML("missing.toml"); + }, + }, + }, + }); + const result = await runChecks(tempDir, [loaded]); + expect(result.results[0].error).toContain("ENOENT"); + }); + + test.each(["traversal", "absolute", "symlink"] as const)( + "blocks %s paths outside the project", + async (kind) => { + await Bun.write(join(outsideDir, "secret.toml"), 'secret = "private"\n'); + const paths = { + traversal: "../outside.toml", + absolute: join(outsideDir, "secret.toml"), + symlink: "linked/secret.toml", + }; + if (kind === "symlink") { + symlinkSync(outsideDir, join(tempDir, "linked"), "junction"); + } + const loaded = makeLoadedAdr({ + rules: { + read: { + description: "Attempt to read outside the project", + async check(ctx) { + await ctx.readTOML(paths[kind]); + }, + }, + }, + }); + const result = await runChecks(tempDir, [loaded]); + expect(result.results[0].error).toContain("access denied"); + } + ); + + test("parsed mutations do not leak across calls or rules", async () => { + await Bun.write( + join(tempDir, "config.toml"), + '[project]\nitems = ["original"]\n' + ); + const observed: unknown[] = []; + const rule = { + description: "Mutate a parsed object", + async check(ctx) { + const first = await ctx.readTOML("config.toml"); + if ( + typeof first !== "object" || + first === null || + !("project" in first) + ) { + throw new Error("unexpected TOML shape"); + } + const project = first.project; + if ( + typeof project !== "object" || + project === null || + !("items" in project) || + !Array.isArray(project.items) || + !project.items.every( + (item): item is string => typeof item === "string" + ) + ) { + throw new Error("unexpected TOML shape"); + } + const items = project.items; + observed.push([...items]); + items.push("changed"); + observed.push(await ctx.readTOML("config.toml")); + }, + } satisfies RuleSet["rules"][string]; + const result = await runChecks(tempDir, [ + makeLoadedAdr({ rules: { first: rule, second: rule } }), + ]); + expect(result.results.map((entry) => entry.error)).toEqual([ + undefined, + undefined, + ]); + expect(observed).toEqual([ + ["original"], + { project: { items: ["original"] } }, + ["original"], + { project: { items: ["original"] } }, + ]); + }); + + test("on-disk sandboxed rules can inspect TOML and report violations", async () => { + const adrsDir = join(tempDir, ".archgate", "adrs"); + mkdirSync(adrsDir, { recursive: true }); + await Bun.write( + join(tempDir, "pyproject.toml"), + '[project]\nname = "demo"\n' + ); + await Bun.write( + join(adrsDir, "TOML-001.md"), + "---\nid: TOML-001\ntitle: TOML Test\ndomain: general\nrules: true\n---\n" + ); + await Bun.write( + join(adrsDir, "TOML-001.rules.ts"), + `/// +export default { + rules: { + "project-name": { + description: "Require the approved project name", + async check(ctx) { + const doc = await ctx.readTOML("pyproject.toml") as { + project: { name: string }; + }; + if (doc.project.name !== "approved") { + ctx.report.violation({ + message: "Unapproved project name: " + doc.project.name, + file: "pyproject.toml", + }); + } + }, + }, + }, +} satisfies RuleSet; +` + ); + const loaded = await loadRuleAdrs(tempDir); + const result = await runChecks(tempDir, loaded); + expect(result.results).toHaveLength(1); + expect(result.results[0].error).toBeUndefined(); + expect(result.results[0].violations).toEqual([ + { + message: "Unapproved project name: demo", + file: "pyproject.toml", + adrId: "TOML-001", + ruleId: "project-name", + severity: "error", + }, + ]); + }); +}); diff --git a/tests/helpers/__snapshots__/rules-shim.test.ts.snap b/tests/helpers/__snapshots__/rules-shim.test.ts.snap index 5c45f84f..f5f2f47e 100644 --- a/tests/helpers/__snapshots__/rules-shim.test.ts.snap +++ b/tests/helpers/__snapshots__/rules-shim.test.ts.snap @@ -251,6 +251,15 @@ declare interface RuleContext { fileAtBase(path: string): Promise; readJSON(path: "package.json"): Promise; readJSON(path: string): Promise; + /** + * Read and parse a TOML file at a project-relative path, using the same + * sandbox as \`readFile\`. Each call returns a fresh parsed value as + * \`unknown\`; narrow or cast it to the expected shape in your rule. + * Accepts any filename, including \`.prototools\`. + * + * @throws {Error} On invalid TOML, unreadable files, or sandbox violations. + */ + readTOML(path: string): Promise; /** * Read a YAML file, or a Markdown file with YAML frontmatter, as one * {@link ReadYamlResult}. Dispatch is extension-based, so a multi-document From 5abb2ce2870b2c286dc792ef9978b087ffb7170f Mon Sep 17 00:00:00 2001 From: rhuanbarreto <283004+rhuanbarreto@users.noreply.github.com> Date: Fri, 2 Oct 2026 16:44:34 +0000 Subject: [PATCH 2/7] docs: regenerate llms-full.txt Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- docs/public/llms-full.txt | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/public/llms-full.txt b/docs/public/llms-full.txt index 7684e346..955d7900 100644 --- a/docs/public/llms-full.txt +++ b/docs/public/llms-full.txt @@ -882,6 +882,7 @@ The `check` function receives a `RuleContext` object that provides everything a | `ctx.glob(pattern)` | `Promise` | Find files matching a glob pattern | | `ctx.readFile(path)` | `Promise` | Read a file's content as a string | | `ctx.readJSON(path)` | `Promise` | Read and parse a JSON file | +| `ctx.readTOML(path)` | `Promise` | Read and parse a TOML file | ### Search Operations @@ -5683,6 +5684,27 @@ const tsconfig = (await ctx.readJSON("tsconfig.json")) as { }; ``` +#### readTOML + +```typescript +readTOML(path: string): Promise; +``` + +Read and parse a TOML file with Bun's built-in parser. The path is relative to the project root and uses the same sandbox as `readFile`. Any filename is accepted, including `.prototools`. The parsed value is `unknown`; narrow or cast it to your expected shape. + +Each call returns a fresh parsed object, so mutations cannot leak into another call or rule. Invalid TOML and unreadable files throw, surfacing as a rule execution error with exit code 2. + +```typescript +const doc = (await ctx.readTOML("pyproject.toml")) as { + project?: { dependencies?: string[] }; + "dependency-groups"?: Record; +}; +const dependencies = doc.project?.dependencies ?? []; +const workerDependencies = doc["dependency-groups"]?.worker ?? []; +``` + +Parsed values do not include source locations. Use `ctx.grep()` when a violation needs a line number; its matches include the line and column. A regex search finds text occurrences, so scope the search to the relevant table when keys repeat. + #### readYAML ```typescript From 540e3e975b5e8c56cfa1c82f97b7375e7a2d18b3 Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Fri, 2 Oct 2026 18:53:58 +0200 Subject: [PATCH 3/7] docs: include readTOML in rule context examples Signed-off-by: Rhuan Barreto --- docs/public/llms-full.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/public/llms-full.txt b/docs/public/llms-full.txt index 955d7900..7b7d9e2b 100644 --- a/docs/public/llms-full.txt +++ b/docs/public/llms-full.txt @@ -884,6 +884,8 @@ The `check` function receives a `RuleContext` object that provides everything a | `ctx.readJSON(path)` | `Promise` | Read and parse a JSON file | | `ctx.readTOML(path)` | `Promise` | Read and parse a TOML file | +| `ctx.readTOML(path)` | `Promise` | Read and parse a TOML file | + ### Search Operations | Method | Returns | Description | @@ -5526,6 +5528,7 @@ interface RuleContext { fileAtBase(path: string): Promise; readJSON(path: "package.json"): Promise; readJSON(path: string): Promise; + readTOML(path: string): Promise; readYAML(path: string): Promise; ast( path: string, @@ -5669,6 +5672,7 @@ For a **structural** comparison (ignoring comments and formatting) prefer `ast(p ```typescript readJSON(path: "package.json"): Promise; readJSON(path: string): Promise; +readTOML(path: string): Promise; ``` Read and parse a JSON file. The path is relative to the project root. Returns the parsed value as `unknown` -- cast to the expected type in your rule. The literal path `"package.json"` matches a dedicated overload that returns the ambient `PackageJson` interface (`name`, `version`, `scripts`, `dependencies`, `devDependencies`, `peerDependencies`, `optionalDependencies`, `workspaces`, `engines`, ... plus an index signature), so the common case needs no cast. From 0db75ebd50662302e7b449aa245d06cb8600bef1 Mon Sep 17 00:00:00 2001 From: rhuanbarreto <283004+rhuanbarreto@users.noreply.github.com> Date: Fri, 2 Oct 2026 16:55:16 +0000 Subject: [PATCH 4/7] docs: regenerate llms-full.txt Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- docs/public/llms-full.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/public/llms-full.txt b/docs/public/llms-full.txt index 7b7d9e2b..955d7900 100644 --- a/docs/public/llms-full.txt +++ b/docs/public/llms-full.txt @@ -884,8 +884,6 @@ The `check` function receives a `RuleContext` object that provides everything a | `ctx.readJSON(path)` | `Promise` | Read and parse a JSON file | | `ctx.readTOML(path)` | `Promise` | Read and parse a TOML file | -| `ctx.readTOML(path)` | `Promise` | Read and parse a TOML file | - ### Search Operations | Method | Returns | Description | @@ -5528,7 +5526,6 @@ interface RuleContext { fileAtBase(path: string): Promise; readJSON(path: "package.json"): Promise; readJSON(path: string): Promise; - readTOML(path: string): Promise; readYAML(path: string): Promise; ast( path: string, @@ -5672,7 +5669,6 @@ For a **structural** comparison (ignoring comments and formatting) prefer `ast(p ```typescript readJSON(path: "package.json"): Promise; readJSON(path: string): Promise; -readTOML(path: string): Promise; ``` Read and parse a JSON file. The path is relative to the project root. Returns the parsed value as `unknown` -- cast to the expected type in your rule. The literal path `"package.json"` matches a dedicated overload that returns the ambient `PackageJson` interface (`name`, `version`, `scripts`, `dependencies`, `devDependencies`, `peerDependencies`, `optionalDependencies`, `workspaces`, `engines`, ... plus an index signature), so the common case needs no cast. From 2390528ad01ce2032f394204e7b14d156dc48b75 Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Fri, 2 Oct 2026 19:01:15 +0200 Subject: [PATCH 5/7] test: mirror TOML helper test path Signed-off-by: Rhuan Barreto --- tests/engine/{runner-toml.test.ts => toml-utils.test.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/engine/{runner-toml.test.ts => toml-utils.test.ts} (100%) diff --git a/tests/engine/runner-toml.test.ts b/tests/engine/toml-utils.test.ts similarity index 100% rename from tests/engine/runner-toml.test.ts rename to tests/engine/toml-utils.test.ts From ebff98a7c903ddcc38f17fa96646939e5d532c36 Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Sat, 3 Oct 2026 03:43:31 +0200 Subject: [PATCH 6/7] docs: add readTOML to full RuleContext examples Signed-off-by: Rhuan Barreto --- docs/public/llms-full.txt | 1 + docs/src/content/docs/nb/reference/rule-api.mdx | 1 + docs/src/content/docs/pt-br/reference/rule-api.mdx | 1 + docs/src/content/docs/reference/rule-api.mdx | 1 + 4 files changed, 4 insertions(+) diff --git a/docs/public/llms-full.txt b/docs/public/llms-full.txt index 955d7900..3a97dcab 100644 --- a/docs/public/llms-full.txt +++ b/docs/public/llms-full.txt @@ -5526,6 +5526,7 @@ interface RuleContext { fileAtBase(path: string): Promise; readJSON(path: "package.json"): Promise; readJSON(path: string): Promise; + readTOML(path: string): Promise; readYAML(path: string): Promise; ast( path: string, diff --git a/docs/src/content/docs/nb/reference/rule-api.mdx b/docs/src/content/docs/nb/reference/rule-api.mdx index a08fc81b..c5e2fdb0 100644 --- a/docs/src/content/docs/nb/reference/rule-api.mdx +++ b/docs/src/content/docs/nb/reference/rule-api.mdx @@ -71,6 +71,7 @@ interface RuleContext { fileAtBase(path: string): Promise; readJSON(path: "package.json"): Promise; readJSON(path: string): Promise; + readTOML(path: string): Promise; readYAML(path: string): Promise; ast( path: string, diff --git a/docs/src/content/docs/pt-br/reference/rule-api.mdx b/docs/src/content/docs/pt-br/reference/rule-api.mdx index 8604ab59..11c45b55 100644 --- a/docs/src/content/docs/pt-br/reference/rule-api.mdx +++ b/docs/src/content/docs/pt-br/reference/rule-api.mdx @@ -71,6 +71,7 @@ interface RuleContext { fileAtBase(path: string): Promise; readJSON(path: "package.json"): Promise; readJSON(path: string): Promise; + readTOML(path: string): Promise; readYAML(path: string): Promise; ast( path: string, diff --git a/docs/src/content/docs/reference/rule-api.mdx b/docs/src/content/docs/reference/rule-api.mdx index fe45cf91..ccb94049 100644 --- a/docs/src/content/docs/reference/rule-api.mdx +++ b/docs/src/content/docs/reference/rule-api.mdx @@ -71,6 +71,7 @@ interface RuleContext { fileAtBase(path: string): Promise; readJSON(path: "package.json"): Promise; readJSON(path: string): Promise; + readTOML(path: string): Promise; readYAML(path: string): Promise; ast( path: string, From 891021c5c7e4af8abe32aaac9d22ece7838711fb Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Sat, 3 Oct 2026 11:14:20 +0200 Subject: [PATCH 7/7] chore: ignore dev-only braces advisory GHSA-vfj7-8cjw-p6xm until patched Signed-off-by: Rhuan Barreto --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4b611995..0074c26b 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ ], "type": "module", "scripts": { - "audit": "bun audit", + "audit": "bun audit --ignore=GHSA-vfj7-8cjw-p6xm", "build:check": "bun build src/cli.ts --compile --bytecode --outfile dist/.build-check && rm -f dist/.build-check dist/.build-check.exe", "build:winget": "bun run shims/winget/build.ts", "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",