-
Notifications
You must be signed in to change notification settings - Fork 0
[rig-tasks] Add 10 rig samples — 2026-07-25 #114
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,18 @@ | ||
| # 88 - Git Contributor Mapper | ||
|
|
||
| ```rig | ||
| import { agent, p, s } from "rig"; | ||
|
|
||
| // Agent role: map git contributors to their commit counts, primary work areas, and role classification. | ||
| const gitContributorMapper = agent({ | ||
| model: "small", | ||
| instructions: p`Analyze git contributors using: ${p.bash("git shortlog -sn --no-merges")} and ${p.bash("git log --no-merges --name-only --pretty=format:'%an' | head -500")}. For each contributor, count their commits, identify which directories they primarily touch, and classify their role as core (many commits across many files), peripheral (few commits or limited scope), or single-file.`, | ||
| output: s.record(s.object({ | ||
| commitCount: s.number, | ||
| primaryAreas: s.array(s.string), | ||
| role: s.enum("core", "peripheral", "single-file"), | ||
| })), | ||
| }); | ||
|
|
||
| export default gitContributorMapper; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| # 89 - Markdown Doc Summarizer | ||
|
|
||
| ```rig | ||
| import { agent, p, s } from "rig"; | ||
|
|
||
| // Agent role: summarize each top-level section of a markdown doc, then compile into a full report. | ||
| const sectionSummarizer = agent({ | ||
| name: "sectionSummarizer", | ||
| model: "nano", | ||
| instructions: p`Summarize the section of documentation provided in the input.`, | ||
| input: s.object({ heading: s.string, content: s.string }), | ||
| output: s.object({ heading: s.string, summary: s.string }), | ||
| }); | ||
|
|
||
| // Agent role: read the project README, delegate per-section summarization, and write the final report. | ||
| const markdownDocSummarizer = agent({ | ||
| model: "small", | ||
| instructions: p`Read the README: ${p.readOptional("README.md", "No README found.")}. Identify each top-level heading (##) and its content. Delegate summarization of each section to the sectionSummarizer agent. Compile all summaries into a report and write it to summaries/README-summary.md via ${p.writeOutput("reportPath", "summaries/README-summary.md")}.`, | ||
| output: s.object({ | ||
| sections: s.array(s.object({ heading: s.string, summary: s.string })), | ||
| reportPath: s.string, | ||
| }), | ||
| agents: { sectionSummarizer }, | ||
| }); | ||
|
|
||
| export default markdownDocSummarizer; | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| # 90 - Workspace Config Drift | ||
|
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] This sample is a verbatim duplicate of 💡 SuggestionEither delete this and replace with a genuinely distinct sample, or differentiate it meaningfully (e.g. add Duplicate samples dilute the sample library and confuse users learning by example. |
||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool } from "rig"; | ||
| import { repair } from "rig/addons"; | ||
|
|
||
| const parseJson = defineTool("parseJson", { | ||
| description: "Parse a JSON string and return it, or report a parse error", | ||
| parameters: s.object({ content: s.string, filename: s.string }), | ||
| handler({ content, filename }) { | ||
| try { | ||
| const parsed = JSON.parse(content); | ||
| return { ok: true, parsed }; | ||
| } catch (e) { | ||
| return { ok: false, error: String(e), filename }; | ||
| } | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: detect drift in workspace config files by reading them and comparing against known defaults. | ||
| const workspaceConfigDrift = agent({ | ||
| model: "small", | ||
| instructions: p`Read project config files: ${p.readOptional("tsconfig.json", "{}")} (tsconfig.json), ${p.readOptional(".eslintrc.json", "{}")} (.eslintrc.json), ${p.readOptional(".prettierrc", "{}")} (.prettierrc). Use the parseJson tool to parse each file. For each config, identify fields that deviate from sensible defaults and report them as drifted. Assign status ok if no drift, warning for minor issues, error for significant mismatches.`, | ||
| output: s.record(s.object({ | ||
| driftedFields: s.array(s.string), | ||
| status: s.enum("ok", "warning", "error"), | ||
| })), | ||
| tools: [parseJson], | ||
| maxTurns: 4, | ||
| addons: repair(), | ||
| }); | ||
|
|
||
| export default workspaceConfigDrift; | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| # 91 - Commit Format Suggester | ||
|
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] This sample is a near-verbatim duplicate of 💡 SuggestionReplace this with a distinct sample that covers a new concept not already in the library. If the intent was to refine the commit-format pattern, update |
||
|
|
||
| ```rig | ||
| import { agent, p, s } from "rig"; | ||
| import { repair, steering } from "rig/addons"; | ||
|
|
||
| // Agent role: review recent git commits and suggest conventional-format rewrites for each one. | ||
| const commitFormatSuggester = agent({ | ||
| model: "small", | ||
| instructions: p`Review recent git commits: ${p.bash("git log --oneline -20 --no-merges")}. For each commit, check whether its message follows conventional commit format (type: description). Suggest a rewritten message in conventional format. Classify each commit as one of: feat, fix, chore, docs, test, refactor, style. Write the full report to commit-report.md via ${p.writeOutput("reportWritten", "commit-report.md")}.`, | ||
| output: s.array(s.object({ | ||
| hash: s.string, | ||
| original: s.string, | ||
| suggested: s.string, | ||
| category: s.enum("feat", "fix", "chore", "docs", "test", "refactor", "style"), | ||
| })), | ||
| maxTurns: 5, | ||
| addons: [steering(), repair()], | ||
|
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] 💡 FixEither remove addons: [steering({ message: "Ensure each suggested commit message uses conventional commit format: type(scope): description." }), repair()],A |
||
| }); | ||
|
|
||
| export default commitFormatSuggester; | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| # 92 - Runtime Env Checker | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool } from "rig"; | ||
|
|
||
| const checkThresholds = defineTool("checkThresholds", { | ||
| description: "Validate environment values against minimum thresholds and return a list of issues", | ||
| parameters: s.object({ | ||
| nodeVersion: s.string, | ||
| heapMB: s.number, | ||
| }), | ||
| handler({ nodeVersion, heapMB }) { | ||
| const issues: string[] = []; | ||
| const majorVersion = parseInt(nodeVersion.replace("v", "").split(".")[0], 10); | ||
| if (majorVersion < 18) issues.push(`Node.js ${nodeVersion} is below minimum v18`); | ||
| if (heapMB < 256) issues.push(`Heap ${heapMB}MB is below 256MB minimum`); | ||
| return { issues }; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: inspect the runtime environment and report overall health. | ||
| const runtimeEnvChecker = agent({ | ||
| model: "small", | ||
| instructions: p`Inspect the runtime environment using: ${p.bash("node --version")}, ${p.bash("uname -a")}, and ${p.bash("node -e \"console.log(Math.round(process.memoryUsage().heapTotal/1024/1024))\"")}. Use the checkThresholds tool to validate versions and memory. Determine overall health as ok, degraded, or critical based on issues found.`, | ||
| output: s.object({ | ||
| health: s.enum("ok", "degraded", "critical"), | ||
| nodeVersion: s.string, | ||
| os: s.string, | ||
| heapMB: s.number, | ||
| issues: s.array(s.string), | ||
| }), | ||
| tools: [checkThresholds], | ||
| }); | ||
|
|
||
| export default runtimeEnvChecker; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| # 93 - Hotspot File Analyzer | ||
|
|
||
| ```rig | ||
| import { agent, p, s } from "rig"; | ||
| import { steering } from "rig/addons"; | ||
|
|
||
| // Agent role: analyze which source files are hot-spots by measuring churn and top contributors. | ||
| const hotspotFileAnalyzer = agent({ | ||
| model: "small", | ||
| instructions: p`Analyze file churn in this repository. Get recently changed files using ${p.bash("git log --name-only --format='' HEAD~100..HEAD | sort | uniq -c | sort -rn | head -30")} and contributor data using ${p.bash("git shortlog -sn --no-merges HEAD~100..HEAD")}. For each hot-spot file, compute a churnScore 0–100 based on how often it changes, list topContributors, and classify riskLevel as low, medium, or high.`, | ||
| output: s.record(s.object({ | ||
| churnScore: s.number, | ||
| topContributors: s.array(s.string), | ||
| riskLevel: s.enum("low", "medium", "high"), | ||
| })), | ||
| maxTurns: 5, | ||
| addons: steering({ message: "Ensure every file entry has a numeric churnScore and at least one topContributor." }), | ||
| }); | ||
|
|
||
| export default hotspotFileAnalyzer; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| # 94 - Ts Interface Conflict Checker | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool } from "rig"; | ||
| import { repair } from "rig/addons"; | ||
|
|
||
| const scanInterfaces = defineTool("scanInterfaces", { | ||
| description: "Scan a TypeScript file for exported interface names using grep", | ||
| parameters: s.object({ filePath: s.string }), | ||
| async handler({ filePath }) { | ||
| const { execSync } = await import("node:child_process"); | ||
| try { | ||
| const result = execSync( | ||
| `grep -n "^export interface\\|^interface " "${filePath}" 2>/dev/null || true`, | ||
| { encoding: "utf8" } | ||
| ); | ||
| const names = result | ||
| .split("\n") | ||
| .filter(Boolean) | ||
| .map((line) => { | ||
| const m = line.match(/interface\s+(\w+)/); | ||
| return m ? m[1] : null; | ||
| }) | ||
| .filter(Boolean) as string[]; | ||
| return { filePath, names }; | ||
| } catch { | ||
| return { filePath, names: [] }; | ||
| } | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: find duplicate TypeScript interface names across all source files in the project. | ||
| const tsInterfaceConflictChecker = agent({ | ||
| model: "small", | ||
| instructions: p`Find all TypeScript source files using ${p.bash("find . -name '*.ts' -not -path '*/node_modules/*' | head -80")}. Use the scanInterfaces tool on each file to collect interface names. Identify any interface name declared in more than one file. Classify each conflict as warning (same name, compatible) or error (likely clash). Set hasConflicts to true if any conflicts exist.`, | ||
| output: s.object({ | ||
| conflicts: s.array(s.object({ | ||
| interfaceName: s.string, | ||
| files: s.array(s.string), | ||
| severity: s.enum("warning", "error"), | ||
| })), | ||
| hasConflicts: s.boolean, | ||
| }), | ||
| tools: [scanInterfaces], | ||
| maxTurns: 6, | ||
| addons: repair(), | ||
| }); | ||
|
|
||
| export default tsInterfaceConflictChecker; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| # 95 - Git Worktree Mapper | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool } from "rig"; | ||
|
|
||
| const parseWorktreePorcelain = defineTool("parseWorktreePorcelain", { | ||
| description: "Parse the output of git worktree list --porcelain into structured entries", | ||
| parameters: s.object({ output: s.string }), | ||
| handler({ output }) { | ||
| const entries: Array<{ path: string; branch?: string; state: string }> = []; | ||
| const blocks = output.trim().split("\n\n"); | ||
| for (const block of blocks) { | ||
| const lines = block.split("\n"); | ||
| const pathLine = lines.find((l) => l.startsWith("worktree ")); | ||
| const branchLine = lines.find((l) => l.startsWith("branch ")); | ||
| const isLocked = lines.some((l) => l.startsWith("locked")); | ||
| const isBare = lines.some((l) => l.startsWith("bare")); | ||
| const entry: { path: string; branch?: string; state: string } = { | ||
| path: pathLine ? pathLine.replace("worktree ", "") : "", | ||
| state: isLocked ? "locked" : isBare ? "bare" : "clean", | ||
| }; | ||
| if (branchLine) entry.branch = branchLine.replace("branch refs/heads/", ""); | ||
| if (entry.path) entries.push(entry); | ||
| } | ||
| return entries; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: list and classify all git worktrees in the current repository. | ||
| const gitWorktreeMapper = agent({ | ||
| model: "small", | ||
| instructions: p`List all git worktrees using ${p.bash("git worktree list --porcelain")}. Use the parseWorktreePorcelain tool to parse the output. Determine the state of each worktree (locked, bare, clean, or dirty if there are uncommitted changes). Provide a summary with totalCount and activeCount (non-bare worktrees).`, | ||
| output: s.object({ | ||
| worktrees: s.array(s.object({ | ||
| path: s.string, | ||
| branch: s.optional(s.string), | ||
| state: s.enum("locked", "bare", "clean", "dirty"), | ||
| })), | ||
|
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 output schema declares 💡 FixEither:
As-is, the LLM is told to classify dirty worktrees but has no tool mechanism to detect them — it will guess. |
||
| summary: s.object({ | ||
| totalCount: s.number, | ||
| activeCount: s.number, | ||
| }), | ||
| }), | ||
| tools: [parseWorktreePorcelain], | ||
| }); | ||
|
|
||
| export default gitWorktreeMapper; | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| # 96 - Test Naming Enforcer | ||
|
|
||
| ```rig | ||
| import { agent, p, s } from "rig"; | ||
| import { steering } from "rig/addons"; | ||
|
|
||
| // Agent role: audit test file naming conventions and report files that violate the standard pattern. | ||
| const testNamingEnforcer = agent({ | ||
| model: "small", | ||
| instructions: p`Find all test files in this project using ${p.bash("find . \\( -name '*.test.ts' -o -name '*.spec.ts' -o -name '*.test.js' -o -name '*.spec.js' \\) -not -path '*/node_modules/*' | head -60")}. For each file, check whether it follows the convention of <subject>.test.ts or <subject>.spec.ts. Classify as correct if it matches, wrong-prefix if the name before the extension separator is unusual, wrong-suffix if it ends differently, or missing-spec if it should be a test file but lacks the marker. Suggest a corrected name where applicable. Set allConform to true only if every file is classified as correct.`, | ||
| output: s.object({ | ||
| files: s.record(s.object({ | ||
| convention: s.enum("correct", "wrong-prefix", "wrong-suffix", "missing-spec"), | ||
| suggestedName: s.optional(s.string), | ||
| })), | ||
| allConform: s.boolean, | ||
| }), | ||
| maxTurns: 5, | ||
| addons: steering({ message: "Ensure every discovered test file has an entry in files and allConform is a boolean." }), | ||
| }); | ||
|
|
||
| export default testNamingEnforcer; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| # 97 - Pkg Dependency Graph | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool } from "rig"; | ||
|
|
||
| const classifyDependency = defineTool("classifyDependency", { | ||
| description: "Classify a dependency as runtime, dev, or peer based on its presence in package.json sections", | ||
| parameters: s.object({ | ||
| name: s.string, | ||
| inDependencies: s.boolean, | ||
| inDevDependencies: s.boolean, | ||
| inPeerDependencies: s.boolean, | ||
| }), | ||
| handler({ inDependencies, inDevDependencies, inPeerDependencies }) { | ||
|
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 💡 Fixhandler({ name, inDependencies, inDevDependencies, inPeerDependencies }) {
// use name if you want to log or return it, otherwise remove it from parameters
}Either remove |
||
| if (inPeerDependencies) return "peer"; | ||
| if (inDevDependencies) return "dev"; | ||
| if (inDependencies) return "runtime"; | ||
| return "dev"; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: extract and classify direct dependencies from package.json, then describe the overall tree shape. | ||
| const pkgDependencyGraph = agent({ | ||
| model: "small", | ||
| instructions: p`Read the project manifest: ${p.read("package.json")}. Get the resolved dependency tree using ${p.bash("npm ls --json --depth=1 2>/dev/null || echo '{}'")}. Use the classifyDependency tool to classify each direct dependency as runtime, dev, or peer. Also list all devDependency names. Estimate the treeShape as flat (<5 deps), shallow (5–20), or deep (>20) and set depthScore to the total direct dependency count.`, | ||
|
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 💡 SuggestionReturn a richer object from the tool so the LLM can use it to build the output without needing to remember package names independently: handler({ name, inDependencies, inDevDependencies, inPeerDependencies }) {
const type = inPeerDependencies ? "peer" : inDevDependencies ? "dev" : inDependencies ? "runtime" : "dev";
return { name, type };
}This makes the tool a reliable anchor rather than a pure classification oracle. |
||
| output: s.object({ | ||
| directDeps: s.array(s.object({ | ||
| name: s.string, | ||
| version: s.string, | ||
| type: s.enum("runtime", "dev", "peer"), | ||
| })), | ||
| devDeps: s.array(s.string), | ||
| treeShape: s.enum("flat", "shallow", "deep"), | ||
| depthScore: s.number, | ||
| }), | ||
| tools: [classifyDependency], | ||
| }); | ||
|
|
||
| export default pkgDependencyGraph; | ||
| ``` | ||
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]
p.readOptionalis used here, butREADME.mdis inlined directly into the main agent's instructions. This means the entire file content is injected into the prompt at construction time — not at delegation time — which could balloon token usage for large READMEs before the agent even begins section extraction.💡 Suggestion
Consider passing
README.mdasinputto the orchestrator agent instead, so the caller controls what document is summarized and the sample is more reusable:This also makes the sample demonstrate
p.readInput— a distinct pattern not yet shown by surrounding samples.