-
Notifications
You must be signed in to change notification settings - Fork 0
[rig-tasks] Add 10 rig samples — 2026-07-28 #253
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,57 @@ | ||
| # 270 - Dotfile Inventory Mapper | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool, repair } from "rig"; | ||
|
|
||
| const categorizeDotfile = defineTool("categorizeDotfile", { | ||
| description: "Categorize a dotfile by its filename into a purpose and category.", | ||
| parameters: s.object({ filename: s.string }), | ||
| handler({ filename }) { | ||
| const f = filename.toLowerCase(); | ||
| if (f.includes("bash") || f.includes("zsh") || f.includes("fish") || f.includes("profile") || f.includes("aliases")) { | ||
| return { purpose: "Shell configuration and aliases", category: "shell" as const }; | ||
| } | ||
| if (f.includes("vim") || f.includes("emacs") || f.includes("vscode") || f.includes("editorconfig") || f.includes("nvim")) { | ||
| return { purpose: "Editor settings and configuration", category: "editor" as const }; | ||
| } | ||
| if (f.includes("git") || f.includes("gitconfig") || f.includes("gitignore")) { | ||
| return { purpose: "Git version control configuration", category: "git" as const }; | ||
| } | ||
| if (f.includes("ssh") || f.includes("gnupg") || f.includes("gpg")) { | ||
| return { purpose: "SSH or GPG security configuration", category: "ssh" as const }; | ||
| } | ||
| if (f.includes("npm") || f.includes("yarn") || f.includes("pip") || f.includes("cargo") || f.includes("gem")) { | ||
| return { purpose: "Package manager settings", category: "package-manager" as const }; | ||
| } | ||
| return { purpose: "Miscellaneous dotfile configuration", category: "misc" as const }; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: inventory dotfiles in the home directory and categorize them by purpose. | ||
| const dotfileInventoryMapper = agent({ | ||
| model: "small", | ||
| addons: repair(), | ||
| instructions: p`Inventory dotfiles found in the home directory. | ||
|
|
||
| Discovered dotfiles: | ||
| ${p.bash("find ~ -maxdepth 2 -name '.*' -type f 2>/dev/null | head -50")} | ||
|
|
||
| For each file path, call categorizeDotfile with just the filename (basename). | ||
| Build the dotfiles record keyed by filename with purpose and category. | ||
| Count totalFound (number of entries in dotfiles). | ||
| Build categorySummary as a record of category -> count of files in that category.`, | ||
| tools: [categorizeDotfile], | ||
| output: s.object({ | ||
| dotfiles: s.record( | ||
| s.object({ | ||
| purpose: s.string, | ||
| category: s.enum("shell", "editor", "git", "ssh", "package-manager", "misc"), | ||
| }) | ||
| ), | ||
| totalFound: s.int, | ||
| categorySummary: s.record(s.int), | ||
| }), | ||
| }); | ||
|
|
||
| export default dotfileInventoryMapper; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| # 271 - Regex Pattern Tester | ||
|
|
||
| ```rig | ||
| import { agent, s, defineTool, repair } from "rig"; | ||
|
|
||
| const runRegexTest = defineTool("runRegexTest", { | ||
| description: "Test a regex pattern against an input string and return whether it matches.", | ||
| parameters: s.object({ regex: s.string, input: s.string }), | ||
| handler({ regex, input }) { | ||
| try { | ||
| const matched = new RegExp(regex).test(input); | ||
| return { matched, error: undefined }; | ||
| } catch (e) { | ||
| return { matched: false, error: String(e) }; | ||
| } | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: run all test cases for each regex pattern and report pass/fail results. | ||
| const regexPatternTester = agent({ | ||
| model: "small", | ||
| addons: repair(), | ||
| input: s.object({ | ||
| patterns: s.array( | ||
| s.object({ | ||
| name: s.string, | ||
| regex: s.string, | ||
| testCases: s.array( | ||
| s.object({ input: s.string, shouldMatch: s.boolean }) | ||
| ), | ||
| }) | ||
| ), | ||
| }), | ||
| instructions: `For each pattern in the input, call runRegexTest for every test case. | ||
| A test case passes when the matched result equals shouldMatch. | ||
| Collect failedCases (input, expected, got) for each pattern. | ||
| A pattern passes when all test cases pass. | ||
| Count passCount (patterns where passed=true) and failCount. | ||
| allPassed is true when failCount === 0.`, | ||
| tools: [runRegexTest], | ||
| output: s.object({ | ||
| results: s.array( | ||
| s.object({ | ||
| name: s.string, | ||
| passed: s.boolean, | ||
| failedCases: s.array( | ||
| s.object({ input: s.string, expected: s.boolean, got: s.boolean }) | ||
| ), | ||
| }) | ||
| ), | ||
| passCount: s.int, | ||
| failCount: s.int, | ||
| allPassed: s.boolean, | ||
| }), | ||
| }); | ||
|
|
||
| export default regexPatternTester; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| # 272 - Commit Churn Classifier | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool, steering } from "rig"; | ||
|
|
||
| const classifyRisk = defineTool("classifyRisk", { | ||
| description: "Classify a file's churn risk level based on how many times it has changed.", | ||
| parameters: s.object({ churnCount: s.int }), | ||
| handler({ churnCount }) { | ||
| if (churnCount <= 3) return "stable" as const; | ||
| if (churnCount <= 10) return "active" as const; | ||
| if (churnCount <= 25) return "volatile" as const; | ||
| return "critical" as const; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: analyze git commit history to classify file churn risk. | ||
| const commitChurnClassifier = agent({ | ||
| model: "small", | ||
| addons: steering(), | ||
| instructions: p`Analyze file churn from git commit history. | ||
|
|
||
| File change frequency (count + filename): | ||
| ${p.bash("git log --name-only --format='' | grep -v '^$' | sort | uniq -c | sort -rn | head -30")} | ||
|
|
||
| For each line, parse the count and file path. | ||
| Call classifyRisk with the churnCount to get the riskLevel. | ||
| Build a record keyed by file path with churnCount and riskLevel. | ||
| Include only files that appear in the output above.`, | ||
| tools: [classifyRisk], | ||
| 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,43 @@ | ||
| # 273 - Npm Package Size Estimator | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool } from "rig"; | ||
|
|
||
| const classifySize = defineTool("classifySize", { | ||
| description: "Classify a package size into a tier based on kilobytes.", | ||
| parameters: s.object({ sizeKb: s.number }), | ||
| handler({ sizeKb }) { | ||
| 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 NPM package publish size and classify it. | ||
| const npmPackageSizeEstimator = agent({ | ||
| model: "small", | ||
| instructions: p`Estimate the published NPM package size. | ||
|
|
||
| npm pack dry-run output: | ||
| ${p.bash("npm pack --dry-run 2>&1")} | ||
|
|
||
| Parse the output to find: | ||
| 1. The total package size in KB (look for "package size:" or "Tarball Details" lines). | ||
| 2. The top individual files with their sizes. | ||
|
|
||
| Call classifySize with the estimatedSizeKb to get the sizeRating. | ||
| List the top files (up to 10) as topFiles array with path and sizeKb. | ||
| Write a brief recommendation about the package size.`, | ||
| tools: [classifySize], | ||
| output: s.object({ | ||
| estimatedSizeKb: s.number, | ||
| topFiles: s.array(s.object({ path: 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,56 @@ | ||
| # 274 - Ts Branch Coverage Analyzer | ||
|
|
||
| ```rig | ||
| import { agent, p, s } from "rig"; | ||
|
|
||
| // Agent role: analyze TypeScript file content to identify branches and their coverage. | ||
| const branchAnalyzer = agent({ | ||
| model: "small", | ||
| instructions: `Given the TypeScript file content below, identify all branch points. | ||
| For each branch, extract: functionName (surrounding function or "module-level"), line number, | ||
| branchType (if/ternary/switch/logical/nullish), and covered (true if the branch has a test or always executes). | ||
| Return the branches array.`, | ||
| input: s.object({ fileContent: s.string, filePath: s.string }), | ||
| output: s.array( | ||
| s.object({ | ||
| functionName: s.string, | ||
| line: s.int, | ||
| branchType: s.enum("if", "ternary", "switch", "logical", "nullish"), | ||
| covered: s.boolean, | ||
| }) | ||
| ), | ||
| }); | ||
|
|
||
| // Agent role: coordinate branch coverage analysis for a TypeScript file. | ||
| const tsBranchCoverageAnalyzer = agent({ | ||
| model: "small", | ||
| input: s.object({ filePath: s.string }), | ||
| instructions: p`Analyze TypeScript branch coverage for the file at the path provided in input. | ||
|
|
||
| Read the file content: | ||
| ${p.readInput("filePath")} | ||
|
|
||
| Delegate analysis to the branchAnalyzer subagent passing fileContent and filePath. | ||
| Aggregate the results: count totalBranches, coveredBranches, uncoveredBranches. | ||
| Compute coveragePercent as (coveredBranches / totalBranches * 100), 0 if no branches.`, | ||
| agents: { branchAnalyzer }, | ||
| output: s.object({ | ||
| branches: s.array( | ||
| s.object({ | ||
| functionName: s.string, | ||
| line: s.int, | ||
| branchType: s.enum("if", "ternary", "switch", "logical", "nullish"), | ||
| covered: s.boolean, | ||
| }) | ||
| ), | ||
| summary: s.object({ | ||
| totalBranches: s.int, | ||
| coveredBranches: s.int, | ||
| uncoveredBranches: s.int, | ||
| coveragePercent: s.number, | ||
| }), | ||
| }), | ||
| }); | ||
|
|
||
| export default tsBranchCoverageAnalyzer; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| # 275 - Env Variable Type Inferrer | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool } from "rig"; | ||
|
|
||
| const inferType = defineTool("inferType", { | ||
| description: "Infer the type of an environment variable value using regex patterns.", | ||
| parameters: s.object({ key: s.string, value: s.string }), | ||
| handler({ value }) { | ||
| if (/^\d+(\.\d+)?$/.test(value)) return "number" as const; | ||
| if (/^(true|false)$/i.test(value)) return "boolean" as const; | ||
| if (/^https?:\/\//.test(value)) return "url" as const; | ||
| if (/^[/~.]/.test(value)) return "path" as const; | ||
| if (value.length === 0) return "unknown" as const; | ||
| return "string" as const; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: read .env.example and infer the type of each variable. | ||
| const envVariableTypeInferrer = agent({ | ||
| model: "small", | ||
| instructions: p`Infer types for environment variables from .env.example. | ||
|
|
||
| File contents: | ||
| ${p.readOptional(".env.example", "# no .env.example found")} | ||
|
|
||
| For each non-comment, non-empty line, parse KEY=VALUE. | ||
| Call inferType with the key and value to determine the type. | ||
| Write a one-line description for each variable based on its key name and inferred type. | ||
| Build the variables record keyed by variable name. | ||
| Set allDocumented to true if all variables have non-empty descriptions.`, | ||
| tools: [inferType], | ||
| output: s.object({ | ||
| variables: 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,54 @@ | ||
| # 276 - Git Hook Installer | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool, repair } from "rig"; | ||
|
|
||
| const checkHooksDir = defineTool("checkHooksDir", { | ||
| description: "Check if the .git/hooks directory exists.", | ||
| parameters: s.object({}), | ||
| async handler() { | ||
| const { access } = await import("node:fs/promises"); | ||
| try { | ||
| await access(".git/hooks"); | ||
| return { exists: true }; | ||
| } catch { | ||
| return { exists: false }; | ||
| } | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: install git hook scripts into .git/hooks for the current repository. | ||
| const gitHookInstaller = agent({ | ||
| model: "small", | ||
| addons: repair(), | ||
| input: s.object({ | ||
| hooks: s.array( | ||
| s.object({ | ||
| name: s.string, | ||
| script: s.string, | ||
| description: s.string, | ||
| }) | ||
| ), | ||
| }), | ||
| instructions: p`Install git hook scripts into the .git/hooks directory. | ||
|
|
||
| Existing hooks directory: | ||
| ${p.bash("ls .git/hooks 2>/dev/null || echo 'no .git/hooks directory'")} | ||
|
|
||
| First call checkHooksDir to verify the directory exists. | ||
| For each hook in input.hooks: | ||
| - If .git/hooks exists, write the script to .git/hooks/<name> using a write operation, then run chmod +x via bash. | ||
| - If .git/hooks does not exist, add the hook name to skippedHooks. | ||
|
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 instructions say "write the script to Without those, the agent must hallucinate file I/O rather than using declared prompt intents. This undermines the sample's value as a teaching example for rig's 💡 Exampleinstructions: p`...
For each hook: ${p.write("${hook.name}", "${hook.script}")} then ${p.bash("chmod +x .git/hooks/${hook.name}")}(Adapt to the actual rig template tag syntax for dynamic values.) |
||
| Track writtenHooks as array of { name, path } for each successfully written hook. | ||
| Set totalWritten and allWritten accordingly.`, | ||
| tools: [checkHooksDir], | ||
| output: s.object({ | ||
| writtenHooks: s.array(s.object({ name: s.string, path: s.string })), | ||
| skippedHooks: s.array(s.string), | ||
| totalWritten: s.int, | ||
| allWritten: s.boolean, | ||
| }), | ||
| }); | ||
|
|
||
| export default gitHookInstaller; | ||
| ``` | ||
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
keyparameter is declared ininferType's schema but never used — the handler only operates onvalue.Removing it from the schema keeps the interface honest and avoids confusing future readers who might expect key-based heuristics (e.g.
DATABASE_URL→urleven when the value is empty).💡 Minimal fix
Or use
keyas a fallback: if value is empty but the key name containsURL, return"url".