From 4bd9354bfd370b4139cad7622aa894daaea4e8de Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 08:06:36 +0000 Subject: [PATCH] =?UTF-8?q?Add=2010=20rig=20samples=20(411=E2=80=93420)=20?= =?UTF-8?q?=E2=80=94=202026-08-14?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../samples/411-ts-decorator-usage-scanner.md | 40 ++++++++++++ .../samples/412-git-tag-semver-validator.md | 55 ++++++++++++++++ .../413-package-json-completeness-scorer.md | 51 +++++++++++++++ .../samples/414-parallel-branch-analysis.md | 55 ++++++++++++++++ .../samples/415-lockfile-integrity-checker.md | 54 +++++++++++++++ .../416-graphql-schema-type-extractor.md | 47 ++++++++++++++ .../samples/417-git-stale-branch-reporter.md | 57 ++++++++++++++++ .../samples/418-ts-abstract-class-finder.md | 54 +++++++++++++++ .../samples/419-dockerfile-env-inspector.md | 65 +++++++++++++++++++ .../samples/420-npm-script-prefix-analyzer.md | 45 +++++++++++++ 10 files changed, 523 insertions(+) create mode 100644 skills/rig/samples/411-ts-decorator-usage-scanner.md create mode 100644 skills/rig/samples/412-git-tag-semver-validator.md create mode 100644 skills/rig/samples/413-package-json-completeness-scorer.md create mode 100644 skills/rig/samples/414-parallel-branch-analysis.md create mode 100644 skills/rig/samples/415-lockfile-integrity-checker.md create mode 100644 skills/rig/samples/416-graphql-schema-type-extractor.md create mode 100644 skills/rig/samples/417-git-stale-branch-reporter.md create mode 100644 skills/rig/samples/418-ts-abstract-class-finder.md create mode 100644 skills/rig/samples/419-dockerfile-env-inspector.md create mode 100644 skills/rig/samples/420-npm-script-prefix-analyzer.md diff --git a/skills/rig/samples/411-ts-decorator-usage-scanner.md b/skills/rig/samples/411-ts-decorator-usage-scanner.md new file mode 100644 index 0000000..93ab73d --- /dev/null +++ b/skills/rig/samples/411-ts-decorator-usage-scanner.md @@ -0,0 +1,40 @@ +# 411 - TS Decorator Usage Scanner + +```rig +import { agent, p, s, defineTool, repair } from "rig"; +import { readFile } from "node:fs/promises"; + +const scanDecorators = defineTool("scanDecorators", { + description: "Scan a TypeScript file for decorator usages matching @UpperCase patterns", + parameters: s.object({ filePath: s.path }), + handler: async ({ filePath }: { filePath: string }) => { + const content = await readFile(filePath, "utf8"); + const matches = content.match(/@[A-Z]\w+/g) ?? []; + return { decorators: matches, filePath }; + }, +}); + +// Agent role: Scan TypeScript source files for decorator usages and report counts per decorator. +const tsDecoratorScanner = agent({ + model: "small", + instructions: p`Scan all TypeScript source files for decorator usage. +Files to scan: ${p.glob("src/**/*.ts")} +Use the scanDecorators tool on each file path listed above. +Return an object with: +- decorators: a record mapping each decorator name to { usageCount, files } +- totalDecorated: total number of files that had at least one decorator +- mostUsedDecorator: the decorator name with the highest usageCount, or omit if none found`, + output: s.object({ + decorators: s.record(s.object({ + usageCount: s.int, + files: s.array(s.path), + })), + totalDecorated: s.int, + mostUsedDecorator: s.optional(s.string), + }), + tools: [scanDecorators], + addons: [repair()], +}); + +export default tsDecoratorScanner; +``` diff --git a/skills/rig/samples/412-git-tag-semver-validator.md b/skills/rig/samples/412-git-tag-semver-validator.md new file mode 100644 index 0000000..1d8182d --- /dev/null +++ b/skills/rig/samples/412-git-tag-semver-validator.md @@ -0,0 +1,55 @@ +# 412 - Git Tag Semver Validator + +```rig +import { agent, p, s, defineTool, steering } from "rig"; + +const validateSemver = defineTool("validateSemver", { + description: "Validate a git tag against semver format and classify it", + parameters: s.object({ tag: s.string }), + handler: ({ tag }: { tag: string }) => { + const releaseRe = /^\d+\.\d+\.\d+$/; + const prereleaseRe = /^\d+\.\d+\.\d+-.+$/; + if (releaseRe.test(tag)) { + return { valid: true, normalized: tag, type: "release" as const }; + } else if (prereleaseRe.test(tag)) { + return { valid: true, normalized: tag, type: "prerelease" as const }; + } + // strip leading 'v' and retry + const stripped = tag.replace(/^v/, ""); + if (releaseRe.test(stripped)) { + return { valid: true, normalized: stripped, type: "release" as const }; + } else if (prereleaseRe.test(stripped)) { + return { valid: true, normalized: stripped, type: "prerelease" as const }; + } + return { valid: false, normalized: tag, type: "invalid" as const }; + }, +}); + +// Agent role: Validate all git tags in the repository against semver format and produce a validity report. +const gitTagSemverValidator = agent({ + model: "small", + instructions: p`Validate all git tags against semver format. +Git tags: ${p.bash("git tag --list")} +Use the validateSemver tool on each tag. +Return an object with: +- tags: array of objects each with { tag, valid, normalized, type } +- validCount: number of valid tags +- invalidCount: number of invalid tags +- allValid: true if invalidCount is 0`, + output: s.object({ + tags: s.array(s.object({ + tag: s.string, + valid: s.boolean, + normalized: s.string, + type: s.enum("release", "prerelease", "invalid"), + })), + validCount: s.int, + invalidCount: s.int, + allValid: s.boolean, + }), + tools: [validateSemver], + addons: [steering()], +}); + +export default gitTagSemverValidator; +``` diff --git a/skills/rig/samples/413-package-json-completeness-scorer.md b/skills/rig/samples/413-package-json-completeness-scorer.md new file mode 100644 index 0000000..2469c13 --- /dev/null +++ b/skills/rig/samples/413-package-json-completeness-scorer.md @@ -0,0 +1,51 @@ +# 413 - Package JSON Completeness Scorer + +```rig +import { agent, p, s, defineTool, repair } from "rig"; + +const scoreField = defineTool("scoreField", { + description: "Classify a package.json field as required, recommended, or optional and assign a score", + parameters: s.object({ fieldName: s.string }), + handler: ({ fieldName }: { fieldName: string }) => { + const required = ["name", "version", "description", "main", "license"]; + const recommended = ["repository", "keywords", "author", "bugs", "homepage", "engines", "files"]; + if (required.includes(fieldName)) { + return { category: "required" as const, score: 3 }; + } else if (recommended.includes(fieldName)) { + return { category: "recommended" as const, score: 2 }; + } + return { category: "optional" as const, score: 1 }; + }, +}); + +// Agent role: Score package.json completeness by classifying its fields and computing a completeness percentage. +const packageJsonScorer = agent({ + model: "small", + instructions: p`Score the completeness of package.json. +File contents: ${p.read("package.json")} +Use scoreField on each top-level field present in the file. +Also check for all required/recommended fields that are missing. +Return: +- fields: record mapping each field name to { present: true, category, score } +- For missing required/recommended fields add them with present: false +- totalScore: sum of scores for present fields +- maxScore: sum of all possible scores (required*3 + recommended*2) +- completenessPercent: (totalScore / maxScore) * 100 +- missingRequired: array of required field names that are absent`, + 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 packageJsonScorer; +``` diff --git a/skills/rig/samples/414-parallel-branch-analysis.md b/skills/rig/samples/414-parallel-branch-analysis.md new file mode 100644 index 0000000..52faaa5 --- /dev/null +++ b/skills/rig/samples/414-parallel-branch-analysis.md @@ -0,0 +1,55 @@ +# 414 - Parallel Branch Analysis Workflow + +```rig +import { agent, p, s, workflow } from "rig"; + +const repoMetric = s.object({ + label: s.string, + value: s.number, + detail: s.string, +}); + +// Agent role: Analyze branch health and return a labeled metric summarizing branch counts. +const branchHealthAgent = agent({ + model: "small", + instructions: p`Analyze git branches and report a summary metric. +Branches: ${p.bash("git branch -a")} +Count total and stale branches. Return: +- label: "branch-health" +- value: ratio of active to total branches (0.0 to 1.0) +- detail: JSON string with totalBranches, staleCount, activeBranches as a short summary`, + output: repoMetric, +}); + +// Agent role: Analyze commit frequency and return a labeled metric for commits per day. +const commitFrequencyAgent = agent({ + model: "small", + instructions: p`Analyze git commit frequency over the last 30 days and return a metric. +Commits: ${p.bash("git log --since='30 days ago' --format='%ad' --date=short")} +Count unique commit dates. Return: +- label: "commit-frequency" +- value: average commits per day over 30 days +- detail: JSON string with totalCommits, activeDays`, + output: repoMetric, +}); + +// Workflow role: Run branch health and commit frequency analysis in parallel, then classify overall repository health. +const parallelBranchAnalysis = workflow({ + meta: { name: "parallel-branch-analysis", description: "Parallel branch and commit frequency analysis" }, + body: async ({ call, parallel }) => { + const [branchHealth, commitFrequency] = await parallel([ + () => call(branchHealthAgent, "analyze"), + () => call(commitFrequencyAgent, "analyze"), + ]); + + const overallHealth = await call.json( + `Given metrics ${JSON.stringify([branchHealth, commitFrequency])}, classify overall repository health as "healthy", "needs-attention", or "critical".`, + s.enum("healthy", "needs-attention", "critical"), + ); + + return { branchHealth, commitFrequency, overallHealth }; + }, +}); + +export default parallelBranchAnalysis; +``` diff --git a/skills/rig/samples/415-lockfile-integrity-checker.md b/skills/rig/samples/415-lockfile-integrity-checker.md new file mode 100644 index 0000000..828bebb --- /dev/null +++ b/skills/rig/samples/415-lockfile-integrity-checker.md @@ -0,0 +1,54 @@ +# 415 - Lockfile Integrity Checker + +```rig +import { agent, p, s, defineTool, repair } from "rig"; + +const verifyLockEntry = defineTool("verifyLockEntry", { + description: "Verify that a package lock entry has required resolved and integrity fields", + parameters: s.object({ packageName: s.string, entryJson: s.string }), + handler: ({ packageName: _packageName, entryJson }: { packageName: string; entryJson: string }) => { + let entry: Record; + try { + entry = JSON.parse(entryJson) as Record; + } catch { + return { valid: false, hasIntegrity: false, hasResolved: false }; + } + const integrityVal = entry["integrity"]; + const resolvedVal = entry["resolved"]; + const hasIntegrity = typeof integrityVal === "string" && integrityVal.length > 0; + const hasResolved = typeof resolvedVal === "string" && resolvedVal.length > 0; + return { + valid: hasIntegrity && hasResolved, + hasIntegrity, + hasResolved, + }; + }, +}); + +// Agent role: Check package-lock.json entries for completeness of resolved and integrity fields. +const lockfileIntegrityChecker = agent({ + model: "small", + instructions: p`Check the lockfile for integrity and resolved fields. +Contents: ${p.read("package-lock.json")} +For each package in the "packages" or "dependencies" section, use verifyLockEntry with the package name and its JSON entry (serialized as a JSON string). +Return: +- packages: record mapping package name to { valid, hasIntegrity, hasResolved } +- mismatchCount: count of packages where valid is false +- isClean: true if mismatchCount is 0 +- totalChecked: total number of packages checked`, + output: s.object({ + packages: s.record(s.object({ + valid: s.boolean, + hasIntegrity: s.boolean, + hasResolved: s.boolean, + })), + mismatchCount: s.int, + isClean: s.boolean, + totalChecked: s.int, + }), + tools: [verifyLockEntry], + addons: [repair()], +}); + +export default lockfileIntegrityChecker; +``` diff --git a/skills/rig/samples/416-graphql-schema-type-extractor.md b/skills/rig/samples/416-graphql-schema-type-extractor.md new file mode 100644 index 0000000..caf8e98 --- /dev/null +++ b/skills/rig/samples/416-graphql-schema-type-extractor.md @@ -0,0 +1,47 @@ +# 416 - GraphQL Schema Type Extractor + +```rig +import { agent, p, s, defineTool, steering } from "rig"; +import { readFile } from "node:fs/promises"; + +const extractGraphqlTypes = defineTool("extractGraphqlTypes", { + description: "Extract type declarations from a GraphQL schema file", + parameters: s.object({ filePath: s.path }), + handler: async ({ filePath }: { filePath: string }) => { + const content = await readFile(filePath, "utf8"); + const results: Array<{ name: string; kind: string; fieldCount: number; fields: string[]; sourceFile: string }> = []; + const re = /^(type|input|enum|interface|union)\s+(\w+)[^{]*\{([^}]*)\}/gm; + let match: RegExpExecArray | null; + while ((match = re.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.push({ name, kind, fieldCount: fields.length, fields, sourceFile: filePath }); + } + return results; + }, +}); + +// Agent role: Extract and catalog all type declarations from GraphQL schema files in the workspace. +const graphqlSchemaTypeExtractor = agent({ + model: "small", + instructions: p`Extract all GraphQL type declarations from schema files. +Schema files: ${p.bash("find . -name '*.graphql' -not -path '*/node_modules/*'")} +Use extractGraphqlTypes on each file path. +Aggregate all results into a single record keyed by type name. +Each entry should have: kind (type/input/enum/interface/union), fieldCount, fields (array), sourceFile.`, + output: s.record(s.object({ + kind: s.enum("type", "input", "enum", "interface", "union"), + fieldCount: s.int, + fields: s.array(s.string), + sourceFile: s.string, + })), + tools: [extractGraphqlTypes], + addons: [steering()], +}); + +export default graphqlSchemaTypeExtractor; +``` diff --git a/skills/rig/samples/417-git-stale-branch-reporter.md b/skills/rig/samples/417-git-stale-branch-reporter.md new file mode 100644 index 0000000..fe43b7d --- /dev/null +++ b/skills/rig/samples/417-git-stale-branch-reporter.md @@ -0,0 +1,57 @@ +# 417 - Git Stale Branch Reporter + +```rig +import { agent, p, s, defineTool, repair } from "rig"; +import { execSync } from "node:child_process"; + +const getBranchAge = defineTool("getBranchAge", { + description: "Get the age of a git branch by looking at its last commit date", + parameters: s.object({ branchName: s.string }), + handler: ({ branchName }: { branchName: string }) => { + let lastCommit: string; + try { + lastCommit = execSync(`git log -1 --format=%ci "${branchName}" 2>/dev/null`, { encoding: "utf8" }).trim(); + } catch { + lastCommit = ""; + } + if (!lastCommit) { + return { lastCommit: "unknown", ageDays: -1, ageClass: "ancient" as const }; + } + const commitDate = new Date(lastCommit); + const ageDays = Math.floor((Date.now() - commitDate.getTime()) / (1000 * 60 * 60 * 24)); + let ageClass: "fresh" | "recent" | "stale" | "ancient"; + if (ageDays <= 7) ageClass = "fresh"; + else if (ageDays <= 30) ageClass = "recent"; + else if (ageDays <= 180) ageClass = "stale"; + else ageClass = "ancient"; + return { lastCommit, ageDays, ageClass }; + }, +}); + +// Agent role: Report the age and staleness classification of each remote git branch. +const gitStaleBranchReporter = agent({ + model: "small", + instructions: p`Report the age of each remote git branch. +Remote branches: ${p.bash("git branch -r")} +Use getBranchAge on each branch name (strip leading whitespace and 'origin/' prefix if needed, but pass the full ref). +Return: +- branches: record mapping branch name to { lastCommit, ageDays, ageClass } +- staleCount: number of branches with ageClass "stale" +- ancientCount: number of branches with ageClass "ancient" +- totalBranches: total count`, + output: s.object({ + branches: s.record(s.object({ + lastCommit: s.string, + ageDays: s.int, + ageClass: s.enum("fresh", "recent", "stale", "ancient"), + })), + staleCount: s.int, + ancientCount: s.int, + totalBranches: s.int, + }), + tools: [getBranchAge], + addons: [repair()], +}); + +export default gitStaleBranchReporter; +``` diff --git a/skills/rig/samples/418-ts-abstract-class-finder.md b/skills/rig/samples/418-ts-abstract-class-finder.md new file mode 100644 index 0000000..cae5d7e --- /dev/null +++ b/skills/rig/samples/418-ts-abstract-class-finder.md @@ -0,0 +1,54 @@ +# 418 - TS Abstract Class Finder + +```rig +import { agent, p, s, defineTool, steering } from "rig"; +import { readFile } from "node:fs/promises"; + +const extractAbstractClasses = defineTool("extractAbstractClasses", { + description: "Extract abstract class declarations and count their abstract methods from a TypeScript file", + parameters: s.object({ filePath: s.path }), + handler: async ({ filePath }: { filePath: string }) => { + const content = await readFile(filePath, "utf8"); + const classRe = /abstract\s+class\s+(\w+)/g; + const abstractMethodRe = /abstract\s+(?:\w+\s+)*(\w+)\s*\(/g; + const classes: Array<{ name: string; methodCount: number; abstractMethodCount: number; sourceFile: string }> = []; + let classMatch: RegExpExecArray | null; + while ((classMatch = classRe.exec(content)) !== null) { + const name = classMatch[1]; + // Count all method-like patterns in the file as approximation + const allMethods = (content.match(/^\s+(?:(?:public|private|protected|async|static|readonly)\s+)*\w+\s*\(/gm) ?? []).length; + const abstractMethods = (content.match(abstractMethodRe) ?? []).length; + classes.push({ name, methodCount: allMethods, abstractMethodCount: abstractMethods, sourceFile: filePath }); + } + return classes; + }, +}); + +// Agent role: Find all abstract classes in TypeScript source files and report their method counts. +const tsAbstractClassFinder = agent({ + model: "small", + instructions: p`Find all abstract classes in TypeScript source files. +Files to scan: ${p.glob("src/**/*.ts")} +Use extractAbstractClasses on each file. +Aggregate results into a record keyed by class name. +Return: +- classes: record mapping class name to { methodCount, abstractMethodCount, sourceFile } +- totalClasses: total number of abstract classes found +- totalAbstractMethods: sum of all abstractMethodCount values +- mostAbstractFile: path of the file with the most abstract classes, or omit if none`, + output: s.object({ + classes: s.record(s.object({ + methodCount: s.int, + abstractMethodCount: s.int, + sourceFile: s.path, + })), + totalClasses: s.int, + totalAbstractMethods: s.int, + mostAbstractFile: s.optional(s.string), + }), + tools: [extractAbstractClasses], + addons: [steering()], +}); + +export default tsAbstractClassFinder; +``` diff --git a/skills/rig/samples/419-dockerfile-env-inspector.md b/skills/rig/samples/419-dockerfile-env-inspector.md new file mode 100644 index 0000000..524c634 --- /dev/null +++ b/skills/rig/samples/419-dockerfile-env-inspector.md @@ -0,0 +1,65 @@ +# 419 - Dockerfile ENV Inspector + +```rig +import { agent, p, s, defineTool, repair } from "rig"; +import { readFile } from "node:fs/promises"; + +const extractDockerfileEnv = defineTool("extractDockerfileEnv", { + description: "Extract ENV instructions from a Dockerfile and classify each variable as build-time or runtime", + parameters: s.object({ filePath: s.path }), + handler: async ({ filePath }: { filePath: string }) => { + const content = await readFile(filePath, "utf8"); + const results: Record = {}; + // Handle ENV KEY VALUE form + const singleRe = /^ENV\s+([A-Z_][A-Z0-9_]*)\s+(.+)$/gm; + let m: RegExpExecArray | null; + while ((m = singleRe.exec(content)) !== null) { + const key = m[1]; + const value = m[2].trim(); + const envType = /^(BUILD_|CI_)/.test(key) ? "build-time" as const : "runtime" as const; + results[key] = { value, envType }; + } + // Handle ENV KEY=VALUE form + const assignRe = /^ENV\s+(.+)$/gm; + while ((m = assignRe.exec(content)) !== null) { + const pairs = m[1].split(/\s+/); + for (const pair of pairs) { + const eqIdx = pair.indexOf("="); + if (eqIdx > 0) { + const key = pair.slice(0, eqIdx); + const value = pair.slice(eqIdx + 1); + const envType = /^(BUILD_|CI_)/.test(key) ? "build-time" as const : "runtime" as const; + results[key] = { value, envType }; + } + } + } + return { filePath, envVars: results }; + }, +}); + +// Agent role: Parse ENV instructions from all Dockerfiles and classify each variable as build-time or runtime. +const dockerfileEnvInspector = agent({ + model: "small", + instructions: p`Parse ENV instructions from all Dockerfiles in the workspace. +Dockerfiles: ${p.bash("find . -name 'Dockerfile*' -not -path '*/node_modules/*'")} +Use extractDockerfileEnv on each file path. +Return: +- files: record mapping file path to { envVars: record mapping var name to { value, envType } } +- totalVars: total number of ENV variables found across all files +- totalFiles: number of files processed`, + output: s.object({ + files: s.record(s.object({ + envVars: s.record(s.object({ + value: s.string, + envType: s.enum("build-time", "runtime"), + })), + })), + totalVars: s.int, + totalFiles: s.int, + }), + tools: [extractDockerfileEnv], + addons: [repair()], +}); + +export default dockerfileEnvInspector; +``` diff --git a/skills/rig/samples/420-npm-script-prefix-analyzer.md b/skills/rig/samples/420-npm-script-prefix-analyzer.md new file mode 100644 index 0000000..34a561e --- /dev/null +++ b/skills/rig/samples/420-npm-script-prefix-analyzer.md @@ -0,0 +1,45 @@ +# 420 - NPM Script Prefix Analyzer + +```rig +import { agent, p, s, defineTool, steering } from "rig"; + +const classifyScriptPrefix = defineTool("classifyScriptPrefix", { + description: "Extract and classify the command prefix of an npm script", + parameters: s.object({ scriptName: s.string, command: s.string }), + handler: ({ scriptName: _scriptName, command }: { scriptName: string; command: string }) => { + const firstToken = command.trim().split(/\s+/)[0] ?? ""; + let prefixClass: "node" | "ts-node" | "npx" | "shell" | "other"; + if (firstToken === "node") prefixClass = "node"; + else if (firstToken === "ts-node" || firstToken === "tsx") prefixClass = "ts-node"; + else if (firstToken === "npx") prefixClass = "npx"; + else if (["bash", "sh", "zsh", "echo", "rm", "cp", "mkdir"].includes(firstToken)) prefixClass = "shell"; + else prefixClass = "other"; + return { command, prefix: firstToken, prefixClass }; + }, +}); + +// Agent role: Analyze the command prefixes of all npm scripts and produce a summary of script types. +const npmScriptPrefixAnalyzer = agent({ + model: "small", + instructions: p`Analyze the command prefixes of all npm scripts. +package.json: ${p.read("package.json")} +For each script in the "scripts" field, use classifyScriptPrefix with the script name and its command string. +Return: +- scripts: record mapping script name to { command, prefix, prefixClass } +- totalScripts: total number of scripts +- prefixCounts: record mapping each prefixClass value to how many scripts use it`, + output: s.object({ + scripts: s.record(s.object({ + command: s.string, + prefix: s.string, + prefixClass: s.enum("node", "ts-node", "npx", "shell", "other"), + })), + totalScripts: s.int, + prefixCounts: s.record(s.int), + }), + tools: [classifyScriptPrefix], + addons: [steering()], +}); + +export default npmScriptPrefixAnalyzer; +```