-
Notifications
You must be signed in to change notification settings - Fork 0
[rig-tasks] Add 10 rig samples — 2026-08-23 #475
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,40 @@ | ||
| # 451 - Shell Shebang Glob Validator | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool, repair } from "rig"; | ||
| import { readFile } from "node:fs/promises"; | ||
|
|
||
| const checkShebangLine = defineTool("checkShebangLine", { | ||
| description: "Check if a shell script file has a shebang line and whether it is standard", | ||
| 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 isStandard = hasShebang && (firstLine === "#!/bin/sh" || firstLine === "#!/bin/bash" || firstLine === "#!/usr/bin/env bash" || firstLine === "#!/usr/bin/env sh"); | ||
| return { hasShebang, shebangLine, isStandard }; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: Scan all shell scripts in the workspace and report shebang line status for each. | ||
| const shellShebangValidator = agent({ | ||
| model: "small", | ||
| instructions: p`You are a shell script auditor. The workspace contains these shell scripts: ${p.glob("**/*.sh")}. For each file path listed, call the checkShebangLine tool. Then return the full result object.`, | ||
| 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 shellShebangValidator; | ||
|
|
||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| # 452 - Git Log Graph Summarizer | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool, steering, repair } from "rig"; | ||
|
|
||
| const parseGraphLine = defineTool("parseGraphLine", { | ||
| description: "Classify a git log graph line as merge, commit, or branch-point", | ||
| parameters: s.object({ line: s.string }), | ||
| handler: ({ line }: { line: string }): "merge" | "commit" | "branch-point" => { | ||
| if (line.includes("Merge")) return "merge" as const; | ||
| if (/\*/.test(line) && /[0-9a-f]{7}/.test(line)) return "commit" as const; | ||
| return "branch-point" as const; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: Summarize the git log graph by classifying each line and counting merges, commits, and branch-points. | ||
| const gitLogGraphSummarizer = agent({ | ||
| model: "small", | ||
| instructions: p`Analyze the following git log graph output: ${p.bash("git log --oneline --graph -20")}. For each line, call parseGraphLine to classify it. Return the structured summary.`, | ||
| output: s.object({ | ||
| lines: s.array(s.object({ | ||
| type: s.enum("merge", "commit", "branch-point"), | ||
| hash: s.optional(s.string), | ||
| message: s.optional(s.string), | ||
| })), | ||
| mergeCount: s.int, | ||
| commitCount: s.int, | ||
| branchPoints: s.int, | ||
| }), | ||
| tools: [parseGraphLine], | ||
| addons: [steering(), repair()], | ||
| }); | ||
|
|
||
| export default gitLogGraphSummarizer; | ||
|
|
||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| # 453 - TypeScript Complexity Scorer | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool, repair } from "rig"; | ||
| import { readFile } from "node:fs/promises"; | ||
|
|
||
| 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 * 0.5 + callbacks * 0.5; | ||
| const complexity: "low" | "medium" | "high" = score < 5 ? "low" : score < 15 ? "medium" : "high"; | ||
| const topContributors: string[] = []; | ||
| if (nestedBraces > 0) topContributors.push(`nestedBraces:${nestedBraces}`); | ||
| if (ternaries > 0) topContributors.push(`ternaries:${ternaries}`); | ||
| if (callbacks > 0) topContributors.push(`callbacks:${callbacks}`); | ||
| return { score, complexity, topContributors }; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: Score complexity of all TypeScript source files in src/ and return a ranked summary. | ||
| const tsComplexityScorer = agent({ | ||
| model: "small", | ||
| instructions: p`Score the complexity of these TypeScript files: ${p.glob("src/**/*.ts")}. Call scoreFileComplexity for each file path. Then return the full result.`, | ||
| 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,41 @@ | ||
| # 454 - 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`Count files by extension using: ${p.bash("find . -type f -not -path './.git/*' | sed 's/.*\\.//' | sort | uniq -c | sort -rn | head -20")}. Return extCounts mapping each extension to its count.`, | ||
| output: s.object({ | ||
| extCounts: s.record(s.int), | ||
| }), | ||
| }); | ||
|
|
||
| // Agent role: Count environment variables grouped by prefix category. | ||
| const envHealthAgent = agent({ | ||
| model: "small", | ||
| instructions: p`Analyze environment variables using: ${p.bash("env | cut -d= -f1 | sed 's/_.*$//' | sort | uniq -c | sort -rn | head -20")}. Return categories mapping each prefix to its count.`, | ||
| output: s.object({ | ||
| categories: s.record(s.int), | ||
| }), | ||
| }); | ||
|
|
||
| // Workflow role: Run fileCountAgent and envHealthAgent then synthesize an overall health assessment. | ||
| const parallelMultiToolWorkflow = workflow({ | ||
| meta: { name: "parallel-multi-tool-workflow", description: "Run file count and env health agents and combine results" }, | ||
| body: async ({ call, phase }) => { | ||
| phase("Gather"); | ||
|
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 name 💡 Suggested fixEither rename to |
||
| const fileSummary = await call(fileCountAgent, "Analyze", { label: "file-count" }); | ||
| const envSummary = await call(envHealthAgent, "Analyze", { label: "env-health" }); | ||
| phase("Synthesize"); | ||
| const totalFiles = Object.values(fileSummary?.extCounts ?? {}).reduce((a: number, b: unknown) => a + (b as number), 0); | ||
| const totalEnv = Object.values(envSummary?.categories ?? {}).reduce((a: number, b: unknown) => a + (b as number), 0); | ||
| const overallHealth: "healthy" | "degraded" | "unknown" = totalFiles > 0 && totalEnv > 0 ? "healthy" : totalFiles > 0 || totalEnv > 0 ? "degraded" : "unknown"; | ||
| return { fileSummary: fileSummary ?? { extCounts: {} }, envSummary: envSummary ?? { categories: {} }, overallHealth }; | ||
| }, | ||
| }); | ||
|
|
||
| export default parallelMultiToolWorkflow; | ||
|
|
||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| # 455 - Zlib Compression Analyzer | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool, repair } from "rig"; | ||
| import { readFile } from "node:fs/promises"; | ||
| import { deflateSync } from "node:zlib"; | ||
|
|
||
| const measureCompressionRatio = defineTool("measureCompressionRatio", { | ||
| description: "Measure the zlib compression ratio of a file and classify it", | ||
| parameters: s.object({ filePath: s.path }), | ||
| handler: async ({ filePath }: { filePath: string }) => { | ||
| const buf = await readFile(filePath); | ||
| const originalSize = buf.length; | ||
| if (originalSize === 0) { | ||
| return { ratio: 1, compressionClass: "incompressible" as const, originalSize: 0, compressedSize: 0 }; | ||
| } | ||
| const compressed = deflateSync(buf); | ||
| const compressedSize = compressed.length; | ||
| const ratio = compressedSize / originalSize; | ||
| const compressionClass: "excellent" | "good" | "poor" | "incompressible" = | ||
| ratio < 0.3 ? "excellent" : ratio < 0.6 ? "good" : ratio < 0.9 ? "poor" : "incompressible"; | ||
| return { ratio, compressionClass, originalSize, compressedSize }; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: Analyze zlib compression ratios for files in targetDir and report compressibility. | ||
| const zlibCompressionAnalyzer = agent({ | ||
| model: "small", | ||
| input: s.object({ targetDir: s.string }), | ||
| instructions: p`List files to analyze using: ${p.bash("find . -maxdepth 3 -type f -not -path './.git/*' | head -30")}. Call measureCompressionRatio for each file path. Return the full analysis.`, | ||
|
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] 💡 Suggested fixEither remove the |
||
| output: s.object({ | ||
| files: s.record(s.object({ | ||
| ratio: s.number, | ||
| compressionClass: s.enum("excellent", "good", "poor", "incompressible"), | ||
| originalSize: s.int, | ||
| compressedSize: s.int, | ||
| })), | ||
| 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,38 @@ | ||
| # 456 - File Crypto Hash Reporter | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool, repair } from "rig"; | ||
| import { readFile } from "node:fs/promises"; | ||
| import { createHash } from "node:crypto"; | ||
|
|
||
| const computeFileHash = defineTool("computeFileHash", { | ||
| description: "Compute a cryptographic hash of a file", | ||
| parameters: s.object({ filePath: s.path, algorithm: s.optional(s.string) }), | ||
| handler: async ({ filePath, algorithm = "sha256" }: { filePath: string; algorithm?: string }) => { | ||
| const buf = await readFile(filePath); | ||
| const hash = createHash(algorithm).update(buf).digest("hex"); | ||
|
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] 💡 Suggested fixBound the glob or use a bash command with exclusions, similar to 455: instructions: p`Find files to hash: ${p.bash("find . -maxdepth 3 -type f -not -path './.git/*' -not -path './node_modules/*' | head -30")}`, |
||
| return { hash, sizeBytes: buf.length, algorithm }; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: Compute cryptographic hashes for all files in targetDir. | ||
| const fileCryptoHashReporter = agent({ | ||
| model: "small", | ||
|
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] Same issue as 455: 💡 Suggested fixFor |
||
| input: s.object({ targetDir: s.string, algorithm: s.optional(s.string) }), | ||
| instructions: p`Find files to hash in the workspace: ${p.glob("**/*")}. For each file, call computeFileHash. Use the algorithm from input if provided (default sha256). Return the result.`, | ||
| 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,49 @@ | ||
| # 457 - Source Line Length Auditor | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool, repair } from "rig"; | ||
| import { readFile } from "node:fs/promises"; | ||
|
|
||
| const auditFileLengths = defineTool("auditFileLengths", { | ||
| description: "Audit a file for lines exceeding the maximum length limit", | ||
| parameters: s.object({ filePath: s.path, maxLen: s.optional(s.number) }), | ||
| handler: async ({ filePath, maxLen = 100 }: { filePath: string; maxLen?: number }) => { | ||
| const content = await readFile(filePath, "utf8"); | ||
| const lines = content.split("\n"); | ||
| let longLineCount = 0; | ||
| let longestLine = 0; | ||
| let worstLineNumber = 0; | ||
| lines.forEach((line: string, idx: number) => { | ||
| if (line.length > maxLen) { | ||
| longLineCount++; | ||
| if (line.length > longestLine) { | ||
| longestLine = line.length; | ||
| worstLineNumber = idx + 1; | ||
| } | ||
| } | ||
| }); | ||
| return { longLineCount, longestLine, worstLineNumber }; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: Audit TypeScript source files for lines exceeding maxLen and report violations. | ||
| const sourceLineLengthAuditor = agent({ | ||
| model: "small", | ||
| input: s.object({ dir: s.string, maxLen: s.optional(s.number) }), | ||
| instructions: p`Audit these TypeScript files for long lines: ${p.glob("**/*.ts")}. Call auditFileLengths for each file, passing maxLen from input. Return the full audit.`, | ||
| output: s.object({ | ||
| files: s.record(s.object({ | ||
| longLineCount: s.int, | ||
| longestLine: s.int, | ||
| worstLineNumber: s.int, | ||
| })), | ||
| totalViolations: s.int, | ||
| worstFile: s.string, | ||
| }), | ||
| tools: [auditFileLengths], | ||
| addons: [repair()], | ||
| }); | ||
|
|
||
| export default sourceLineLengthAuditor; | ||
|
|
||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| # 458 - Workspace Symlink Inventory | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool } from "rig"; | ||
| import { readlink, realpath } from "node:fs/promises"; | ||
|
|
||
| const resolveSymlink = defineTool("resolveSymlink", { | ||
| description: "Resolve a symlink path and check if it is broken", | ||
| parameters: s.object({ symlinkPath: s.path }), | ||
| handler: async ({ symlinkPath }: { symlinkPath: string }) => { | ||
| const target = await readlink(symlinkPath).catch(() => ""); | ||
| if (!target) return { path: symlinkPath, target: "", broken: true }; | ||
| const broken = await realpath(symlinkPath).then(() => false).catch(() => true); | ||
| return { path: symlinkPath, target, broken }; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: Inventory all symlinks in the workspace and report whether each is broken. | ||
| const workspaceSymlinkInventory = agent({ | ||
| model: "small", | ||
| instructions: p`Find all symlinks in the workspace: ${p.bash("find . -type l 2>/dev/null | head -50")}. Call resolveSymlink for each path. Return the inventory.`, | ||
| output: s.object({ | ||
| symlinks: s.array(s.object({ | ||
| path: s.path, | ||
| target: s.string, | ||
| broken: s.boolean, | ||
| })), | ||
| totalSymlinks: s.int, | ||
| brokenCount: s.int, | ||
| }), | ||
| tools: [resolveSymlink], | ||
| }); | ||
|
|
||
| export default workspaceSymlinkInventory; | ||
|
|
||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| # 459 - XML Attribute Extractor | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool } from "rig"; | ||
|
|
||
| const parseXmlAttributes = defineTool("parseXmlAttributes", { | ||
| description: "Extract element names and their attribute names from XML content", | ||
| parameters: s.object({ xmlContent: s.string }), | ||
| handler: ({ xmlContent }: { xmlContent: string }) => { | ||
| const openTagPattern = /<([A-Za-z][A-Za-z0-9_:-]*)([^>]*?)(?:\/?>)/g; | ||
| const elements: Record<string, string[]> = {}; | ||
| let tagMatch: RegExpExecArray | null; | ||
| while ((tagMatch = openTagPattern.exec(xmlContent)) !== null) { | ||
| const tagName = tagMatch[1]; | ||
| const attrStr = tagMatch[2] ?? ""; | ||
| const attrs: string[] = []; | ||
| const ap = /([A-Za-z][A-Za-z0-9_:-]*)=/g; | ||
| let attrMatch: RegExpExecArray | null; | ||
| while ((attrMatch = ap.exec(attrStr)) !== null) { | ||
| attrs.push(attrMatch[1]); | ||
| } | ||
| if (!elements[tagName]) elements[tagName] = []; | ||
| attrs.forEach((a: string) => { | ||
| if (!elements[tagName].includes(a)) elements[tagName].push(a); | ||
| }); | ||
| } | ||
| const uniqueElements = Object.keys(elements).length; | ||
| const uniqueAttributes = new Set(Object.values(elements).flat()).size; | ||
| return { elements, uniqueElements, uniqueAttributes }; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: Extract all XML element names and their attribute names from the given XML file. | ||
| const xmlAttributeExtractor = agent({ | ||
| model: "small", | ||
| input: s.object({ xmlFile: s.string }), | ||
| instructions: p`Parse the XML file content: ${p.readInput("xmlFile")}. Call parseXmlAttributes with the full XML content. Return the extracted structure.`, | ||
| output: s.object({ | ||
| elements: s.record(s.array(s.string)), | ||
| uniqueElements: s.int, | ||
| uniqueAttributes: s.int, | ||
| }), | ||
| tools: [parseXmlAttributes], | ||
| }); | ||
|
|
||
| export default xmlAttributeExtractor; | ||
|
|
||
| ``` |
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.
[/grill-with-docs] The regex
/?[^:]+:/gfor ternary detection will false-positive on object literals, type annotations, and template strings — common in TypeScript. The scoring formula thus systematically over-counts complexity in type-heavy files.💡 Suggested fix
This is a sample, so perfect accuracy isn't required, but a brief comment acknowledging the limitation would help readers calibrate expectations: