-
Notifications
You must be signed in to change notification settings - Fork 0
[rig-tasks] Add 10 rig samples — 2026-07-25 #122
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,48 @@ | ||
| # 110 - Workspace Config Drift 3 | ||
|
|
||
| ```rig | ||
| import { agent, defineTool, p, s } from "rig"; | ||
| import { repair } from "rig/addons"; | ||
|
|
||
| // Agent role: detect drift in workspace config files against a baseline. | ||
| const workspaceConfigDrift = agent({ | ||
| model: "mini", | ||
| maxTurns: 6, | ||
| addons: repair(), | ||
| input: s.object({ | ||
| baselineFile: s.path, | ||
| }), | ||
| instructions: p`Read the baseline config from ${p.readInput("baselineFile")}. | ||
| Compare it against the current workspace configs: | ||
| - tsconfig.json: ${p.readOptional("tsconfig.json", "{}")} | ||
| - .eslintrc.json: ${p.readOptional(".eslintrc.json", "{}")} | ||
| - .prettierrc: ${p.readOptional(".prettierrc", "{}")} | ||
|
|
||
| Use the parseJson tool to parse each config. For each file, identify fields that differ from | ||
| the baseline. Return only the declared output.`, | ||
| tools: [ | ||
| defineTool("parseJson", { | ||
| description: "Parse a JSON string and return the keys", | ||
| parameters: s.object({ content: s.string }), | ||
| handler({ content }) { | ||
| try { | ||
| return Object.keys(JSON.parse(content)); | ||
| } catch { | ||
| return []; | ||
| } | ||
| }, | ||
| }), | ||
| ], | ||
| output: s.object({ | ||
| results: s.record( | ||
| s.object({ | ||
| driftedFields: s.array(s.string), | ||
| status: s.enum("ok", "warning", "error"), | ||
| }) | ||
| ), | ||
| overallStatus: s.enum("ok", "warning", "error"), | ||
| }), | ||
| }); | ||
|
|
||
| export default workspaceConfigDrift; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| # 111 - Conventional Commit Suggester | ||
|
|
||
| ```rig | ||
| import { agent, p, s } from "rig"; | ||
| import { repair, steering } from "rig/addons"; | ||
|
|
||
| // Agent role: suggest conventional-format rewrites for recent git commit messages. | ||
| const conventionalCommitSuggester = agent({ | ||
| model: "mini", | ||
| maxTurns: 6, | ||
| addons: [steering({ message: "Ensure each suggestion follows the conventional commits spec: <type>(<scope>?): <description> using imperative mood." }), repair()], | ||
| instructions: p`Review the recent git log and suggest conventional commit message rewrites: | ||
| ${p.bash("git log --oneline -20")} | ||
|
|
||
| For each commit, identify the best category (feat/fix/chore/docs/test/refactor/style) and | ||
| rewrite the message to follow the conventional commits spec in imperative mood. | ||
| Write the full report to output. Return only the declared output.`, | ||
| output: s.object({ | ||
| suggestions: s.array( | ||
| s.object({ | ||
| hash: s.string, | ||
| original: s.string, | ||
| suggested: s.string, | ||
| category: s.enum("feat", "fix", "chore", "docs", "test", "refactor", "style"), | ||
| }) | ||
| ), | ||
| totalReviewed: s.int, | ||
| }), | ||
| }); | ||
|
|
||
| export default conventionalCommitSuggester; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| # 112 - Runtime Env Health | ||
|
|
||
| ```rig | ||
| import { agent, defineTool, p, s } from "rig"; | ||
|
|
||
| // Agent role: inspect the runtime environment and report health status. | ||
| const runtimeEnvHealth = agent({ | ||
| model: "mini", | ||
| instructions: p`Inspect the runtime environment using the collected info: | ||
| Node.js version: ${p.bash("node --version")} | ||
| OS info: ${p.bash("uname -a")} | ||
| Memory: ${p.bash("free -m 2>/dev/null || vm_stat 2>/dev/null || echo 'unknown'")} | ||
|
|
||
| Use the checkThreshold tool to validate Node.js version (minimum v18) and available memory | ||
| (minimum 256 MB). Return only the declared output.`, | ||
| tools: [ | ||
| defineTool("checkThreshold", { | ||
| description: "Check if a numeric value meets a minimum threshold", | ||
| parameters: s.object({ | ||
| value: s.number, | ||
| minimum: s.number, | ||
| label: s.string, | ||
| }), | ||
| handler({ value, minimum, label }) { | ||
| return { label, ok: value >= minimum, value, minimum }; | ||
| }, | ||
| }), | ||
| ], | ||
| output: s.object({ | ||
| nodeVersion: s.string, | ||
| os: s.string, | ||
| memoryMb: s.number, | ||
| health: s.enum("ok", "degraded", "critical"), | ||
| issues: s.array(s.string), | ||
| }), | ||
| }); | ||
|
|
||
| export default runtimeEnvHealth; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| # 113 - Git Hotspot Analyzer | ||
|
|
||
| ```rig | ||
| import { agent, p, s } from "rig"; | ||
| import { steering } from "rig/addons"; | ||
|
|
||
| // Agent role: identify hot-spot files by git churn and top contributors. | ||
| const gitHotspotAnalyzer = agent({ | ||
| model: "mini", | ||
| addons: steering({ message: "Focus only on the top N files by change frequency. Skip binary files and node_modules." }), | ||
| input: s.object({ | ||
| topN: s.int, | ||
| }), | ||
| instructions: p`Analyze git history to find the most frequently changed files. | ||
|
|
||
| Changed files from git log: | ||
| ${p.bash("git log --follow --name-only --format='' -- . | sort | uniq -c | sort -rn | head -20")} | ||
|
|
||
| For the top files identified, get contributor info: | ||
| ${p.bash("git shortlog -sn --all -- . 2>/dev/null | head -10")} | ||
|
|
||
| Select the top \${input.topN} files by churn score. For each file compute a churnScore | ||
| (number of commits) and list topContributors. Return only the declared output as a record | ||
| keyed by file path.`, | ||
| output: s.record( | ||
| s.object({ | ||
| churnScore: s.number, | ||
| topContributors: s.array(s.string), | ||
| }) | ||
| ), | ||
| }); | ||
|
|
||
| export default gitHotspotAnalyzer; | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| # 114 - Loc Statistics Gatherer | ||
|
|
||
| ```rig | ||
| import { agent, defineTool, p, s } from "rig"; | ||
|
|
||
| // Agent role: gather lines-of-code statistics per file extension. | ||
| const locStatisticsGatherer = agent({ | ||
| model: "mini", | ||
| instructions: p`Count lines of code by file extension in this workspace. | ||
|
|
||
| Source files found: | ||
| ${p.bash("find . -not -path '*/node_modules/*' -not -path '*/.git/*' \\( -name '*.ts' -o -name '*.js' -o -name '*.py' -o -name '*.go' -o -name '*.rs' \\) 2>/dev/null | head -200")} | ||
|
|
||
| Line counts: | ||
| ${p.bash("find . -not -path '*/node_modules/*' -not -path '*/.git/*' \\( -name '*.ts' -o -name '*.js' -o -name '*.py' \\) -exec wc -l {} + 2>/dev/null | tail -5")} | ||
|
|
||
| Use the classifyComplexity tool to determine overall complexity. Aggregate results by extension. | ||
| Return only the declared output.`, | ||
| tools: [ | ||
| defineTool("classifyComplexity", { | ||
| description: "Classify total line count into complexity bucket", | ||
| parameters: s.object({ totalLines: s.int }), | ||
| handler({ totalLines }) { | ||
| if (totalLines < 1000) return "small"; | ||
| if (totalLines < 10000) return "medium"; | ||
| if (totalLines < 100000) return "large"; | ||
| return "xlarge"; | ||
| }, | ||
| }), | ||
| ], | ||
| output: s.object({ | ||
| byExtension: s.record(s.object({ lineCount: s.int, fileCount: s.int })), | ||
| totalLines: s.int, | ||
| complexity: s.enum("small", "medium", "large", "xlarge"), | ||
| }), | ||
| }); | ||
|
|
||
| export default locStatisticsGatherer; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| # 115 - Import Cycle Detector | ||
|
|
||
| ```rig | ||
| import { agent, p, s } from "rig"; | ||
| import { repair } from "rig/addons"; | ||
|
|
||
| // Agent role: detect circular import cycles in the TypeScript project. | ||
| const importCycleDetector = agent({ | ||
| model: "mini", | ||
| maxTurns: 6, | ||
| addons: repair(), | ||
| instructions: p`Detect circular imports in this TypeScript project. | ||
|
|
||
| TypeScript config: | ||
| ${p.bash("cat tsconfig.json 2>/dev/null || echo '{}'")} | ||
|
|
||
| Circular imports (via madge): | ||
| ${p.bash("npx --yes madge --circular --extensions ts . 2>/dev/null || echo 'madge not available or no cycles found'")} | ||
|
|
||
| Alternatively, check for potential cycles by scanning imports: | ||
| ${p.bash("grep -r --include='*.ts' 'from.*\\.' . 2>/dev/null | grep -v node_modules | head -50")} | ||
|
|
||
| Identify any circular import chains. For each cycle, classify severity: | ||
| - high: cycles in core/shared modules | ||
| - medium: cycles in feature modules | ||
| - low: cycles in utility or test files | ||
|
|
||
| Return only the declared output.`, | ||
| output: s.object({ | ||
| hasCycles: s.boolean, | ||
| cycles: s.array( | ||
| s.object({ | ||
| path: s.array(s.string), | ||
| severity: s.enum("high", "medium", "low"), | ||
| }) | ||
| ), | ||
| cycleCount: s.int, | ||
| }), | ||
| }); | ||
|
|
||
| export default importCycleDetector; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| # 116 - Barrel File Generator | ||
|
|
||
| ```rig | ||
| import { agent, defineTool, p, s } from "rig"; | ||
|
|
||
| // Agent role: generate barrel index.ts files for TypeScript source directories. | ||
| const barrelFileGenerator = agent({ | ||
| model: "mini", | ||
| input: s.object({ | ||
| srcDir: s.path, | ||
| }), | ||
| instructions: p`Generate barrel index.ts files for TypeScript source directories. | ||
|
|
||
| Source files found (excluding index.ts): | ||
| ${p.bash("find . -name '*.ts' ! -name 'index.ts' ! -name '*.test.ts' ! -name '*.spec.ts' -not -path '*/node_modules/*' 2>/dev/null | head -100")} | ||
|
|
||
| Use the classifyExports tool to identify exported symbols in files. Then generate barrel | ||
| content for each directory that has exported symbols. Write each barrel file using p.write. | ||
|
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 reference 💡 Suggested fixEither:
As a sample, misleading prose about side effects can confuse users trying to learn from the pattern. |
||
|
|
||
| Count the total number of files scanned, barrel files that would be written, export count, | ||
| and directories involved. Return only the declared output.`, | ||
| tools: [ | ||
| defineTool("classifyExports", { | ||
| description: "Scan a TypeScript file path and detect export patterns", | ||
| parameters: s.object({ filePath: s.string }), | ||
| async handler({ filePath }) { | ||
| const { execSync } = await import("node:child_process"); | ||
| try { | ||
| const out = execSync(`grep -n "^export " "${filePath}" 2>/dev/null || true`, { encoding: "utf-8" }); | ||
| return { filePath, exportLines: out.trim().split("\n").filter(Boolean).length }; | ||
| } catch { | ||
| return { filePath, exportLines: 0 }; | ||
| } | ||
| }, | ||
| }), | ||
| ], | ||
| output: s.object({ | ||
| filesScanned: s.int, | ||
| barrelFilesWritten: s.int, | ||
| exportCount: s.int, | ||
| directories: s.array(s.string), | ||
| }), | ||
| }); | ||
|
|
||
| export default barrelFileGenerator; | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| # 117 - Git Hook Inventory | ||
|
|
||
| ```rig | ||
| import { agent, defineTool, p, s } from "rig"; | ||
|
|
||
| // Agent role: inventory git hooks and classify each as active, stub, or missing. | ||
| const gitHookInventory = agent({ | ||
| model: "mini", | ||
| instructions: p`Inventory the git hooks in this repository. | ||
|
|
||
| Available hooks in .git/hooks: | ||
| ${p.bash("ls .git/hooks/ 2>/dev/null || echo 'no hooks directory'")} | ||
|
|
||
| Sample hooks content: | ||
| ${p.bash("for f in .git/hooks/pre-commit .git/hooks/commit-msg .git/hooks/pre-push; do echo \"=== $f ===\"; cat \"$f\" 2>/dev/null || echo 'missing'; done")} | ||
|
|
||
| Use the classifyHook tool to classify each hook. Return a record keyed by hook name | ||
| with status, summary, and whether it runs asynchronously (uses & or async patterns). | ||
| Return only the declared output.`, | ||
| tools: [ | ||
| defineTool("classifyHook", { | ||
| description: "Classify a git hook by its content", | ||
| parameters: s.object({ name: s.string, content: s.string }), | ||
| handler({ content }) { | ||
| if (!content || content === "missing") return { status: "missing", isAsync: false }; | ||
| const isSample = content.includes("sample") || content.trim() === "#!/bin/sh"; | ||
| const isAsync = content.includes(" &") || content.includes("async"); | ||
| return { status: isSample ? "stub" : "active", isAsync }; | ||
| }, | ||
| }), | ||
|
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 💡 Suggested fixEither include handler({ name, content }) {
if (!content || content === "missing") return { status: "missing", isAsync: false, summary: "not installed" };
const isSample = content.includes("sample") || content.trim() === "#!/bin/sh";
const isAsync = content.includes(" &") || content.includes("async");
return { status: isSample ? "stub" : "active", isAsync, summary: isSample ? "sample hook" : name };
} |
||
| ], | ||
| output: s.record( | ||
| s.object({ | ||
| summary: s.string, | ||
| status: s.enum("active", "stub", "missing"), | ||
| isAsync: s.boolean, | ||
| }) | ||
| ), | ||
| }); | ||
|
|
||
| export default gitHookInventory; | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| # 118 - Pr Review Checklist | ||
|
|
||
| ```rig | ||
| import { agent, p, s } from "rig"; | ||
| import { repair } from "rig/addons"; | ||
|
|
||
| // Agent role: generate a PR review checklist from the recent git diff. | ||
| const prReviewChecklist = agent({ | ||
| model: "mini", | ||
| maxTurns: 6, | ||
| addons: repair(), | ||
| instructions: p`Generate a PR review checklist based on the changes in this branch. | ||
|
|
||
| Changed files: | ||
| ${p.bash("git diff --name-only HEAD~1 2>/dev/null || git diff --name-only HEAD 2>/dev/null || echo 'no diff available'")} | ||
|
|
||
| Diff summary: | ||
| ${p.bash("git diff --stat HEAD~1 2>/dev/null || git diff --stat HEAD 2>/dev/null || echo 'no diff stats'")} | ||
|
|
||
| Key changes: | ||
| ${p.bash("git diff HEAD~1 -- . 2>/dev/null | head -200 || echo 'no diff content'")} | ||
|
|
||
| For each concern in the changes, produce a checklist item with: | ||
| - A clear action item description | ||
| - Category (test/docs/security/performance/style) | ||
| - Priority (high/medium/low) | ||
|
|
||
| Also list the changed files and indicate if the PR is ready for review. | ||
| Return only the declared output.`, | ||
| output: s.object({ | ||
| checklist: s.array( | ||
| s.object({ | ||
| item: s.string, | ||
| category: s.enum("test", "docs", "security", "performance", "style"), | ||
| priority: s.enum("high", "medium", "low"), | ||
| }) | ||
| ), | ||
| changedFiles: s.array(s.string), | ||
| ready: s.boolean, | ||
| }), | ||
| }); | ||
|
|
||
| export default prReviewChecklist; | ||
| ``` |
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]
\${input.topN}renders as a literal string in the prompt — the caller-suppliedtopNlimit is never injected into the model's instructions, so it is silently ignored.💡 Suggested fix
Inject the value through the steering message or via an
s.intdescription. SincetopNis a number (not a path),p.readInputwon't work here — instead, include it explicitly in thesteeringaddon message or in the agent description:Or pass
topNthrough the instructions string by having the harness resolve it, if that pattern is supported by the runtime. Until then the backslash escape means the model always sees the literal text${input.topN}rather than e.g.5.