-
Notifications
You must be signed in to change notification settings - Fork 0
[rig-tasks] Add 10 rig samples — 2026-08-15 #427
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,53 @@ | ||
| # 411 - Package Json Completeness Scorer | ||
|
|
||
| ```rig | ||
| import { agent, defineTool, p, repair, s } from "rig"; | ||
|
|
||
| const scoreField = defineTool("scoreField", { | ||
| description: "Score a package.json field for completeness and return its category.", | ||
| parameters: s.object({ fieldName: s.string, value: s.unknown }), | ||
| handler: ({ fieldName, value }: { fieldName: string; value: unknown }) => { | ||
| const required = ["name", "version"]; | ||
| const recommended = ["description", "main", "scripts", "license", "repository", "keywords"]; | ||
| const category = required.includes(fieldName) | ||
| ? ("required" as const) | ||
| : recommended.includes(fieldName) | ||
| ? ("recommended" as const) | ||
| : ("optional" as const); | ||
| const present = value !== undefined && value !== null && value !== ""; | ||
| const score = present ? 1 : 0; | ||
| return { present, category, score }; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: score the completeness of package.json against required and recommended fields. | ||
| const packageJsonCompletenessScorer = agent({ | ||
| model: "small", | ||
| instructions: p`Score the completeness of package.json. | ||
|
|
||
| Content: | ||
| ${p.read("package.json")} | ||
|
|
||
| Required fields: name, version. | ||
| Recommended fields: description, main, scripts, license, repository, keywords. | ||
| Optional fields: author, bugs, homepage, engines, files, types. | ||
|
|
||
| For each field from all three categories, call scoreField with fieldName and value (or undefined if absent). | ||
| Build fields record. totalScore = sum of scores. maxScore = total fields. completenessPercent = (totalScore / maxScore) * 100. missingRequired = required fields where present is false.`, | ||
| output: s.object({ | ||
| fields: s.record(s.object({ | ||
| present: s.boolean, | ||
| category: s.enum("required", "recommended", "optional"), | ||
| score: s.number, | ||
| })), | ||
| totalScore: s.number, | ||
| maxScore: s.number, | ||
| completenessPercent: s.number, | ||
| missingRequired: s.array(s.string), | ||
| }), | ||
| tools: [scoreField], | ||
| addons: [repair()], | ||
| }); | ||
|
|
||
| export default packageJsonCompletenessScorer; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| # 412 - Parallel Branch Analysis Workflow | ||
|
|
||
| ```rig | ||
| import { agent, p, s, workflow } from "rig"; | ||
|
|
||
| const branchMetric = s.object({ | ||
| totalBranches: s.number, | ||
| staleCount: s.number, | ||
| activeBranches: s.number, | ||
| }); | ||
|
|
||
| const commitMetric = s.object({ | ||
| totalCommits: s.number, | ||
| activeDays: s.number, | ||
| averagePerDay: s.number, | ||
| }); | ||
|
|
||
| // Agent role: measure branch count and identify stale branches. | ||
| const branchHealthAgent = agent({ | ||
| model: "small", | ||
| output: branchMetric, | ||
| instructions: p`Count total, stale (>30 days old), and active branches. | ||
| ${p.bash("git branch -a 2>/dev/null || echo ''")} | ||
| ${p.bash("git for-each-ref --format='%(refname:short) %(committerdate:relative)' refs/heads/ 2>/dev/null || echo ''")}`, | ||
| }); | ||
|
|
||
| // Agent role: measure commit frequency over the last 30 days. | ||
| const commitFrequencyAgent = agent({ | ||
| model: "small", | ||
| output: commitMetric, | ||
| instructions: p`Count total commits in last 30 days and estimate active days and average per day. | ||
| ${p.bash("git log --oneline --since='30 days ago' 2>/dev/null || echo ''")}`, | ||
| }); | ||
|
|
||
| // Workflow role: run branch health and commit frequency agents in parallel, then classify overall health. | ||
| const parallelBranchAnalysis = workflow({ | ||
| meta: { name: "parallelBranchAnalysis", description: "Parallel branch analysis", phases: ["Analyze", "Rate"] }, | ||
| body: async ({ call, phase }) => { | ||
| phase("Analyze"); | ||
| const [branchHealth, commitFrequency] = await Promise.all([ | ||
| call(branchHealthAgent, "analyze"), | ||
| call(commitFrequencyAgent, "analyze"), | ||
| ]); | ||
| phase("Rate"); | ||
| const overallHealth = await call.json( | ||
| `Branch health: ${JSON.stringify(branchHealth)}. Commit frequency: ${JSON.stringify(commitFrequency)}. Classify overall health as "healthy", "needs-attention", or "critical".`, | ||
| s.enum("healthy", "needs-attention", "critical"), | ||
| ); | ||
| return { branchHealth, commitFrequency, overallHealth }; | ||
| }, | ||
| }); | ||
|
|
||
| export default parallelBranchAnalysis; | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| # 413 - Lockfile Integrity Checker | ||
|
|
||
| ```rig | ||
| import { agent, defineTool, p, repair, s } from "rig"; | ||
|
|
||
| const verifyLockEntry = defineTool("verifyLockEntry", { | ||
| description: "Verify a package entry in package-lock.json has required integrity and resolved fields.", | ||
| parameters: s.object({ packageName: s.string, entryJson: s.string }), | ||
| handler({ entryJson }: { packageName: string; entryJson: string }) { | ||
| try { | ||
| const entry = JSON.parse(entryJson) as Record<string, unknown>; | ||
| const hasIntegrity = typeof entry["integrity"] === "string" && (entry["integrity"] as string).length > 0; | ||
| const hasResolved = typeof entry["resolved"] === "string" && (entry["resolved"] as string).length > 0; | ||
| const valid = hasIntegrity && hasResolved; | ||
| return { valid, hasIntegrity, hasResolved }; | ||
| } catch { | ||
| return { valid: false, hasIntegrity: false, hasResolved: false }; | ||
| } | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: check package-lock.json entries for integrity and resolved fields. | ||
| const lockfileIntegrityChecker = agent({ | ||
| model: "small", | ||
| instructions: p`Check integrity of package-lock.json entries. | ||
|
|
||
| Content: | ||
| ${p.read("package-lock.json")} | ||
|
|
||
| For each package in the "packages" or "dependencies" section, call verifyLockEntry with the package name and its JSON entry (as a string). Build the packages record. mismatchCount = number of invalid entries. isClean = mismatchCount === 0. totalChecked = total packages checked.`, | ||
| output: s.object({ | ||
| packages: s.record(s.object({ | ||
| valid: s.boolean, | ||
| hasIntegrity: s.boolean, | ||
| hasResolved: s.boolean, | ||
| })), | ||
| mismatchCount: s.number, | ||
| isClean: s.boolean, | ||
| totalChecked: s.number, | ||
| }), | ||
| tools: [verifyLockEntry], | ||
| addons: [repair()], | ||
| }); | ||
|
|
||
| export default lockfileIntegrityChecker; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| # 414 - Graphql Schema Type Extractor | ||
|
|
||
| ```rig | ||
| import { agent, defineTool, p, s } from "rig"; | ||
| import { steering } from "rig"; | ||
|
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] Duplicate import from 💡 Suggested fiximport { agent, defineTool, p, s, steering } from "rig";Two separate |
||
|
|
||
| const extractGraphqlTypes = defineTool("extractGraphqlTypes", { | ||
| description: "Extract type declarations from a GraphQL schema file.", | ||
| parameters: s.object({ filePath: s.path }), | ||
| async handler({ filePath }: { filePath: string }) { | ||
| const { readFile } = await import("node:fs/promises"); | ||
| const content = await readFile(filePath, "utf8"); | ||
| const pattern = /^(type|input|enum|interface|union)\s+(\w+)[^{]*\{([^}]*)\}/gm; | ||
| const results: Record<string, { kind: string; fieldCount: number; fields: string[]; sourceFile: string }> = {}; | ||
| let match: RegExpExecArray | null; | ||
| while ((match = pattern.exec(content)) !== null) { | ||
| const kind = match[1]; | ||
| const name = match[2]; | ||
| const body = match[3]; | ||
| const fields = body.split("\n").map((l: string) => l.trim()).filter((l: string) => l.length > 0 && !l.startsWith("#")); | ||
| results[name] = { kind, fieldCount: fields.length, fields, sourceFile: filePath }; | ||
| } | ||
|
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 regex 💡 Suggested fixRemove the // Replace [^}]* with [\s\S]*? to span lines
const pattern = /^(type|input|enum|interface|union)\s+(\w+)[^{]*\{([\s\S]*?)\}/gm;This matches the closing |
||
| return results; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: discover and extract all GraphQL type declarations across .graphql files. | ||
| const graphqlSchemaTypeExtractor = agent({ | ||
| model: "small", | ||
| instructions: p`Extract GraphQL type declarations from .graphql files. | ||
|
|
||
| Files found: | ||
| ${p.bash("find . -name '*.graphql' -not -path '*/node_modules/*' 2>/dev/null || echo '(none)'")} | ||
|
|
||
| For each .graphql file found, call extractGraphqlTypes with the file path. Merge all results into a single record keyed by type name.`, | ||
| output: s.record(s.object({ | ||
| kind: s.enum("type", "input", "enum", "interface", "union"), | ||
| fieldCount: s.number, | ||
| fields: s.array(s.string), | ||
| sourceFile: s.string, | ||
| })), | ||
| tools: [extractGraphqlTypes], | ||
| addons: [steering()], | ||
| }); | ||
|
|
||
| export default graphqlSchemaTypeExtractor; | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| # 415 - Os Path Structure Analyzer | ||
|
|
||
| ```rig | ||
| import { agent, defineTool, p, repair, s } from "rig"; | ||
| import { basename } from "node:path"; | ||
|
|
||
| const classifyDirectory = defineTool("classifyDirectory", { | ||
| description: "Classify a directory path into a category based on its basename.", | ||
| parameters: s.object({ dirPath: s.path, rootDir: s.string }), | ||
| handler({ dirPath, rootDir }: { dirPath: string; rootDir: string }) { | ||
| const name = basename(dirPath); | ||
| const depth = dirPath.replace(rootDir, "").split("/").filter(Boolean).length; | ||
| const srcNames = new Set(["src", "lib", "source"]); | ||
| const testNames = new Set(["test", "tests", "__tests__", "spec", "specs"]); | ||
| const configNames = new Set(["config", "configs", "conf", ".config"]); | ||
| const buildNames = new Set(["dist", "build", "out", "output", ".next", ".cache"]); | ||
| const vendorNames = new Set(["node_modules", "vendor", "third_party"]); | ||
| const category = srcNames.has(name) ? "src" | ||
| : testNames.has(name) ? "test" | ||
| : configNames.has(name) ? "config" | ||
| : buildNames.has(name) ? "build" | ||
| : vendorNames.has(name) ? "vendor" | ||
| : "other"; | ||
| return { depth, category }; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: analyze the directory structure of a given root path and classify each directory. | ||
| const osPathStructureAnalyzer = agent({ | ||
| model: "small", | ||
| input: s.object({ rootDir: s.string }), | ||
| instructions: p`Analyze the directory structure of the given rootDir. | ||
|
|
||
| Directories found: | ||
| ${p.bash("find . -maxdepth 3 -type d -not -path '*/node_modules/*' 2>/dev/null || echo '(none)'")} | ||
|
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] 💡 Suggested fixUse instructions: p`Analyze the directory structure of the given rootDir.
Directories found:
${p.bash("find ${input.rootDir} -maxdepth 3 -type d -not -path '*/node_modules/*' 2>/dev/null || echo '(none)'")}`,Or use a |
||
|
|
||
| For each directory, call classifyDirectory with its path and the rootDir. Build the directories record. Compute maxDepth and categoryCounts.`, | ||
| output: s.object({ | ||
| directories: s.record(s.object({ | ||
| depth: s.number, | ||
| category: s.string, | ||
| })), | ||
| maxDepth: s.number, | ||
| categoryCounts: s.record(s.number), | ||
| }), | ||
| tools: [classifyDirectory], | ||
| addons: [repair()], | ||
| }); | ||
|
|
||
| export default osPathStructureAnalyzer; | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| # 416 - Ci Log Error Classifier | ||
|
|
||
| ```rig | ||
| import { agent, defineTool, p, repair, s } from "rig"; | ||
|
|
||
| const classifyLogLine = defineTool("classifyLogLine", { | ||
| description: "Classify a CI log line into an error class and severity.", | ||
| parameters: s.object({ line: s.string }), | ||
| handler({ line }: { line: string }) { | ||
| const lower = line.toLowerCase(); | ||
| const errorClass = | ||
| lower.includes("error") && (lower.includes("compil") || lower.includes("syntax") || lower.includes("tsc")) | ||
| ? ("compile" as const) | ||
| : lower.includes("test") && (lower.includes("fail") || lower.includes("error")) | ||
| ? ("test" as const) | ||
| : lower.includes("eslint") || lower.includes("lint") | ||
| ? ("lint" as const) | ||
| : lower.includes("econnrefused") || lower.includes("enotfound") || lower.includes("network") | ||
| ? ("network" as const) | ||
| : lower.includes("permission") || lower.includes("eacces") | ||
| ? ("permission" as const) | ||
| : ("unknown" as const); | ||
| const severity = | ||
| lower.includes("error") ? ("error" as const) | ||
| : lower.includes("warn") ? ("warning" as const) | ||
| : ("info" as const); | ||
| return { errorClass, severity }; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: classify each line of a CI log file by error class and severity. | ||
| const ciLogErrorClassifier = agent({ | ||
| model: "small", | ||
| input: s.object({ logFile: s.string }), | ||
| instructions: p`Classify lines in the CI log file at the path provided in logFile. | ||
|
|
||
| Log content: | ||
| ${p.readInput("logFile")} | ||
|
|
||
| For each non-empty line, call classifyLogLine. Build the lines array. Compute errorCount (lines with severity "error"), warningCount (lines with severity "warning"). dominantError = the most frequent errorClass among error-severity lines (omit if none).`, | ||
| output: s.object({ | ||
| lines: s.array(s.object({ | ||
| line: s.string, | ||
| errorClass: s.enum("compile", "test", "lint", "network", "permission", "unknown"), | ||
| severity: s.enum("error", "warning", "info"), | ||
| })), | ||
| errorCount: s.number, | ||
| warningCount: s.number, | ||
| dominantError: s.optional(s.string), | ||
| }), | ||
| tools: [classifyLogLine], | ||
| addons: [repair()], | ||
| }); | ||
|
|
||
| export default ciLogErrorClassifier; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| # 417 - Ini Config Parser | ||
|
|
||
| ```rig | ||
| import { agent, defineTool, p, repair, s } from "rig"; | ||
|
|
||
| const parseIniSection = defineTool("parseIniSection", { | ||
| description: "Parse an INI file content and return sections with key-value pairs.", | ||
| parameters: s.object({ content: s.string }), | ||
| handler({ content }: { content: string }) { | ||
| const sections: Record<string, Record<string, string>> = {}; | ||
| let currentSection = "__default__"; | ||
| sections[currentSection] = {}; | ||
| for (const rawLine of content.split("\n")) { | ||
| const line = rawLine.trim(); | ||
| if (!line || line.startsWith(";") || line.startsWith("#")) continue; | ||
| const sectionMatch = /^\[(.+)\]$/.exec(line); | ||
| if (sectionMatch) { | ||
| currentSection = sectionMatch[1]; | ||
| sections[currentSection] = {}; | ||
| continue; | ||
| } | ||
| const kvMatch = /^([^=]+)=(.*)$/.exec(line); | ||
| if (kvMatch) { | ||
| sections[currentSection][kvMatch[1].trim()] = kvMatch[2].trim(); | ||
| } | ||
| } | ||
| return sections; | ||
| }, | ||
| }); | ||
|
|
||
| // Agent role: parse an INI config file and report its sections and key-value pairs. | ||
| const iniConfigParser = agent({ | ||
| model: "small", | ||
| input: s.object({ configFile: s.string }), | ||
| instructions: p`Parse the INI config file at the path provided in configFile. | ||
|
|
||
| Content: | ||
| ${p.readInput("configFile")} | ||
|
|
||
| Call parseIniSection with the full file content. Build the sections record. totalSections = number of sections (excluding __default__ if empty). totalKeys = total key-value pairs across all sections. hasDefaultSection = true if there are keys outside any named section.`, | ||
| output: s.object({ | ||
| sections: s.record(s.record(s.string)), | ||
| totalKeys: s.number, | ||
| totalSections: s.number, | ||
| hasDefaultSection: s.boolean, | ||
| }), | ||
| tools: [parseIniSection], | ||
| addons: [repair()], | ||
| }); | ||
|
|
||
| export default iniConfigParser; | ||
| ``` |
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] This sample is near-duplicate of the existing
360-parallel-branch-analysis-workflow.md, which covers the same domain (parallel branch + commit analysis, same health enum). The key difference — usingPromise.allinstead ofparallel()— is noted in the PR description as a typecheck workaround, but that makes this sample teach a weaker pattern.💡 Options
parallel()helper now supports heterogeneous types, update 360 instead of adding a workaround sample.Samples are learning material — duplicates with subtly worse patterns are actively harmful.