-
Notifications
You must be signed in to change notification settings - Fork 0
[rig-tasks] Add 10 rig samples — 2026-07-28 #242
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,51 @@ | ||
| # 250 - Git Commit Annotator | ||
|
|
||
| ```rig | ||
| import { agent, p, s } from "rig"; | ||
|
|
||
| // Agent role: summarize a single git commit line into a category and impact rating | ||
| const commitSummarizer = agent({ | ||
| name: "commitSummarizer", | ||
| model: "small", | ||
| instructions: p`Analyze the git commit message provided in the input and classify it. | ||
|
|
||
| Commit info: ${p.inputField("commitLine")} | ||
|
|
||
| Classify into category (feat, fix, chore, docs, refactor, test, perf) and rate impact (low, medium, high).`, | ||
| input: s.object({ commitLine: s.string }), | ||
| output: s.object({ | ||
| category: s.enum("feat", "fix", "chore", "docs", "refactor", "test", "perf"), | ||
| impact: s.enum("low", "medium", "high"), | ||
| }), | ||
| }); | ||
|
|
||
| // Agent role: annotate recent git commits with category and impact via subagent delegation, writing a markdown report | ||
| const gitCommitAnnotator = agent({ | ||
| name: "gitCommitAnnotator", | ||
| model: "small", | ||
| agents: { commitSummarizer }, | ||
| instructions: p`Annotate recent git commits using the commitSummarizer subagent. | ||
|
|
||
| Recent commits (hash + message): | ||
| ${p.bash("git log --oneline -20")} | ||
|
|
||
| For each commit line, delegate to commitSummarizer with the full commit line as commitLine. | ||
| Collect the category and impact for every commit. | ||
| Build a markdown report table with columns: hash, message, category, impact. | ||
| Write the report to commit-annotations.md. | ||
| ${p.writeOutput("report", "commit-annotations.md")}`, | ||
| output: s.object({ | ||
| annotations: s.array( | ||
| s.object({ | ||
| hash: s.string, | ||
| message: s.string, | ||
| category: s.enum("feat", "fix", "chore", "docs", "refactor", "test", "perf"), | ||
| impact: s.enum("low", "medium", "high"), | ||
| }) | ||
| ), | ||
| report: s.string, | ||
| }), | ||
| }); | ||
|
|
||
| export default gitCommitAnnotator; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| # 251 - Npm Script Dep Mapper | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool, repair } from "rig"; | ||
|
|
||
| const parseScriptDeps = defineTool("parseScriptDeps", { | ||
| description: "Extract npm run references and detect pre/post hooks for a script", | ||
| parameters: s.object({ | ||
| scriptName: s.string, | ||
| scriptBody: s.string, | ||
| allScriptNames: s.array(s.string), | ||
| }), | ||
| handler: ({ scriptName, scriptBody, allScriptNames }) => { | ||
| const deps: string[] = []; | ||
| const runRefs = scriptBody.match(/npm run ([\w:.-]+)/g) ?? []; | ||
| runRefs.forEach((ref: string) => deps.push(ref.replace("npm run ", ""))); | ||
| const hasPreHook = allScriptNames.includes("pre" + scriptName); | ||
| const hasPostHook = allScriptNames.includes("post" + scriptName); | ||
| return JSON.stringify({ deps, hasPreHook, hasPostHook }); | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: map npm script dependencies and classify each script by category | ||
| const npmScriptDepMapper = agent({ | ||
| name: "npmScriptDepMapper", | ||
| model: "small", | ||
| maxTurns: 2, | ||
| addons: repair(), | ||
| tools: [parseScriptDeps], | ||
| instructions: p`Analyze npm scripts from package.json and map their dependencies. | ||
|
|
||
| package.json: ${p.read("package.json")} | ||
|
|
||
| For each script, call parseScriptDeps with the scriptName, scriptBody, and list of all script names. | ||
| Classify each script's category: build, test, lint, dev, deploy, util, hook, or other. | ||
| A script is a hook category if its name starts with pre or post. | ||
| Set hasPreHook and hasPostHook based on tool output. | ||
| Return a record keyed by script name.`, | ||
| output: s.record( | ||
| s.object({ | ||
| command: s.string, | ||
| dependsOn: s.array(s.string), | ||
| category: s.enum("build", "test", "lint", "dev", "deploy", "util", "hook", "other"), | ||
| hasPreHook: s.boolean, | ||
| hasPostHook: s.boolean, | ||
| }) | ||
| ), | ||
| }); | ||
|
|
||
| export default npmScriptDepMapper; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| # 252 - Markdown Link Checker | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool, repair } from "rig"; | ||
|
|
||
| const checkUrl = defineTool("checkUrl", { | ||
| description: "Check the HTTP status of a URL using curl", | ||
| parameters: s.object({ url: s.string }), | ||
| handler: async ({ url }) => { | ||
| const { execSync } = await import("node:child_process"); | ||
| try { | ||
| const code = execSync( | ||
| `curl --head --silent --max-time 5 -o /dev/null -w "%{http_code}" "${url}" 2>/dev/null`, | ||
| { encoding: "utf8", timeout: 8000 } | ||
| ).trim(); | ||
| return code; | ||
| } catch { | ||
| return "timeout"; | ||
| } | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: locate markdown files, extract HTTP links, and check each link's HTTP status | ||
| const markdownLinkChecker = agent({ | ||
| name: "markdownLinkChecker", | ||
| model: "small", | ||
| addons: repair(), | ||
| tools: [checkUrl], | ||
| instructions: p`Check HTTP links found across all markdown files in this workspace. | ||
|
|
||
| Markdown files found: | ||
| ${p.bash("find . -name '*.md' -not -path '*/node_modules/*' | head -20")} | ||
|
|
||
| Unique URLs extracted: | ||
| ${p.bash("grep -roh 'https\\?://[^)\"\\s]*' . --include='*.md' 2>/dev/null | sort -u | head -50")} | ||
|
|
||
| For each URL, call checkUrl and classify the result: | ||
| - ok: HTTP 200-299 | ||
| - redirect: HTTP 301, 302, 307, 308 | ||
| - broken: HTTP 400-599 | ||
| - timeout: curl timed out or failed | ||
| - skipped: malformed or non-http URL | ||
|
|
||
| Set allValid to true only if brokenCount is 0.`, | ||
| output: s.object({ | ||
| links: s.array( | ||
| s.object({ | ||
| url: s.url, | ||
| status: s.enum("ok", "broken", "redirect", "timeout", "skipped"), | ||
| httpCode: s.optional(s.int), | ||
| foundInFile: s.optional(s.path), | ||
| }) | ||
| ), | ||
| brokenCount: s.int, | ||
| allValid: s.boolean, | ||
| }), | ||
| }); | ||
|
|
||
| export default markdownLinkChecker; | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| # 253 - Package Version Drift | ||
|
|
||
| ```rig | ||
| import { agent, p, s } from "rig"; | ||
|
|
||
| // Agent role: compare installed npm package versions against the latest published versions and classify drift | ||
| const pkgVersionDrift = agent({ | ||
| name: "pkgVersionDrift", | ||
| model: "small", | ||
| instructions: p`Report npm package version drift for this project. | ||
|
|
||
| Package manifest: ${p.read("package.json")} | ||
|
|
||
| Outdated packages (JSON): ${p.bash("npm outdated --json 2>/dev/null || echo '{}'")} | ||
|
|
||
| For each dependency, classify driftLevel: | ||
| - ok: current matches latest | ||
| - patch: only patch version behind | ||
| - minor: minor version behind | ||
| - major: major version behind | ||
|
|
||
| Write a markdown drift summary report. | ||
| ${p.writeOutput("report", "version-drift-report.md")}`, | ||
| output: s.object({ | ||
| packages: s.array( | ||
| s.object({ | ||
| name: s.string, | ||
| current: s.string, | ||
| latest: s.string, | ||
| driftLevel: s.enum("ok", "patch", "minor", "major"), | ||
| }) | ||
| ), | ||
| report: s.string, | ||
| reportWritten: s.boolean, | ||
|
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] 💡 SuggestionRemove |
||
| }), | ||
| }); | ||
|
|
||
| export default pkgVersionDrift; | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| # 254 - Git Author Stats | ||
|
|
||
| ```rig | ||
| import { agent, p, s } from "rig"; | ||
|
|
||
| // Agent role: aggregate git commit statistics per author including commit counts and date ranges | ||
| const gitAuthorStats = agent({ | ||
| name: "gitAuthorStats", | ||
| model: "small", | ||
| instructions: p`Aggregate git commit statistics per author. | ||
|
|
||
| Author commit counts: ${p.bash("git shortlog -sne --all 2>/dev/null | head -40")} | ||
|
|
||
| Commit log with author emails and dates: ${p.bash("git log --format='%ae|%ad' --date=short 2>/dev/null | head -200")} | ||
|
|
||
| For each unique author email, compute: | ||
| - commitCount: total number of commits | ||
| - firstCommit: earliest commit date (YYYY-MM-DD) | ||
| - lastCommit: most recent commit date (YYYY-MM-DD) | ||
|
|
||
| Identify topAuthor with the most commits. | ||
| Compute totalCommits across all authors.`, | ||
| output: s.object({ | ||
| authors: s.record( | ||
| s.object({ | ||
| commitCount: s.int, | ||
| firstCommit: s.string, | ||
| lastCommit: s.string, | ||
| }) | ||
| ), | ||
| topAuthor: s.string, | ||
| totalCommits: s.int, | ||
| }), | ||
| }); | ||
|
|
||
| export default gitAuthorStats; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| # 255 - Ts Path Alias Validator | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool } from "rig"; | ||
| import { existsSync } from "node:fs"; | ||
|
|
||
| const checkAlias = defineTool("checkAlias", { | ||
| description: "Check whether a TypeScript path alias target exists on the filesystem", | ||
| parameters: s.object({ alias: s.string, target: s.string }), | ||
| handler: ({ alias, target }) => { | ||
| const resolved = target.replace(/\/\*$/, ""); | ||
| const exists = existsSync(resolved); | ||
| return JSON.stringify({ alias, target, exists }); | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: validate TypeScript path alias targets from tsconfig.json against the filesystem | ||
| const tsPathAliasValidator = agent({ | ||
| name: "tsPathAliasValidator", | ||
| model: "small", | ||
| tools: [checkAlias], | ||
| instructions: p`Validate TypeScript path aliases defined in tsconfig.json. | ||
|
|
||
| tsconfig.json: ${p.read("tsconfig.json")} | ||
|
|
||
| Import alias usage in source: ${p.bash("grep -r --include='*.ts' -h 'from [\"\\x27]' . 2>/dev/null | grep -v node_modules | grep -v '\\./' | grep -v '\\.\\./' | head -40")} | ||
|
|
||
| For each entry in compilerOptions.paths, call checkAlias with the alias pattern and its first target path. | ||
| Classify each alias: | ||
| - valid: target exists on disk and alias is used in imports | ||
| - broken: target path does not exist | ||
| - unused: target exists but alias not found in any imports | ||
|
|
||
| Report totalAliases and brokenCount.`, | ||
| output: s.object({ | ||
| aliases: s.record( | ||
| s.object({ | ||
| target: s.string, | ||
| status: s.enum("valid", "broken", "unused"), | ||
| }) | ||
| ), | ||
| totalAliases: s.int, | ||
| brokenCount: s.int, | ||
| }), | ||
| }); | ||
|
|
||
| export default tsPathAliasValidator; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| # 256 - Gh Actions Timing Analyzer | ||
|
|
||
| ```rig | ||
| import { agent, p, s, defineTool, repair } from "rig"; | ||
|
|
||
| const parseWorkflowSteps = defineTool("parseWorkflowSteps", { | ||
| description: "Parse a GitHub Actions workflow YAML to extract job names, step counts, and detect optimization patterns", | ||
| parameters: s.object({ content: s.string, filename: s.string }), | ||
| handler: ({ content, filename }) => { | ||
| const jobMatches = content.match(/^\s{2}[\w-]+:/gm) ?? []; | ||
|
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. [/diagnosing-bugs] The job-count regex 💡 Suggested fixAnchor the match to the const jobsSection = content.match(/^jobs:\n([\s\S]*?)(?=^\S|$)/m)?.[1] ?? '';
const jobMatches = jobsSection.match(/^ [\w-]+:/gm) ?? [];Or just document the known over-counting so sample readers aren't misled. |
||
| const stepMatches = content.match(/^\s+-\s+(?:name:|uses:|run:)/gm) ?? []; | ||
| const hasCache = /uses:\s+actions\/cache/.test(content) || /cache:/.test(content); | ||
| const hasMatrix = /matrix:/.test(content); | ||
| const hasInstall = /npm (ci|install)|yarn install|pnpm install/.test(content); | ||
| const missingCache = hasInstall && !hasCache; | ||
| const suggestions: string[] = []; | ||
| if (missingCache) suggestions.push("Add actions/cache after install step to speed up builds"); | ||
| if (hasMatrix && jobMatches.length === 1) suggestions.push("Matrix found but only one job — consider parallel jobs for different test suites"); | ||
| if (stepMatches.length > 15) suggestions.push("Workflow has many steps — consider splitting into reusable workflows"); | ||
| return JSON.stringify({ filename, jobs: jobMatches.length, steps: stepMatches.length, hasCache, hasMatrix, suggestions }); | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: analyze GitHub Actions workflow files for timing and performance optimization opportunities | ||
| const ghActionsTimingAnalyzer = agent({ | ||
| name: "ghActionsTimingAnalyzer", | ||
| model: "small", | ||
| addons: repair(), | ||
| tools: [parseWorkflowSteps], | ||
| instructions: p`Analyze GitHub Actions workflow files for performance optimization opportunities. | ||
|
|
||
| Workflow files: ${p.glob(".github/workflows/*.yml")} | ||
|
|
||
| Workflow contents: ${p.bash("find .github/workflows -name '*.yml' 2>/dev/null | head -10 | xargs cat 2>/dev/null || echo 'No workflows found'")} | ||
|
|
||
| For each workflow file, call parseWorkflowSteps with the file content and filename. | ||
| Collect results and determine if any workflows are optimizable (have suggestions). | ||
| Set optimizable to true if any workflow has suggestions.`, | ||
| output: s.object({ | ||
| workflows: s.array( | ||
| s.object({ | ||
| file: s.path, | ||
| jobs: s.int, | ||
| steps: s.int, | ||
| hasCache: s.boolean, | ||
| hasMatrix: s.boolean, | ||
| suggestions: s.array(s.string), | ||
| }) | ||
| ), | ||
| totalWorkflows: s.int, | ||
| optimizable: s.boolean, | ||
| }), | ||
| }); | ||
|
|
||
| export default ghActionsTimingAnalyzer; | ||
| ``` | ||
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.
[/diagnosing-bugs] Shell injection risk:
urlis interpolated directly into a shell string. A URL with embedded$(...)or backticks would execute arbitrary code when curl is invoked viaexecSync.💡 Suggested fix
Shell-escape the URL or pass it as an argument after
--:Or avoid the shell entirely with
node:https/fetch.