Skip to content

Commit 425e116

Browse files
Add 10 rig sample files (401-410) — 2026-08-11 (#404)
1 parent d408996 commit 425e116

10 files changed

Lines changed: 507 additions & 0 deletions
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# 401 - Git File At Revision
2+
3+
```rig
4+
import { agent, p, s, repair, defineTool } from "rig";
5+
6+
const extractRevisionMetadata = defineTool("extractRevisionMetadata", {
7+
description: "Get commit hash, message, and file content at a given git revision.",
8+
parameters: s.object({ revision: s.string, filePath: s.path }),
9+
handler: async ({ revision, filePath }: { revision: string; filePath: string }) => {
10+
const { execSync } = await import("node:child_process");
11+
const fileContent = execSync(`git show ${revision}:${filePath} 2>/dev/null || echo ""`, { encoding: "utf8" });
12+
const logLine = execSync(`git log --oneline -1 ${revision} -- ${filePath} 2>/dev/null || echo ""`, { encoding: "utf8" }).trim();
13+
const spaceIdx = logLine.indexOf(" ");
14+
return {
15+
fileContent,
16+
commitHash: spaceIdx > -1 ? logLine.slice(0, spaceIdx) : logLine,
17+
commitMessage: spaceIdx > -1 ? logLine.slice(spaceIdx + 1) : "",
18+
linesCount: fileContent.split("\n").length,
19+
};
20+
},
21+
});
22+
23+
// Agent role: Extract file content at a specific git revision and return metadata.
24+
const gitFileAtRevision = agent({
25+
model: "small",
26+
input: s.object({ filePath: s.path, revision: s.string }),
27+
instructions: p`Use the extractRevisionMetadata tool with the filePath and revision from the input to retrieve the file content and commit metadata. Return all fields exactly as provided by the tool.`,
28+
tools: [extractRevisionMetadata],
29+
output: s.object({
30+
fileContent: s.string,
31+
commitHash: s.string,
32+
commitMessage: s.string,
33+
linesCount: s.int,
34+
revision: s.string,
35+
}),
36+
addons: [repair()],
37+
});
38+
39+
export default gitFileAtRevision;
40+
```
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# 402 - Json Schema Structure Validator
2+
3+
```rig
4+
import { agent, p, s, repair, defineTool } from "rig";
5+
6+
const validateStructure = defineTool("validateStructure", {
7+
description: "Validate a JSON data file against a JSON schema file, checking required fields and types.",
8+
parameters: s.object({ schemaContent: s.string, dataContent: s.string }),
9+
handler: async ({ schemaContent, dataContent }: { schemaContent: string; dataContent: string }) => {
10+
const errors: string[] = [];
11+
let schema: Record<string, unknown>;
12+
let data: Record<string, unknown>;
13+
try {
14+
schema = JSON.parse(schemaContent) as Record<string, unknown>;
15+
} catch (e) {
16+
return { valid: false, errors: ["Invalid JSON in schema file"], checkedFields: 0, schemaTitle: undefined };
17+
}
18+
try {
19+
data = JSON.parse(dataContent) as Record<string, unknown>;
20+
} catch (e) {
21+
return { valid: false, errors: ["Invalid JSON in data file"], checkedFields: 0, schemaTitle: undefined };
22+
}
23+
const required = Array.isArray(schema["required"]) ? (schema["required"] as string[]) : [];
24+
const properties = (schema["properties"] ?? {}) as Record<string, { type?: string }>;
25+
let checkedFields = 0;
26+
for (const key of required) {
27+
checkedFields++;
28+
if (!(key in data)) {
29+
errors.push(`Missing required field: ${key}`);
30+
} else if (properties[key]?.type) {
31+
const expected = properties[key].type as string;
32+
const actual = Array.isArray(data[key]) ? "array" : typeof data[key];
33+
if (actual !== expected) {
34+
errors.push(`Field "${key}" expected type "${expected}" but got "${actual}"`);
35+
}
36+
}
37+
}
38+
return {
39+
valid: errors.length === 0,
40+
errors,
41+
checkedFields,
42+
schemaTitle: typeof schema["title"] === "string" ? schema["title"] : undefined,
43+
};
44+
},
45+
});
46+
47+
// Agent role: Validate a JSON data file against a JSON schema and report structural errors.
48+
const jsonSchemaStructureValidator = agent({
49+
model: "small",
50+
input: s.object({ schemaFile: s.path, dataFile: s.path }),
51+
instructions: p`Schema file content:
52+
${p.readInput("schemaFile")}
53+
54+
Data file content:
55+
${p.readInput("dataFile")}
56+
57+
Use the validateStructure tool with both file contents to check required fields and types. Return the validation result.`,
58+
tools: [validateStructure],
59+
output: s.object({
60+
valid: s.boolean,
61+
errors: s.array(s.string),
62+
checkedFields: s.int,
63+
schemaTitle: s.optional(s.string),
64+
}),
65+
addons: [repair()],
66+
});
67+
68+
export default jsonSchemaStructureValidator;
69+
```
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# 403 - Markdown Frontmatter Extractor
2+
3+
```rig
4+
import { agent, p, s, repair, defineTool } from "rig";
5+
6+
const parseFrontmatter = defineTool("parseFrontmatter", {
7+
description: "Parse YAML-style frontmatter from a markdown file path.",
8+
parameters: s.object({ filePath: s.path }),
9+
handler: async ({ filePath }: { filePath: string }) => {
10+
const { readFile } = await import("node:fs/promises");
11+
const content = await readFile(filePath, "utf8");
12+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
13+
if (!match) {
14+
return { hasFrontmatter: false, title: undefined, date: undefined, tags: undefined };
15+
}
16+
const fm = match[1];
17+
const titleMatch = fm.match(/^title:\s*(.+)$/m);
18+
const dateMatch = fm.match(/^date:\s*(.+)$/m);
19+
const tagsMatch = fm.match(/^tags:\s*\[([^\]]*)\]/m) ?? fm.match(/^tags:\s*\n((?: - .+\n?)*)/m);
20+
let tags: string[] | undefined;
21+
if (tagsMatch) {
22+
tags = tagsMatch[1].split(/[\n,]/).map((t: string) => t.replace(/^\s*-\s*/, "").trim()).filter(Boolean);
23+
}
24+
return {
25+
hasFrontmatter: true,
26+
title: titleMatch ? titleMatch[1].trim() : undefined,
27+
date: dateMatch ? dateMatch[1].trim() : undefined,
28+
tags,
29+
};
30+
},
31+
});
32+
33+
// Agent role: Extract YAML frontmatter metadata from all markdown files in the workspace.
34+
const markdownFrontmatterExtractor = agent({
35+
model: "small",
36+
instructions: p`Markdown files found:
37+
${p.glob("**/*.md")}
38+
39+
For each markdown file path listed above, call the parseFrontmatter tool with that file path. Return a record keyed by file path with the frontmatter fields.`,
40+
tools: [parseFrontmatter],
41+
output: s.record(
42+
s.object({
43+
title: s.optional(s.string),
44+
date: s.optional(s.string),
45+
tags: s.optional(s.array(s.string)),
46+
hasFrontmatter: s.boolean,
47+
})
48+
),
49+
addons: [repair()],
50+
});
51+
52+
export default markdownFrontmatterExtractor;
53+
```
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# 404 - Git Diff Word Frequency
2+
3+
```rig
4+
import { agent, p, s, defineTool } from "rig";
5+
6+
const countWordChanges = defineTool("countWordChanges", {
7+
description: "Count added and deleted words from git --word-diff=porcelain output.",
8+
parameters: s.object({ diff: s.string }),
9+
handler: ({ diff }: { diff: string }) => {
10+
const addedWords: Record<string, number> = {};
11+
const deletedWords: Record<string, number> = {};
12+
for (const line of diff.split("\n")) {
13+
if (line.startsWith("+") && !line.startsWith("+++")) {
14+
const word = line.slice(1).trim();
15+
if (word) addedWords[word] = (addedWords[word] ?? 0) + 1;
16+
} else if (line.startsWith("-") && !line.startsWith("---")) {
17+
const word = line.slice(1).trim();
18+
if (word) deletedWords[word] = (deletedWords[word] ?? 0) + 1;
19+
}
20+
}
21+
const topAdded = Object.entries(addedWords).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([w]) => w);
22+
const topDeleted = Object.entries(deletedWords).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([w]) => w);
23+
const mostFrequent = topAdded[0];
24+
return {
25+
topAddedWords: topAdded,
26+
topDeletedWords: topDeleted,
27+
totalAdditions: Object.values(addedWords).reduce((a, b) => a + b, 0),
28+
totalDeletions: Object.values(deletedWords).reduce((a, b) => a + b, 0),
29+
mostFrequentAddition: mostFrequent ?? null,
30+
};
31+
},
32+
});
33+
34+
// Agent role: Count added and deleted words in the current git diff and return the top words.
35+
const gitDiffWordFrequency = agent({
36+
model: "small",
37+
instructions: p`Word-diff output:
38+
${p.bash("git diff --word-diff=porcelain")}
39+
40+
Use the countWordChanges tool with the diff content above to count added and deleted words. Return the results.`,
41+
tools: [countWordChanges],
42+
output: s.object({
43+
topAddedWords: s.array(s.string),
44+
topDeletedWords: s.array(s.string),
45+
totalAdditions: s.int,
46+
totalDeletions: s.int,
47+
mostFrequentAddition: s.optional(s.string),
48+
}),
49+
});
50+
51+
export default gitDiffWordFrequency;
52+
```
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# 405 - Ts Async Function Finder
2+
3+
```rig
4+
import { agent, p, s, steering, defineTool } from "rig";
5+
6+
const scanAsyncFunctions = defineTool("scanAsyncFunctions", {
7+
description: "Scan a TypeScript file for async function signatures.",
8+
parameters: s.object({ filePath: s.path }),
9+
handler: async ({ filePath }: { filePath: string }) => {
10+
const { readFile } = await import("node:fs/promises");
11+
const content = await readFile(filePath, "utf8");
12+
const matches: string[] = [];
13+
const asyncFnRe = /async\s+function\s+(\w+)|const\s+(\w+)\s*=\s*async\s*(?:\([^)]*\)|[^=]+)\s*=>/g;
14+
let m: RegExpExecArray | null;
15+
while ((m = asyncFnRe.exec(content)) !== null) {
16+
const name = m[1] ?? m[2];
17+
if (name) matches.push(name);
18+
}
19+
return { functions: matches };
20+
},
21+
});
22+
23+
// Agent role: Find all async function signatures in TypeScript source files.
24+
const tsAsyncFunctionFinder = agent({
25+
model: "small",
26+
instructions: p`TypeScript files in this repository:
27+
${p.bash("find . -name '*.ts' -not -path '*/node_modules/*'")}
28+
29+
For each file path listed above, call the scanAsyncFunctions tool. Accumulate results into a record keyed by file path with the list of async function names. Return totalAsync (sum of all async functions) and mostAsyncFile (the file with the most async functions).`,
30+
tools: [scanAsyncFunctions],
31+
maxTurns: 5,
32+
output: s.object({
33+
files: s.record(s.array(s.string)),
34+
totalAsync: s.int,
35+
mostAsyncFile: s.optional(s.path),
36+
}),
37+
addons: [steering()],
38+
});
39+
40+
export default tsAsyncFunctionFinder;
41+
```
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# 406 - Npm Dep Depth Analyzer
2+
3+
```rig
4+
import { agent, p, s, repair, defineTool } from "rig";
5+
6+
const classifyDepDepth = defineTool("classifyDepDepth", {
7+
description: "Parse npm ls JSON output and classify each dependency by depth.",
8+
parameters: s.object({ npmLsJson: s.string }),
9+
handler: ({ npmLsJson }: { npmLsJson: string }) => {
10+
type DepEntry = { version?: string; dependencies?: Record<string, DepEntry> };
11+
const packages: Record<string, { depth: number; depthClass: "direct" | "transitive-shallow" | "transitive-deep" }> = {};
12+
let maxDepth = 0;
13+
let directCount = 0;
14+
function walk(node: DepEntry, depth: number): void {
15+
if (!node.dependencies) return;
16+
for (const [name, child] of Object.entries(node.dependencies)) {
17+
if (depth > maxDepth) maxDepth = depth;
18+
const depthClass: "direct" | "transitive-shallow" | "transitive-deep" =
19+
depth === 1 ? "direct" : depth === 2 ? "transitive-shallow" : "transitive-deep";
20+
if (!packages[name]) {
21+
packages[name] = { depth, depthClass };
22+
if (depth === 1) directCount++;
23+
}
24+
walk(child, depth + 1);
25+
}
26+
}
27+
try {
28+
const tree = JSON.parse(npmLsJson) as DepEntry;
29+
walk(tree, 1);
30+
} catch {
31+
// empty tree
32+
}
33+
return { packages, maxDepth, directCount };
34+
},
35+
});
36+
37+
// Agent role: Analyze npm dependency depth by reading package.json and running npm ls.
38+
const npmDepDepthAnalyzer = agent({
39+
model: "small",
40+
instructions: p`package.json:
41+
${p.read("package.json")}
42+
43+
npm dependency tree (JSON):
44+
${p.bash("npm ls --depth=3 --json 2>/dev/null || echo '{}'")}
45+
46+
Use the classifyDepDepth tool with the npm ls JSON output above. Return packages, maxDepth, and directCount.`,
47+
tools: [classifyDepDepth],
48+
output: s.object({
49+
packages: s.record(
50+
s.object({
51+
depth: s.int,
52+
depthClass: s.enum("direct", "transitive-shallow", "transitive-deep"),
53+
})
54+
),
55+
maxDepth: s.int,
56+
directCount: s.int,
57+
}),
58+
addons: [repair()],
59+
});
60+
61+
export default npmDepDepthAnalyzer;
62+
```
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# 407 - Http Access Log Stats
2+
3+
```rig
4+
import { agent, p, s, repair, defineTool } from "rig";
5+
6+
const parseLogLine = defineTool("parseLogLine", {
7+
description: "Parse a single combined log format HTTP access log line and classify the status code.",
8+
parameters: s.object({ line: s.string }),
9+
handler: ({ line }: { line: string }) => {
10+
const m = line.match(/^(\S+)\s+\S+\s+\S+\s+\[([^\]]+)\]\s+"(\w+)\s+(\S+)\s+\S+"\s+(\d+)/);
11+
if (!m) return { path: "", statusCode: 0, statusClass: "other" as const };
12+
const statusCode = parseInt(m[5], 10);
13+
const statusClass =
14+
statusCode >= 200 && statusCode < 300 ? "2xx" as const :
15+
statusCode >= 300 && statusCode < 400 ? "3xx" as const :
16+
statusCode >= 400 && statusCode < 500 ? "4xx" as const :
17+
statusCode >= 500 ? "5xx" as const : "other" as const;
18+
return { path: m[4], statusCode, statusClass };
19+
},
20+
});
21+
22+
// Agent role: Parse an HTTP access log file and return status count statistics.
23+
const httpAccessLogStats = agent({
24+
model: "small",
25+
input: s.object({ logFile: s.path }),
26+
instructions: p`Access log contents:
27+
${p.readInput("logFile")}
28+
29+
For each line in the log, use the parseLogLine tool to extract the path and status class. Aggregate into statusCounts (record of status class to count), topPaths (up to 10 most frequent paths), totalRequests, and errorRate (fraction of 4xx+5xx requests as a number between 0 and 1).`,
30+
tools: [parseLogLine],
31+
output: s.object({
32+
statusCounts: s.record(s.int),
33+
topPaths: s.array(s.string),
34+
totalRequests: s.int,
35+
errorRate: s.number,
36+
}),
37+
addons: [repair()],
38+
});
39+
40+
export default httpAccessLogStats;
41+
```

0 commit comments

Comments
 (0)