-
Notifications
You must be signed in to change notification settings - Fork 0
[rig-tasks] Add 10 rig samples — 2026-08-21 #466
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| # 441 - Shell Shebang Glob Validator | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool } from "rig"; | ||
| import { readFile } from "node:fs/promises"; | ||
| import { repair } from "rig"; | ||
|
|
||
| const checkShebangLine = defineTool("checkShebangLine", { | ||
| description: "Check if a shell script has a valid shebang line", | ||
| parameters: s.object({ filePath: s.path }), | ||
| handler: async ({ filePath }: { filePath: string }) => { | ||
| const content = await readFile(filePath, "utf8"); | ||
| const firstLine = content.split("\n")[0] ?? ""; | ||
| const hasShebang = firstLine.startsWith("#!"); | ||
| const shebangLine = hasShebang ? firstLine : undefined; | ||
| const standardShebangs = ["/bin/sh", "/bin/bash", "/usr/bin/env bash", "/usr/bin/env sh"]; | ||
| const isStandard = hasShebang && standardShebangs.some(s => firstLine.includes(s)); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/codebase-design] The arrow-function parameter 💡 Suggested fixRename the parameter to avoid the collision: const isStandard = hasShebang && standardShebangs.some(sh => firstLine.includes(sh)); |
||
| return { hasShebang, shebangLine, isStandard }; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: Find all shell scripts and validate their shebang lines. | ||
| const shellShebangGlobValidator = agent({ | ||
| model: "small", | ||
| instructions: p`You have these shell script files: ${p.glob("**/*.sh")}. | ||
| For each file, call checkShebangLine to inspect its shebang. | ||
| Return all files with their shebang status, plus counts of missing and standard shebangs.`, | ||
| output: s.object({ | ||
| files: s.record(s.object({ | ||
| hasShebang: s.boolean, | ||
| shebangLine: s.optional(s.string), | ||
| isStandard: s.boolean, | ||
| })), | ||
| missingShebangCount: s.int, | ||
| standardShebangCount: s.int, | ||
| totalFiles: s.int, | ||
| }), | ||
| tools: [checkShebangLine], | ||
| addons: [repair()], | ||
| }); | ||
|
|
||
| export default shellShebangGlobValidator; | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| # 442 - Git Log Graph Summarizer | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool } from "rig"; | ||
| import { steering } from "rig"; | ||
|
|
||
| const parseGraphLine = defineTool("parseGraphLine", { | ||
| description: "Classify a git log graph line as merge, commit, branch-point, or other", | ||
| parameters: s.object({ line: s.string }), | ||
| handler: ({ line }: { line: string }) => { | ||
| if (/Merge/.test(line)) return "merge" as const; | ||
| if (/\*/.test(line) && /[0-9a-f]{6,}/.test(line)) return "commit" as const; | ||
| if (/[|\\\/]/.test(line) && !/[0-9a-f]{6,}/.test(line)) return "branch-point" as const; | ||
| return "other" as const; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: Summarize the git log graph by classifying each line. | ||
| const gitLogGraphSummarizer = agent({ | ||
| model: "small", | ||
| instructions: p`Analyze this git log graph: ${p.bash("git log --oneline --graph -20")}. | ||
| For each line, call parseGraphLine to classify it. | ||
| Return all classified lines with counts of merges, commits, and branch points.`, | ||
| output: s.object({ | ||
| lines: s.array(s.object({ | ||
| type: s.enum("merge", "commit", "branch-point", "other"), | ||
| hash: s.optional(s.string), | ||
| message: s.optional(s.string), | ||
| })), | ||
| mergeCount: s.int, | ||
| commitCount: s.int, | ||
| branchPoints: s.int, | ||
| }), | ||
| tools: [parseGraphLine], | ||
| addons: [steering()], | ||
| }); | ||
|
|
||
| export default gitLogGraphSummarizer; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| # 443 - TS Complexity Scorer | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool } from "rig"; | ||
| import { readFile } from "node:fs/promises"; | ||
| import { repair } from "rig"; | ||
|
|
||
| const scoreFileComplexity = defineTool("scoreFileComplexity", { | ||
| description: "Score the complexity of a TypeScript file by counting nested braces, ternaries, and callbacks", | ||
| parameters: s.object({ filePath: s.path }), | ||
| handler: async ({ filePath }: { filePath: string }) => { | ||
| const content = await readFile(filePath, "utf8"); | ||
| const nestedBraces = (content.match(/\{[^{}]*\{/g) ?? []).length; | ||
| const ternaries = (content.match(/\?[^:]+:/g) ?? []).length; | ||
| const callbacks = (content.match(/=>\s*\{/g) ?? []).length; | ||
| const score = nestedBraces + ternaries * 2 + callbacks; | ||
| const complexity = score < 10 ? "low" as const : score < 30 ? "medium" as const : "high" as const; | ||
| const topContributors: string[] = []; | ||
| if (nestedBraces > 0) topContributors.push(`nested braces: ${nestedBraces}`); | ||
| if (ternaries > 0) topContributors.push(`ternaries: ${ternaries}`); | ||
| if (callbacks > 0) topContributors.push(`callbacks: ${callbacks}`); | ||
| return { score, complexity, topContributors }; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: Score the complexity of all TypeScript source files. | ||
| const tsComplexityScorer = agent({ | ||
| model: "small", | ||
| instructions: p`You have these TypeScript files: ${p.glob("src/**/*.ts")}. | ||
| For each file, call scoreFileComplexity to get a complexity score. | ||
| Return per-file scores and classifications, the average score, and the most complex file.`, | ||
| output: s.object({ | ||
| files: s.record(s.object({ | ||
| score: s.number, | ||
| complexity: s.enum("low", "medium", "high"), | ||
| topContributors: s.array(s.string), | ||
| })), | ||
| averageScore: s.number, | ||
| mostComplexFile: s.optional(s.string), | ||
| }), | ||
| tools: [scoreFileComplexity], | ||
| addons: [repair()], | ||
| }); | ||
|
|
||
| export default tsComplexityScorer; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| # 444 - Parallel Multi Tool Workflow | ||
|
|
||
| ```rig | ||
| import { agent, workflow, p, s } from "rig"; | ||
|
|
||
| // Agent role: Count files grouped by extension in the workspace. | ||
| const fileCountAgent = agent({ | ||
| model: "small", | ||
| instructions: p`Run ${p.bash("find . -type f -name '*.*' | sed 's/.*\\.//' | sort | uniq -c | sort -rn")} and return extension counts as a record mapping extension to count.`, | ||
| output: s.object({ | ||
| extCounts: s.record(s.int), | ||
| }), | ||
| }); | ||
|
|
||
| // Agent role: Count environment variables and assess health. | ||
| const envHealthAgent = agent({ | ||
| model: "small", | ||
| instructions: p`Run ${p.bash("env | wc -l")} to count total environment variables. Return the count and whether healthy (count > 0).`, | ||
| output: s.object({ | ||
| totalEnvVars: s.int, | ||
| healthy: s.boolean, | ||
| }), | ||
| }); | ||
|
|
||
| // Agent role: Merge file and env summaries into an overall health report. | ||
| const coordinatorAgent = agent({ | ||
| model: "small", | ||
| input: s.object({ | ||
| fileSummary: s.record(s.int), | ||
| envTotalVars: s.int, | ||
| envHealthy: s.boolean, | ||
| }), | ||
| instructions: `Given fileSummary, envTotalVars, and envHealthy, produce an overall health assessment. | ||
| If envTotalVars > 0 and fileSummary has entries, overallHealth is healthy. | ||
| If one is empty, it is degraded. Otherwise unknown.`, | ||
| output: s.object({ | ||
| fileSummary: s.record(s.int), | ||
| envSummary: s.object({ totalEnvVars: s.int, healthy: s.boolean }), | ||
| overallHealth: s.enum("healthy", "degraded", "unknown"), | ||
| }), | ||
| }); | ||
|
|
||
| // Workflow role: Run file count and env health agents in parallel, then merge results. | ||
| const parallelMultiToolWorkflow = workflow({ | ||
| meta: { name: "parallel-multi-tool-workflow", description: "Run file count and env health checks in parallel, then merge results" }, | ||
| body: async ({ call }) => { | ||
| const [fileResult, envResult] = await Promise.all([ | ||
| call(fileCountAgent, "analyze file extensions"), | ||
| call(envHealthAgent, "analyze environment variables"), | ||
| ]); | ||
| return call(coordinatorAgent, { | ||
| fileSummary: fileResult?.extCounts ?? {}, | ||
| envTotalVars: envResult?.totalEnvVars ?? 0, | ||
| envHealthy: envResult?.healthy ?? false, | ||
| }); | ||
| }, | ||
| }); | ||
|
|
||
| export default parallelMultiToolWorkflow; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| # 445 - Zlib Compression Analyzer | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool } from "rig"; | ||
| import { readFile } from "node:fs/promises"; | ||
| import { deflateSync } from "node:zlib"; | ||
| import { repair } from "rig"; | ||
|
|
||
| const measureCompressionRatio = defineTool("measureCompressionRatio", { | ||
| description: "Measure the compression ratio of a file using zlib deflate", | ||
| parameters: s.object({ filePath: s.path }), | ||
| handler: async ({ filePath }: { filePath: string }) => { | ||
| const data = await readFile(filePath); | ||
| const originalSize = data.length; | ||
| if (originalSize === 0) { | ||
| return { originalSize: 0, compressedSize: 0, ratio: 0, compressionClass: "incompressible" as const }; | ||
| } | ||
| const compressed = deflateSync(data); | ||
| const compressedSize = compressed.length; | ||
| const ratio = 1 - compressedSize / originalSize; | ||
| const compressionClass = ratio > 0.5 ? "excellent" as const | ||
| : ratio > 0.3 ? "good" as const | ||
| : ratio > 0.1 ? "poor" as const | ||
| : "incompressible" as const; | ||
| return { originalSize, compressedSize, ratio, compressionClass }; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: Analyze file compression ratios in a target directory. | ||
| const zlibCompressionAnalyzer = agent({ | ||
| model: "small", | ||
| input: s.object({ targetDir: s.string }), | ||
| instructions: p`List files in the target directory: ${p.bash("find . -type f -size +0c")}. | ||
| For each file, call measureCompressionRatio to measure how compressible it is. | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/grill-with-docs] The agent declares 💡 Suggested fixEither use the input in the bash command via p.readInput, or remove the input field: instructions: p`List files in the target directory: ${p.bash("find ${p.readInput('targetDir')} -type f -size +0c")}`,Or drop the |
||
| Return per-file compression stats, total file count, average ratio, and the most compressible file.`, | ||
| output: s.object({ | ||
| files: s.record(s.object({ | ||
| originalSize: s.int, | ||
| compressedSize: s.int, | ||
| ratio: s.number, | ||
| compressionClass: s.enum("excellent", "good", "poor", "incompressible"), | ||
| })), | ||
| totalFiles: s.int, | ||
| averageRatio: s.number, | ||
| mostCompressibleFile: s.optional(s.string), | ||
| }), | ||
| tools: [measureCompressionRatio], | ||
| addons: [repair()], | ||
| }); | ||
|
|
||
| export default zlibCompressionAnalyzer; | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| # 446 - File Crypto Hash Reporter | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool } from "rig"; | ||
| import { readFile, stat } from "node:fs/promises"; | ||
| import { createHash } from "node:crypto"; | ||
| import { repair } from "rig"; | ||
|
|
||
| const computeFileHash = defineTool("computeFileHash", { | ||
| description: "Compute the cryptographic hash of a file", | ||
| parameters: s.object({ filePath: s.path, algorithm: s.string }), | ||
| handler: async ({ filePath, algorithm }: { filePath: string; algorithm: string }) => { | ||
| const data = await readFile(filePath); | ||
| const hash = createHash(algorithm).update(data).digest("hex"); | ||
| const info = await stat(filePath); | ||
| return { hash, sizeBytes: info.size, algorithm }; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: Compute cryptographic hashes for all files in a target directory. | ||
| const fileCryptoHashReporter = agent({ | ||
| model: "small", | ||
| input: s.object({ targetDir: s.string, algorithm: s.optional(s.string) }), | ||
| instructions: p`You have these files: ${p.glob("**/*")}. | ||
| For each file, call computeFileHash using the provided algorithm (default: sha256). | ||
| Return per-file hash, size, and algorithm info, plus totals.`, | ||
| output: s.object({ | ||
| files: s.record(s.object({ | ||
| hash: s.string, | ||
| sizeBytes: s.int, | ||
| algorithm: s.string, | ||
| })), | ||
| totalFiles: s.int, | ||
| algorithm: s.string, | ||
| }), | ||
| tools: [computeFileHash], | ||
| addons: [repair()], | ||
| }); | ||
|
|
||
| export default fileCryptoHashReporter; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| # 447 - XML Attribute Extractor | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool } from "rig"; | ||
| import { repair } from "rig"; | ||
|
|
||
| const extractXmlAttributes = defineTool("extractXmlAttributes", { | ||
| description: "Extract all elements and their attributes from XML/HTML content", | ||
| parameters: s.object({ xmlContent: s.string }), | ||
| handler: ({ xmlContent }: { xmlContent: string }) => { | ||
| const elements: Record<string, { attrs: Record<string, string>; attrCount: number }> = {}; | ||
| let totalAttributes = 0; | ||
| let totalElements = 0; | ||
| const elementPattern = /<(\w+)([^>]*)>/g; | ||
| const attrPattern = /(\w[\w-]*)=["']([^"']*)["']/g; | ||
| let elemMatch: RegExpExecArray | null; | ||
| while ((elemMatch = elementPattern.exec(xmlContent)) !== null) { | ||
| const tagName = elemMatch[1]; | ||
| const attrsStr = elemMatch[2] ?? ""; | ||
| const attrs: Record<string, string> = {}; | ||
| let attrMatch: RegExpExecArray | null; | ||
| const attrRe = new RegExp(attrPattern.source, "g"); | ||
| while ((attrMatch = attrRe.exec(attrsStr)) !== null) { | ||
| attrs[attrMatch[1]] = attrMatch[2]; | ||
| totalAttributes++; | ||
| } | ||
| const key = `${tagName}_${totalElements}`; | ||
| elements[key] = { attrs, attrCount: Object.keys(attrs).length }; | ||
| totalElements++; | ||
| } | ||
| return { elements, totalAttributes, totalElements }; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: Extract all XML element attributes from a given XML file. | ||
| const xmlAttributeExtractor = agent({ | ||
| model: "small", | ||
| input: s.object({ xmlFile: s.string }), | ||
| instructions: p`Read the XML file at the provided path: ${p.readInput("xmlFile")}. | ||
| Call extractXmlAttributes with the full file content. | ||
| Return all elements with their attributes, total attribute count, and element count.`, | ||
| output: s.object({ | ||
| elements: s.record(s.object({ | ||
| attrs: s.record(s.string), | ||
| attrCount: s.int, | ||
| })), | ||
| totalAttributes: s.int, | ||
| totalElements: s.int, | ||
| }), | ||
| tools: [extractXmlAttributes], | ||
| addons: [repair()], | ||
| }); | ||
|
|
||
| export default xmlAttributeExtractor; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| # 448 - Workspace Symlink Inventory | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool } from "rig"; | ||
| import { lstat, readlink } from "node:fs/promises"; | ||
| import { repair } from "rig"; | ||
|
|
||
| const resolveSymlink = defineTool("resolveSymlink", { | ||
| description: "Resolve a symlink and classify it as valid, broken, or relative", | ||
| parameters: s.object({ linkPath: s.path }), | ||
| handler: async ({ linkPath }: { linkPath: string }) => { | ||
| const target = await readlink(linkPath); | ||
| const isRelative = !target.startsWith("/"); | ||
| let status: "valid" | "broken" | "relative"; | ||
| try { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/diagnosing-bugs] The broken-link detection is incorrect. 💡 Suggested fixUse import { lstat, readlink, stat } from "node:fs/promises";
const target = await readlink(linkPath);
const isRelative = !target.startsWith("/");
let status: "valid" | "broken" | "relative";
try {
await stat(linkPath); // follows the link; throws if broken
status = isRelative ? "relative" : "valid";
} catch {
status = "broken";
} |
||
| await lstat(linkPath); | ||
| status = isRelative ? "relative" as const : "valid" as const; | ||
| } catch { | ||
| status = "broken" as const; | ||
| } | ||
| return { target, status, isRelative }; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: Inventory all symlinks in the workspace and classify their status. | ||
| const workspaceSymlinkInventory = agent({ | ||
| model: "small", | ||
| instructions: p`Find all symlinks: ${p.bash("find . -type l 2>/dev/null")}. | ||
| For each symlink path, call resolveSymlink to get its target and status. | ||
| Return per-link details plus total link count and broken link count.`, | ||
| output: s.object({ | ||
| links: s.record(s.object({ | ||
| target: s.string, | ||
| status: s.enum("valid", "broken", "relative"), | ||
| isRelative: s.boolean, | ||
| })), | ||
| totalLinks: s.int, | ||
| brokenCount: s.int, | ||
| }), | ||
| tools: [resolveSymlink], | ||
| addons: [repair()], | ||
| }); | ||
|
|
||
| export default workspaceSymlinkInventory; | ||
| ``` | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[/codebase-design] Two separate imports from
"rig"— consolidate them. The project convention (per AGENTS.md) is a single import from therigalias.💡 Suggested fix