-
Notifications
You must be signed in to change notification settings - Fork 0
[rig-tasks] Add 10 rig samples — 2026-07-28 #261
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,31 @@ | ||
| # 280 - Commit Churn Classifier | ||
|
|
||
| ```rig | ||
| import { agent, p, s, steering } from "rig"; | ||
|
|
||
| // Agent role: classify repository files by commit churn frequency and assign a risk level. | ||
| const commitChurnClassifier = agent({ | ||
| model: "small", | ||
| addons: steering({ message: "Be precise: assign riskLevel based on churnCount: >20=critical, >10=volatile, >5=active, else stable." }), | ||
| instructions: p`Classify repository files by how frequently they are committed (churn). | ||
|
|
||
| File churn counts from last 200 commits: | ||
| ${p.bash("git log --name-only --format='' HEAD~200..HEAD 2>/dev/null | grep -v '^$' | sort | uniq -c | sort -rn | head -40")} | ||
|
|
||
| For each file, parse the churn count and assign a riskLevel: | ||
| - critical: churnCount > 20 | ||
| - volatile: churnCount > 10 | ||
| - active: churnCount > 5 | ||
| - stable: churnCount <= 5 | ||
|
|
||
| Return a record keyed by file path with churnCount (integer) and riskLevel.`, | ||
| output: s.record( | ||
| s.object({ | ||
| churnCount: s.int, | ||
| riskLevel: s.enum("stable", "active", "volatile", "critical"), | ||
| }) | ||
| ), | ||
| }); | ||
|
|
||
| export default commitChurnClassifier; | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| # 281 - NPM Package Size Estimator | ||
|
|
||
| ```rig | ||
| import { agent, defineTool, p, s } from "rig"; | ||
|
|
||
| const classifySize = defineTool("classifySize", { | ||
| description: "Classify a package size in KB into a tier rating.", | ||
| parameters: s.object({ sizeKb: s.number }), | ||
| handler({ sizeKb }: { sizeKb: number }) { | ||
| if (sizeKb < 10) return "tiny" as const; | ||
| if (sizeKb < 100) return "small" as const; | ||
| if (sizeKb < 500) return "medium" as const; | ||
| if (sizeKb < 2000) return "large" as const; | ||
| return "xlarge" as const; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: estimate the NPM package size and rate it by tier. | ||
| const npmPackageSizeEstimator = agent({ | ||
| model: "small", | ||
| instructions: p`Estimate the NPM package size for this project. | ||
|
|
||
| Pack dry-run output: | ||
| ${p.bash("npm pack --dry-run 2>&1 | tail -20")} | ||
|
|
||
| Directory size: | ||
| ${p.bash("du -sh . 2>/dev/null | head -5")} | ||
|
|
||
| Use classifySize tool with the estimated total size in KB to get the sizeRating. | ||
| List the top files by size and provide a brief recommendation.`, | ||
| tools: [classifySize], | ||
| output: s.object({ | ||
| estimatedSizeKb: s.number, | ||
| topFiles: s.array(s.object({ name: s.string, sizeKb: s.number })), | ||
| sizeRating: s.enum("tiny", "small", "medium", "large", "xlarge"), | ||
| recommendation: s.string, | ||
| }), | ||
| }); | ||
|
|
||
| export default npmPackageSizeEstimator; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| # 282 - TS Branch Coverage Analyzer | ||
|
|
||
| ```rig | ||
| import { agent, p, s } from "rig"; | ||
|
|
||
| const branchItem = s.object({ | ||
| functionName: s.string, | ||
| line: s.int, | ||
| branchType: s.enum("if", "ternary", "switch", "nullish"), | ||
| covered: s.boolean, | ||
| }); | ||
|
|
||
| // Agent role: extract all branch points from the provided TypeScript source code. | ||
| const branchAnalyzer = agent({ | ||
| model: "small", | ||
| instructions: p`Analyze the TypeScript source code provided in the input and list every branch point (if/ternary/switch/nullish coalescing). For each branch, identify the enclosing function name, line number, branch type, and whether it appears covered (has a test or usage path). Return an array of branch objects.`, | ||
|
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 💡 FixFollow the existing pattern from sample 159 ( instructions: p`Analyze the TypeScript source code:
${p.readInput("filePath")}
For each branch...`,Without this the subagent receives no file content; its |
||
| input: s.object({ filePath: s.path }), | ||
| output: s.array(branchItem), | ||
| }); | ||
|
|
||
| // Agent role: coordinate branch coverage analysis for a TypeScript file. | ||
| const tsBranchCoverageAnalyzer = agent({ | ||
| model: "small", | ||
| input: s.object({ filePath: s.path }), | ||
| instructions: p`Perform branch coverage analysis on a TypeScript file. | ||
|
|
||
| File content: | ||
| ${p.readInput("filePath")} | ||
|
|
||
| Delegate the analysis to the branchAnalyzer subagent, passing the filePath. | ||
| Collect its output (an array of branch objects), then compute a summary with totalBranches, coveredBranches, and uncoveredBranches counts.`, | ||
| output: s.object({ | ||
| branches: s.array(branchItem), | ||
| summary: s.object({ | ||
| totalBranches: s.int, | ||
| coveredBranches: s.int, | ||
| uncoveredBranches: s.int, | ||
| }), | ||
| }), | ||
| agents: { branchAnalyzer }, | ||
| }); | ||
|
|
||
| export default tsBranchCoverageAnalyzer; | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| # 283 - Env Variable Type Inferrer | ||
|
|
||
| ```rig | ||
| import { agent, defineTool, p, s } from "rig"; | ||
|
|
||
| const inferVarType = defineTool("inferVarType", { | ||
| description: "Infer the type of an environment variable value using regex heuristics.", | ||
| parameters: s.object({ value: s.string }), | ||
| handler({ value }: { value: string }) { | ||
| if (/^https?:\/\//i.test(value)) return "url" as const; | ||
| if (/^(true|false|yes|no|1|0)$/i.test(value)) return "boolean" as const; | ||
| if (/^\d+(\.\d+)?$/.test(value)) return "number" as const; | ||
| if (/^(\/|\.\/|~\/)/.test(value) || /\.(txt|json|yaml|yml|pem|key|crt)$/.test(value)) return "path" as const; | ||
| if (value === "") return "unknown" as const; | ||
| return "string" as const; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: infer types for all environment variables defined in .env.example. | ||
| const envVariableTypeInferrer = agent({ | ||
| model: "small", | ||
| instructions: p`Infer the type of each environment variable defined in the .env.example file. | ||
|
|
||
| .env.example contents: | ||
| ${p.readOptional(".env.example", "(no .env.example found)")} | ||
|
|
||
| For each KEY=VALUE line, call inferVarType with the value to get its type. Generate a short description of what each variable likely controls. Set allDocumented to true only if every variable has a non-empty value or inline comment hint.`, | ||
| tools: [inferVarType], | ||
| output: s.object({ | ||
| vars: s.record( | ||
| s.object({ | ||
| type: s.enum("string", "number", "boolean", "url", "path", "unknown"), | ||
| description: s.string, | ||
| }) | ||
| ), | ||
| allDocumented: s.boolean, | ||
| }), | ||
| }); | ||
|
|
||
| export default envVariableTypeInferrer; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| # 284 - Makefile Target Extractor | ||
|
|
||
| ```rig | ||
| import { agent, defineTool, p, s, repair } from "rig"; | ||
|
|
||
| const parseTargets = defineTool("parseTargets", { | ||
| description: "Parse Makefile content to extract phony and real targets with descriptions.", | ||
| parameters: s.object({ content: s.string }), | ||
| handler({ content }: { content: string }) { | ||
| const lines = content.split("\n"); | ||
| const phonySet = new Set<string>(); | ||
| const targets: Array<{ name: string; isPhony: boolean; hasHelp: boolean; description?: string }> = []; | ||
|
|
||
| for (const line of lines) { | ||
| const phonyMatch = line.match(/^\.PHONY\s*:\s*(.+)/); | ||
| if (phonyMatch) { | ||
| for (const t of phonyMatch[1].split(/\s+/)) phonySet.add(t.trim()); | ||
| } | ||
| } | ||
|
|
||
| const targetRe = /^([a-zA-Z0-9_\-./]+)\s*:/; | ||
| for (let i = 0; i < lines.length; i++) { | ||
| const m = lines[i].match(targetRe); | ||
| if (m && !m[1].startsWith(".")) { | ||
| const name = m[1]; | ||
| const helpLine = i > 0 ? lines[i - 1] : ""; | ||
| const hasHelp = /##/.test(helpLine) || /##/.test(lines[i]); | ||
| const description = helpLine.match(/##\s*(.+)/)?.[1]?.trim(); | ||
| targets.push({ name, isPhony: phonySet.has(name), hasHelp, ...(description ? { description } : {}) }); | ||
| } | ||
| } | ||
| return targets; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: extract and classify Makefile targets. | ||
| const makefileTargetExtractor = agent({ | ||
| model: "small", | ||
| addons: repair(), | ||
| instructions: p`Extract and classify targets from the project Makefile. | ||
|
|
||
| Makefile contents: | ||
| ${p.readOptional("Makefile", "(no Makefile found)")} | ||
|
|
||
| Call parseTargets with the full Makefile content. Return the array of targets and include totalCount (all targets) and phonyCount (targets where isPhony is true).`, | ||
| tools: [parseTargets], | ||
| output: s.object({ | ||
| targets: s.array( | ||
| s.object({ | ||
| name: s.string, | ||
| isPhony: s.boolean, | ||
| hasHelp: s.boolean, | ||
| description: s.optional(s.string), | ||
| }) | ||
| ), | ||
| totalCount: s.int, | ||
| phonyCount: s.int, | ||
| }), | ||
| }); | ||
|
|
||
| export default makefileTargetExtractor; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| # 285 - Test Fixture Generator | ||
|
|
||
| ```rig | ||
| import { agent, p, s } from "rig"; | ||
|
|
||
| // Agent role: analyze a function signature and return its parameters and return type. | ||
| const signatureAnalyzer = agent({ | ||
| model: "small", | ||
| instructions: p`Analyze the TypeScript source provided in the input. Extract the signature details for the specified function: its parameters (name and type), return type, and any JSDoc description. Return structured signature details.`, | ||
|
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 💡 FixThe subagent needs to read the file to extract the signature: instructions: p`Analyze the TypeScript source:
${p.readInput("sourceFile")}
Extract the signature of the function named in the input...`,The parent agent already reads the source at line 26, but the subagent doesn't — it will produce hallucinated signatures. |
||
| input: s.object({ sourceFile: s.path, functionName: s.string }), | ||
| output: s.object({ | ||
| functionName: s.string, | ||
| parameters: s.array(s.object({ name: s.string, type: s.string })), | ||
| returnType: s.string, | ||
| description: s.optional(s.string), | ||
| }), | ||
| }); | ||
|
|
||
| // Agent role: generate a test fixture for a specific function in a TypeScript source file. | ||
| const testFixtureGenerator = agent({ | ||
| model: "small", | ||
| input: s.object({ sourceFile: s.path, functionName: s.string }), | ||
| instructions: p`Generate a test fixture file for the specified function. | ||
|
|
||
| Source file content: | ||
| ${p.readInput("sourceFile")} | ||
|
|
||
| 1. Delegate to signatureAnalyzer to get the function signature for the function named in the input. | ||
| 2. Using the signature, generate fixture code: example inputs, mock helpers, and a vitest describe block. | ||
| 3. Write the fixture code to fixture.ts: ${p.writeOutput("fixtureCode", "fixture.ts")} | ||
|
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] 💡 SuggestionUse a dynamic path or make Alternatively, remove |
||
| 4. Return fixtureCode (the full fixture source), the required imports, and suggestedFileName.`, | ||
| output: s.object({ | ||
| fixtureCode: s.string, | ||
| imports: s.array(s.string), | ||
| suggestedFileName: s.path, | ||
| }), | ||
| agents: { signatureAnalyzer }, | ||
| }); | ||
|
|
||
| export default testFixtureGenerator; | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| # 286 - TOML Config Analyzer | ||
|
|
||
| ```rig | ||
| import { agent, defineTool, p, s, repair } from "rig"; | ||
|
|
||
| const analyzeTomlFile = defineTool("analyzeTomlFile", { | ||
| description: "Read a TOML file and extract its top-level section headers and key count.", | ||
| parameters: s.object({ filePath: s.path }), | ||
| async handler({ filePath }: { filePath: string }) { | ||
| const { readFile } = await import("node:fs/promises"); | ||
| try { | ||
| const content = await readFile(filePath, "utf8"); | ||
| const sections = [...content.matchAll(/^\[([^\]]+)\]/gm)].map((m: RegExpMatchArray) => m[1] as string); | ||
| const keyCount = (content.match(/^\s*[a-zA-Z_][a-zA-Z0-9_]*\s*=/gm) ?? []).length; | ||
| const hasRequired = sections.includes("package") || sections.includes("tool") || keyCount > 0; | ||
| return { sections, keyCount, hasRequired }; | ||
| } catch { | ||
| return { sections: [], keyCount: 0, hasRequired: false }; | ||
| } | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: analyze all TOML config files in the workspace and summarize their sections. | ||
| const tomlConfigAnalyzer = agent({ | ||
| model: "small", | ||
| addons: repair(), | ||
| instructions: p`Analyze all TOML config files found in the workspace. | ||
|
|
||
| TOML files found: | ||
| ${p.glob("**/*.toml")} | ||
|
|
||
| For each file path listed above, call analyzeTomlFile to extract its sections, keyCount, and hasRequired flag. Return a record keyed by file path.`, | ||
| tools: [analyzeTomlFile], | ||
| output: s.record( | ||
| s.object({ | ||
| sections: s.array(s.string), | ||
| keyCount: s.int, | ||
| hasRequired: s.boolean, | ||
| }) | ||
| ), | ||
| }); | ||
|
|
||
| export default tomlConfigAnalyzer; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| # 287 - Git Checkpoint Summarizer | ||
|
|
||
| ```rig | ||
| import { agent, defineTool, p, s } from "rig"; | ||
|
|
||
| const classifyCheckpoint = defineTool("classifyCheckpoint", { | ||
| description: "Classify a git checkpoint line as stash, commit, tag, or branch.", | ||
| parameters: s.object({ line: s.string }), | ||
| handler({ line }: { line: string }) { | ||
| if (line.startsWith("stash@{")) return "stash" as const; | ||
| if (/^refs\/tags\//.test(line)) return "tag" as const; | ||
| if (/^refs\/heads\//.test(line)) return "branch" as const; | ||
| return "commit" as const; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: summarize git checkpoints (stashes and recent commits) in the repository. | ||
| const gitCheckpointSummarizer = agent({ | ||
| model: "small", | ||
| instructions: p`Summarize the git checkpoints (stashes and recent commits) for this repository. | ||
|
|
||
| Stash list: | ||
| ${p.bash("git stash list 2>/dev/null || echo '(no stashes)'")} | ||
|
|
||
| Recent commits: | ||
| ${p.bash("git log --oneline -20 2>/dev/null || echo '(no commits)'")} | ||
|
|
||
| For each stash entry and each commit line, call classifyCheckpoint to determine its type. Build the checkpoints array with ref, type, and message fields. Set latestCheckpoint to the most recent commit hash or stash ref, and totalCount to the combined count.`, | ||
| tools: [classifyCheckpoint], | ||
| output: s.object({ | ||
| checkpoints: s.array( | ||
| s.object({ | ||
| ref: s.string, | ||
| type: s.enum("stash", "commit", "tag", "branch"), | ||
| message: s.string, | ||
| }) | ||
| ), | ||
| latestCheckpoint: s.optional(s.string), | ||
| totalCount: s.int, | ||
| }), | ||
| }); | ||
|
|
||
| export default gitCheckpointSummarizer; | ||
| ``` |
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] The
steeringmessage duplicates the classification thresholds that are already spelled out ininstructions. If the thresholds ever change, they'd need to be updated in two places.💡 Suggestion
Either remove the steering addon and rely on the clear rules in
instructions, or keep only the steering message and shorten the instructions. Compare sample 113 (git-hotspot-analyzer.md) where steering is used for emphasis, not duplication.If the steering is genuinely needed to keep the LLM on-spec, consider combining repair with a single concise steering message that doesn't restate the full threshold table.