From 9c1f533bb91c866162734046df6f762adfed07d7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 28 Aug 2026 11:16:37 +0000 Subject: [PATCH] Add 10 rig samples 481-490 (2026-08-28) - 481 git-blame-line-age-analyzer: defineTool+p.glob, s.record output - 482 ts-mapped-type-extractor: defineTool+p.glob, steering addon - 483 dotenv-drift-detector: p.readOptional+p.bash, s.enum classification - 484 vitest-snapshot-reporter: workflow with Promise.all parallel subagents - 485 git-stale-branch-reporter: defineTool with node:child_process, repair addon - 486 ts-abstract-class-finder: defineTool+p.glob, steering addon - 487 git-tag-message-extractor: defineTool+p.bash, s.enum classification - 488 parallel-dep-audit-workflow: workflow with two concurrent auditor agents - 489 ts-union-type-writer: p.writeInput, input schema, validateVariant tool - 490 json-schema-field-classifier: defineTool+p.glob, s.record nested output Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../481-git-blame-line-age-analyzer.md | 53 +++++++++++++++++++ .../samples/482-ts-mapped-type-extractor.md | 50 +++++++++++++++++ .../rig/samples/483-dotenv-drift-detector.md | 44 +++++++++++++++ .../samples/484-vitest-snapshot-reporter.md | 53 +++++++++++++++++++ .../samples/485-git-stale-branch-reporter.md | 47 ++++++++++++++++ .../samples/486-ts-abstract-class-finder.md | 51 ++++++++++++++++++ .../samples/487-git-tag-message-extractor.md | 52 ++++++++++++++++++ .../488-parallel-dep-audit-workflow.md | 50 +++++++++++++++++ .../rig/samples/489-ts-union-type-writer.md | 37 +++++++++++++ .../490-json-schema-field-classifier.md | 48 +++++++++++++++++ 10 files changed, 485 insertions(+) create mode 100644 skills/rig/samples/481-git-blame-line-age-analyzer.md create mode 100644 skills/rig/samples/482-ts-mapped-type-extractor.md create mode 100644 skills/rig/samples/483-dotenv-drift-detector.md create mode 100644 skills/rig/samples/484-vitest-snapshot-reporter.md create mode 100644 skills/rig/samples/485-git-stale-branch-reporter.md create mode 100644 skills/rig/samples/486-ts-abstract-class-finder.md create mode 100644 skills/rig/samples/487-git-tag-message-extractor.md create mode 100644 skills/rig/samples/488-parallel-dep-audit-workflow.md create mode 100644 skills/rig/samples/489-ts-union-type-writer.md create mode 100644 skills/rig/samples/490-json-schema-field-classifier.md diff --git a/skills/rig/samples/481-git-blame-line-age-analyzer.md b/skills/rig/samples/481-git-blame-line-age-analyzer.md new file mode 100644 index 0000000..dfeed13 --- /dev/null +++ b/skills/rig/samples/481-git-blame-line-age-analyzer.md @@ -0,0 +1,53 @@ +# 481 - Git Blame Line Age Analyzer + +```rig +import { agent, defineTool, p, repair, s } from "rig"; + +const parseBlameBlock = defineTool("parseBlameBlock", { + description: "Parse git blame --line-porcelain output for a file and return per-line age stats.", + parameters: s.object({ filePath: s.path }), + handler: async ({ filePath }) => { + const { execSync } = await import("node:child_process"); + let output = ""; + try { + output = execSync(`git blame --line-porcelain -- ${filePath}`, { encoding: "utf-8" }); + } catch { + return { avgAgeDays: 0, staleLines: 0, recentLines: 0, totalLines: 0 }; + } + const now = Date.now(); + const timestamps: number[] = []; + for (const line of output.split("\n")) { + if (line.startsWith("author-time ")) { + timestamps.push(parseInt(line.slice("author-time ".length), 10) * 1000); + } + } + if (timestamps.length === 0) return { avgAgeDays: 0, staleLines: 0, recentLines: 0, totalLines: 0 }; + const ages = timestamps.map((t: number) => (now - t) / 86400000); + const avgAgeDays = ages.reduce((a: number, b: number) => a + b, 0) / ages.length; + const recentLines = ages.filter((d: number) => d < 30).length; + const staleLines = ages.filter((d: number) => d >= 180).length; + return { avgAgeDays, staleLines, recentLines, totalLines: ages.length }; + }, +}); + +// Agent role: analyze line age across TypeScript source files using git blame. +const gitBlameLineAge = agent({ + model: "small", + instructions: p`Find TypeScript files using ${p.glob("src/**/*.ts")}. For each file path, call parseBlameBlock. Return files as a record keyed by path with avgAgeDays, staleLines, recentLines, totalLines. Also include oldestFile (path with highest avgAgeDays, omit if none) and newestFile (path with lowest avgAgeDays, omit if none).`, + output: s.object({ + files: s.record(s.object({ + avgAgeDays: s.number, + staleLines: s.int, + recentLines: s.int, + totalLines: s.int, + })), + oldestFile: s.optional(s.string), + newestFile: s.optional(s.string), + }), + tools: [parseBlameBlock], + maxTurns: 8, + addons: [repair()], +}); + +export default gitBlameLineAge; +``` diff --git a/skills/rig/samples/482-ts-mapped-type-extractor.md b/skills/rig/samples/482-ts-mapped-type-extractor.md new file mode 100644 index 0000000..30dfe5d --- /dev/null +++ b/skills/rig/samples/482-ts-mapped-type-extractor.md @@ -0,0 +1,50 @@ +# 482 - TS Mapped Type Extractor + +```rig +import { agent, defineTool, p, s, steering } from "rig"; + +const extractMappedTypes = defineTool("extractMappedTypes", { + description: "Extract TypeScript mapped type patterns from a source file.", + parameters: s.object({ filePath: s.path }), + handler: async ({ filePath }) => { + const { readFile } = await import("node:fs/promises"); + const content = await readFile(filePath, "utf-8"); + const pattern = /\{\s*\[(\w+)\s+in\s+([^\]]+)\]\s*(?::\s*([^;}\n]+))?/g; + const results: Array<{ keySource: string; valueType: string; isReadonly: boolean; sourceFile: string }> = []; + let m: RegExpExecArray | null; + while ((m = pattern.exec(content)) !== null) { + const before = content.slice(0, m.index); + const isReadonly = /readonly\s*$/.test(before.trimEnd()); + results.push({ + keySource: m[2]?.trim() ?? "unknown", + valueType: m[3]?.trim() ?? "unknown", + isReadonly, + sourceFile: filePath, + }); + } + return results; + }, +}); + +// Agent role: scan TypeScript source files for mapped type patterns and summarize usage. +const tsMappedTypeExtractor = agent({ + model: "small", + instructions: p`Find TypeScript files using ${p.glob("src/**/*.ts")}. For each file path, call extractMappedTypes. Build a types record keyed by a unique name (e.g., "FilePath:index") with keySource, valueType, isReadonly, sourceFile. Include totalMappedTypes, totalFiles, and mostUsedKeySource (the keySource string that appears most often, omit if no types found).`, + output: s.object({ + types: s.record(s.object({ + keySource: s.string, + valueType: s.string, + isReadonly: s.boolean, + sourceFile: s.path, + })), + totalMappedTypes: s.int, + totalFiles: s.int, + mostUsedKeySource: s.optional(s.string), + }), + tools: [extractMappedTypes], + maxTurns: 8, + addons: [steering()], +}); + +export default tsMappedTypeExtractor; +``` diff --git a/skills/rig/samples/483-dotenv-drift-detector.md b/skills/rig/samples/483-dotenv-drift-detector.md new file mode 100644 index 0000000..9a1cf30 --- /dev/null +++ b/skills/rig/samples/483-dotenv-drift-detector.md @@ -0,0 +1,44 @@ +# 483 - Dotenv Drift Detector + +```rig +import { agent, defineTool, p, repair, s } from "rig"; + +const classifyEnvKey = defineTool("classifyEnvKey", { + description: "Classify an env key as declared, undeclared, or unused given env example keys and code keys.", + parameters: s.object({ + key: s.string, + exampleKeys: s.array(s.string), + codeKeys: s.array(s.string), + }), + handler: async ({ key, exampleKeys, codeKeys }) => { + const inExample = exampleKeys.includes(key); + const usedInCode = codeKeys.includes(key); + let status: "declared" | "undeclared" | "unused"; + if (inExample && usedInCode) status = "declared"; + else if (!inExample && usedInCode) status = "undeclared"; + else status = "unused"; + return { inExample, usedInCode, status }; + }, +}); + +// Agent role: detect drift between .env.example declarations and process.env usage in source code. +const dotenvDriftDetector = agent({ + model: "small", + instructions: p`Read the .env.example file: ${p.readOptional(".env.example", "# empty")}. Find all process.env usages in source code: ${p.bash("grep -rn 'process\\.env\\.' src/ 2>/dev/null || echo 'no matches'")}. Extract the declared keys from .env.example (lines matching KEY=) and the used keys from grep output (process.env.KEY patterns). For each unique key across both sets, call classifyEnvKey. Return keys as a record with inExample, usedInCode, status. Include totalKeys, missingFromExample (keys used in code but not in .env.example), and unusedDeclarations (keys in .env.example not used in code).`, + output: s.object({ + keys: s.record(s.object({ + inExample: s.boolean, + usedInCode: s.boolean, + status: s.enum("declared", "undeclared", "unused"), + })), + totalKeys: s.int, + missingFromExample: s.array(s.string), + unusedDeclarations: s.array(s.string), + }), + tools: [classifyEnvKey], + maxTurns: 6, + addons: [repair()], +}); + +export default dotenvDriftDetector; +``` diff --git a/skills/rig/samples/484-vitest-snapshot-reporter.md b/skills/rig/samples/484-vitest-snapshot-reporter.md new file mode 100644 index 0000000..62b45a7 --- /dev/null +++ b/skills/rig/samples/484-vitest-snapshot-reporter.md @@ -0,0 +1,53 @@ +# 484 - Vitest Snapshot Reporter + +```rig +import { agent, p, s, workflow } from "rig"; + +// Agent role: find snapshot files using bash and report total file count. +const snapshotFileAgent = agent({ + model: "small", + instructions: p`Run ${p.bash("find . -name '*.snap' 2>/dev/null || echo ''")} to list snapshot files. Return the list of file paths and total count.`, + output: s.object({ + files: s.array(s.path), + totalFiles: s.int, + }), +}); + +// Agent role: count snapshot entries in a single snapshot file. +const snapshotCountAgent = agent({ + model: "small", + input: s.object({ path: s.path }), + instructions: p`Read the snapshot file at ${p.readInput("path")}. Count the number of snapshot entries (lines matching /^exports\[/). Return the file path and entry count.`, + output: s.object({ + path: s.path, + entryCount: s.int, + }), +}); + +// Workflow role: discover snapshot files and count entries across the workspace. +export default workflow({ + meta: { + name: "vitest-snapshot-reporter", + description: "Discover vitest snapshot files and count snapshot entries across the workspace.", + }, + body: async ({ call }) => { + const fileResult = await call(snapshotFileAgent, "List all snapshot files."); + if (!fileResult) return null; + const counts = await Promise.all( + fileResult.files.map((path: string) => call(snapshotCountAgent, { path })) + ); + const validCounts = counts.filter((r): r is { path: string; entryCount: number } => r !== null); + const totalSnapshots = validCounts.reduce((sum: number, r: { entryCount: number }) => sum + r.entryCount, 0); + const largest = validCounts.reduce( + (best: { path: string; entryCount: number } | null, r: { path: string; entryCount: number }) => + !best || r.entryCount > best.entryCount ? r : best, + null + ); + return { + totalSnapshots, + totalFiles: fileResult.totalFiles, + largestSnapshotFile: largest?.path ?? undefined, + }; + }, +}); +``` diff --git a/skills/rig/samples/485-git-stale-branch-reporter.md b/skills/rig/samples/485-git-stale-branch-reporter.md new file mode 100644 index 0000000..693472f --- /dev/null +++ b/skills/rig/samples/485-git-stale-branch-reporter.md @@ -0,0 +1,47 @@ +# 485 - Git Stale Branch Reporter + +```rig +import { agent, defineTool, p, repair, s } from "rig"; + +const getBranchAge = defineTool("getBranchAge", { + description: "Get the age in days and age class for a git branch.", + parameters: s.object({ branch: s.string }), + handler: async ({ branch }) => { + const { execSync } = await import("node:child_process"); + let lastCommit = ""; + try { + lastCommit = execSync(`git log --format=%ci -1 "${branch.trim()}" 2>/dev/null`, { encoding: "utf-8" }).trim(); + } catch { + return { lastCommit: "unknown", ageDays: 9999, ageClass: "ancient" as const }; + } + if (!lastCommit) return { lastCommit: "unknown", ageDays: 9999, ageClass: "ancient" as const }; + const ageDays = Math.floor((Date.now() - new Date(lastCommit).getTime()) / 86400000); + const ageClass = + ageDays < 7 ? "fresh" : + ageDays < 30 ? "recent" : + ageDays < 180 ? "stale" : "ancient"; + return { lastCommit, ageDays, ageClass } as { lastCommit: string; ageDays: number; ageClass: "fresh" | "recent" | "stale" | "ancient" }; + }, +}); + +// Agent role: report age class of all remote git branches. +const gitStaleBranchReporter = agent({ + model: "small", + instructions: p`List remote branches: ${p.bash("git branch -r 2>/dev/null || echo ''")}. For each branch name, call getBranchAge. Return branches as a record keyed by branch name with lastCommit, ageDays, ageClass. Include staleCount (ageClass=stale), ancientCount (ageClass=ancient), and totalBranches.`, + output: s.object({ + branches: s.record(s.object({ + lastCommit: s.string, + ageDays: s.int, + ageClass: s.enum("fresh", "recent", "stale", "ancient"), + })), + staleCount: s.int, + ancientCount: s.int, + totalBranches: s.int, + }), + tools: [getBranchAge], + maxTurns: 8, + addons: [repair()], +}); + +export default gitStaleBranchReporter; +``` diff --git a/skills/rig/samples/486-ts-abstract-class-finder.md b/skills/rig/samples/486-ts-abstract-class-finder.md new file mode 100644 index 0000000..b291704 --- /dev/null +++ b/skills/rig/samples/486-ts-abstract-class-finder.md @@ -0,0 +1,51 @@ +# 486 - TS Abstract Class Finder + +```rig +import { agent, defineTool, p, s, steering } from "rig"; + +const extractAbstractClasses = defineTool("extractAbstractClasses", { + description: "Extract abstract class declarations and their abstract methods from a TypeScript file.", + parameters: s.object({ filePath: s.path }), + handler: async ({ filePath }) => { + const { readFile } = await import("node:fs/promises"); + const content = await readFile(filePath, "utf-8"); + const classPattern = /abstract\s+class\s+(\w+)[^{]*\{/g; + const abstractMethodPattern = /abstract\s+(?:readonly\s+)?(?:\w+\s*[(<])/g; + const classes: Array<{ name: string; methodCount: number; abstractMethodCount: number; sourceFile: string }> = []; + let m: RegExpExecArray | null; + while ((m = classPattern.exec(content)) !== null) { + const name = m[1]; + const methodMatches = content.match(/\b(?:public|protected|private|async)?\s+\w+\s*[(<]/g) ?? []; + const abstractMatches = content.match(abstractMethodPattern) ?? []; + classes.push({ + name, + methodCount: methodMatches.length, + abstractMethodCount: abstractMatches.length, + sourceFile: filePath, + }); + } + return classes; + }, +}); + +// Agent role: find abstract classes and their abstract method counts across TypeScript source files. +const tsAbstractClassFinder = agent({ + model: "small", + instructions: p`Find TypeScript files using ${p.glob("src/**/*.ts")}. For each file path, call extractAbstractClasses. Build a classes record keyed by class name with methodCount, abstractMethodCount, sourceFile. Include totalClasses, totalAbstractMethods, and mostAbstractFile (path with most abstract methods, omit if none found).`, + output: s.object({ + classes: s.record(s.object({ + methodCount: s.int, + abstractMethodCount: s.int, + sourceFile: s.path, + })), + totalClasses: s.int, + totalAbstractMethods: s.int, + mostAbstractFile: s.optional(s.string), + }), + tools: [extractAbstractClasses], + maxTurns: 8, + addons: [steering()], +}); + +export default tsAbstractClassFinder; +``` diff --git a/skills/rig/samples/487-git-tag-message-extractor.md b/skills/rig/samples/487-git-tag-message-extractor.md new file mode 100644 index 0000000..4996e57 --- /dev/null +++ b/skills/rig/samples/487-git-tag-message-extractor.md @@ -0,0 +1,52 @@ +# 487 - Git Tag Message Extractor + +```rig +import { agent, defineTool, p, s, steering } from "rig"; + +const getTagDetails = defineTool("getTagDetails", { + description: "Get details of a git tag including type (annotated or lightweight), message, and tagger.", + parameters: s.object({ tag: s.string }), + handler: async ({ tag }) => { + const { execSync } = await import("node:child_process"); + let tagType: "annotated" | "lightweight" = "lightweight"; + let message: string | undefined; + let tagger: string | undefined; + try { + const output = execSync(`git cat-file -t "${tag}" 2>/dev/null`, { encoding: "utf-8" }).trim(); + if (output === "tag") { + tagType = "annotated"; + const tagObj = execSync(`git cat-file tag "${tag}" 2>/dev/null`, { encoding: "utf-8" }); + const taggerLine = tagObj.split("\n").find((l: string) => l.startsWith("tagger ")); + if (taggerLine) tagger = taggerLine.replace(/^tagger\s+/, "").trim(); + const msgStart = tagObj.indexOf("\n\n"); + if (msgStart !== -1) message = tagObj.slice(msgStart + 2).trim(); + } + } catch { + // lightweight tag + } + return { tagType, message, tagger }; + }, +}); + +// Agent role: extract and classify all git tag messages and metadata. +const gitTagMessageExtractor = agent({ + model: "small", + instructions: p`List all git tags: ${p.bash("git tag -l 2>/dev/null || echo ''")}. For each tag name, call getTagDetails. Return tags as a record keyed by tag name with tagType, message (omit if lightweight), and tagger (omit if not annotated). Include totalTags, annotatedCount, lightweightCount, and mostRecentTag (last tag alphabetically or by creation, omit if no tags).`, + output: s.object({ + tags: s.record(s.object({ + tagType: s.enum("annotated", "lightweight"), + message: s.optional(s.string), + tagger: s.optional(s.string), + })), + totalTags: s.int, + annotatedCount: s.int, + lightweightCount: s.int, + mostRecentTag: s.optional(s.string), + }), + tools: [getTagDetails], + maxTurns: 8, + addons: [steering()], +}); + +export default gitTagMessageExtractor; +``` diff --git a/skills/rig/samples/488-parallel-dep-audit-workflow.md b/skills/rig/samples/488-parallel-dep-audit-workflow.md new file mode 100644 index 0000000..07a8655 --- /dev/null +++ b/skills/rig/samples/488-parallel-dep-audit-workflow.md @@ -0,0 +1,50 @@ +# 488 - Parallel Dep Audit Workflow + +```rig +import { agent, p, s, workflow } from "rig"; + +// Agent role: audit devDependencies for packages that appear misplaced in dependencies. +const devDepAuditor = agent({ + model: "small", + instructions: p`Read ${p.read("package.json")}. Extract the devDependencies and dependencies objects. Identify packages that appear in both devDependencies and dependencies (misplaced). Return devCount as the number of devDependencies entries, and misplacedPkgs as the list of package names appearing in both.`, + output: s.object({ + devCount: s.int, + misplacedPkgs: s.array(s.string), + }), +}); + +// Agent role: audit production dependencies for obvious peer conflicts. +const prodDepAuditor = agent({ + model: "small", + instructions: p`Read ${p.read("package.json")}. Extract the dependencies and peerDependencies objects. Identify packages listed in peerDependencies that are also explicitly in dependencies (peer conflicts). Return prodCount as the number of dependencies entries, and peerConflicts as the list of conflicting package names.`, + output: s.object({ + prodCount: s.int, + peerConflicts: s.array(s.string), + }), +}); + +// Workflow role: run both dependency auditors concurrently and combine results. +export default workflow({ + meta: { + name: "parallel-dep-audit", + description: "Audit package.json dev and prod dependencies in parallel for misplacements and peer conflicts.", + }, + body: async ({ call }) => { + const [devResult, prodResult] = await Promise.all([ + call(devDepAuditor, "Audit devDependencies."), + call(prodDepAuditor, "Audit prodDependencies."), + ]); + if (!devResult || !prodResult) return null; + const issueCount = devResult.misplacedPkgs.length + prodResult.peerConflicts.length; + const overallHealth: "healthy" | "warnings" | "critical" = + issueCount === 0 ? "healthy" : issueCount > 3 ? "critical" : "warnings"; + return { + devCount: devResult.devCount, + prodCount: prodResult.prodCount, + misplacedPkgs: devResult.misplacedPkgs, + peerConflicts: prodResult.peerConflicts, + overallHealth, + }; + }, +}); +``` diff --git a/skills/rig/samples/489-ts-union-type-writer.md b/skills/rig/samples/489-ts-union-type-writer.md new file mode 100644 index 0000000..3d957a1 --- /dev/null +++ b/skills/rig/samples/489-ts-union-type-writer.md @@ -0,0 +1,37 @@ +# 489 - TS Union Type Writer + +```rig +import { agent, defineTool, p, repair, s } from "rig"; + +const validateVariant = defineTool("validateVariant", { + description: "Check whether a string is a valid TypeScript identifier.", + parameters: s.object({ variant: s.string }), + handler: async ({ variant }) => { + const valid = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(variant); + return { variant, valid }; + }, +}); + +// Agent role: generate a TypeScript union type declaration from caller-supplied variants and write it to a file. +const tsUnionTypeWriter = agent({ + model: "small", + input: s.object({ + typeName: s.string, + variants: s.array(s.string), + outputFile: s.path, + }), + instructions: p`You are given a typeName, variants array, and outputFile path via input. For each variant in input.variants, call validateVariant to check it is a valid TypeScript identifier. Build a TypeScript union type declaration: "export type = | | ...;" using only valid variants. Write the declaration to input.outputFile using ${p.writeInput("outputFile", "typeSource")}. Return outputFile, variantsWritten (valid variants), isValid (true if all variants passed), linesEmitted (line count of the declaration).`, + output: s.object({ + typeSource: s.string, + outputFile: s.path, + variantsWritten: s.array(s.string), + isValid: s.boolean, + linesEmitted: s.int, + }), + tools: [validateVariant], + maxTurns: 6, + addons: [repair()], +}); + +export default tsUnionTypeWriter; +``` diff --git a/skills/rig/samples/490-json-schema-field-classifier.md b/skills/rig/samples/490-json-schema-field-classifier.md new file mode 100644 index 0000000..3b0a7f0 --- /dev/null +++ b/skills/rig/samples/490-json-schema-field-classifier.md @@ -0,0 +1,48 @@ +# 490 - JSON Schema Field Classifier + +```rig +import { agent, defineTool, p, repair, s } from "rig"; + +const classifySchemaFields = defineTool("classifySchemaFields", { + description: "Parse a JSON schema file and classify each top-level property by its JSON Schema type.", + parameters: s.object({ filePath: s.path }), + handler: async ({ filePath }) => { + const { readFile } = await import("node:fs/promises"); + let schema: Record; + try { + schema = JSON.parse(await readFile(filePath, "utf-8")); + } catch { + return { fieldCount: 0, fields: {} }; + } + const properties = (schema["properties"] ?? {}) as Record; + const fields: Record = {}; + for (const [key, def] of Object.entries(properties)) { + const t = def?.type; + if (!t) fields[key] = "unknown"; + else if (Array.isArray(t)) fields[key] = t.length > 1 ? "mixed" : (t[0] ?? "unknown"); + else fields[key] = t; + } + return { fieldCount: Object.keys(fields).length, fields }; + }, +}); + +// Agent role: discover JSON schema files and classify each field by its type. +const jsonSchemaFieldClassifier = agent({ + model: "small", + instructions: p`Find JSON schema files using ${p.glob("**/*.schema.json")}. For each file path, call classifySchemaFields. Return schemas as a record keyed by file path with fieldCount and fields (a record mapping field name to type string). Include totalSchemas, totalFields, and mostComplexSchema (path with highest fieldCount, omit if no schemas found).`, + output: s.object({ + schemas: s.record(s.object({ + fieldCount: s.int, + fields: s.record(s.string), + })), + totalSchemas: s.int, + totalFields: s.int, + mostComplexSchema: s.optional(s.string), + }), + tools: [classifySchemaFields], + maxTurns: 8, + addons: [repair()], +}); + +export default jsonSchemaFieldClassifier; +```