From a3599c6975e2b6ab6c1e4403eb1dc9e6f6e81d06 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2026 14:21:34 +0000 Subject: [PATCH] =?UTF-8?q?Add=2010=20rig=20samples=20(260=E2=80=93269)=20?= =?UTF-8?q?=E2=80=94=202026-07-28?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 260-git-submodule-health: p.bash + p.readOptional + defineTool - 261-ts-decorator-scanner: p.bash + defineTool classifyDecorator - 262-dotfile-inventory: p.bash + defineTool + repair addon - 263-regex-pattern-tester: input schema + defineTool + repair addon - 264-commit-churn-classifier: p.bash + steering addon + s.record - 265-npm-package-size: p.bash + p.readOptional + defineTool - 266-tsconfig-option-analyzer: p.read + p.glob + defineTool + repair - 267-git-patch-summarizer: p.bash + defineTool + steering addon - 268-npm-lifecycle-analyzer: p.read + defineTool classifyScript - 269-ts-jsdoc-coverage: p.bash + async defineTool + repair addon Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../rig/samples/260-git-submodule-health.md | 53 +++++++++++++ .../rig/samples/261-ts-decorator-scanner.md | 49 ++++++++++++ skills/rig/samples/262-dotfile-inventory.md | 52 +++++++++++++ .../rig/samples/263-regex-pattern-tester.md | 63 ++++++++++++++++ .../samples/264-commit-churn-classifier.md | 31 ++++++++ skills/rig/samples/265-npm-package-size.md | 45 +++++++++++ .../samples/266-tsconfig-option-analyzer.md | 56 ++++++++++++++ .../rig/samples/267-git-patch-summarizer.md | 54 ++++++++++++++ .../rig/samples/268-npm-lifecycle-analyzer.md | 51 +++++++++++++ skills/rig/samples/269-ts-jsdoc-coverage.md | 74 +++++++++++++++++++ 10 files changed, 528 insertions(+) create mode 100644 skills/rig/samples/260-git-submodule-health.md create mode 100644 skills/rig/samples/261-ts-decorator-scanner.md create mode 100644 skills/rig/samples/262-dotfile-inventory.md create mode 100644 skills/rig/samples/263-regex-pattern-tester.md create mode 100644 skills/rig/samples/264-commit-churn-classifier.md create mode 100644 skills/rig/samples/265-npm-package-size.md create mode 100644 skills/rig/samples/266-tsconfig-option-analyzer.md create mode 100644 skills/rig/samples/267-git-patch-summarizer.md create mode 100644 skills/rig/samples/268-npm-lifecycle-analyzer.md create mode 100644 skills/rig/samples/269-ts-jsdoc-coverage.md diff --git a/skills/rig/samples/260-git-submodule-health.md b/skills/rig/samples/260-git-submodule-health.md new file mode 100644 index 0000000..44e762e --- /dev/null +++ b/skills/rig/samples/260-git-submodule-health.md @@ -0,0 +1,53 @@ +# 260 - Git Submodule Health + +```rig +import { agent, p, s, defineTool } from "rig"; + +const parseSubmoduleStatus = defineTool("parseSubmoduleStatus", { + description: "Parse a git submodule status line into structured fields.", + parameters: s.object({ line: s.string }), + handler({ line }) { + const statusChar = line[0] ?? " "; + const rest = line.slice(1).trim(); + const parts = rest.split(/\s+/); + const sha = parts[0] ?? ""; + const path = parts[1] ?? ""; + let status: "clean" | "modified" | "uninitialized" | "conflict" = "clean"; + if (statusChar === "+") status = "modified"; + else if (statusChar === "-") status = "uninitialized"; + else if (statusChar === "U") status = "conflict"; + const describeMatch = rest.match(/\(([^)]+)\)$/); + return { path, sha, status, describe: describeMatch?.[1] }; + }, +}); + +// Agent role: Inventory git submodules and report their health status. +const gitSubmoduleHealth = agent({ + model: "small", + instructions: p`Check the health of all git submodules. + +Submodule status: +${p.bash("git submodule status 2>/dev/null || echo '(no submodules)'")} + +Submodule config: +${p.readOptional(".gitmodules", "(no .gitmodules file)")} + +For each non-empty status line, call parseSubmoduleStatus to extract path, sha, status, and optional describe. +Return submodules array, allClean (true if every status is "clean"), and totalCount.`, + tools: [parseSubmoduleStatus], + output: s.object({ + submodules: s.array( + s.object({ + path: s.path, + sha: s.string, + status: s.enum("clean", "modified", "uninitialized", "conflict"), + describe: s.optional(s.string), + }) + ), + allClean: s.boolean, + totalCount: s.int, + }), +}); + +export default gitSubmoduleHealth; +``` diff --git a/skills/rig/samples/261-ts-decorator-scanner.md b/skills/rig/samples/261-ts-decorator-scanner.md new file mode 100644 index 0000000..7fbf738 --- /dev/null +++ b/skills/rig/samples/261-ts-decorator-scanner.md @@ -0,0 +1,49 @@ +# 261 - TypeScript Decorator Scanner + +```rig +import { agent, p, s, defineTool } from "rig"; + +const classifyDecorator = defineTool("classifyDecorator", { + description: "Classify a TypeScript decorator by its name into a usage type.", + parameters: s.object({ name: s.string }), + handler({ name }) { + const lower = name.toLowerCase(); + if (/^(component|module|injectable|controller|service|directive|pipe)$/.test(lower)) + return { type: "class" }; + if (/^(get|post|put|delete|patch|route|httpcode|header|body|query|param)$/.test(lower)) + return { type: "method" }; + if (/^(column|primarycolumn|primarygeneratedcolumn|onetomany|manytoone|manyto)/.test(lower)) + return { type: "property" }; + if (/^(param|body|query|request|response|headers|ip|session|uploadedfile)$/.test(lower)) + return { type: "parameter" }; + return { type: "unknown" }; + }, +}); + +// Agent role: Scan TypeScript files for decorator usage and classify each decorator. +const tsDecoratorScanner = agent({ + model: "small", + instructions: p`Scan for TypeScript decorator patterns in the repository. + +Decorator occurrences: +${p.bash("grep -rn '@[A-Z][A-Za-z]*' --include='*.ts' . 2>/dev/null | head -60 || echo 'none found'")} + +For each unique decorator name found, call classifyDecorator to get its type. +Build a record keyed by decorator name (without @) with count and type. +Set hasExperimental to true if any decorators from experimental frameworks (e.g., legacy Angular, old NestJS) are found. +Return decorators record, totalCount (sum of all counts), and hasExperimental.`, + tools: [classifyDecorator], + output: s.object({ + decorators: s.record( + s.object({ + count: s.int, + type: s.enum("class", "method", "property", "parameter", "unknown"), + }) + ), + totalCount: s.int, + hasExperimental: s.boolean, + }), +}); + +export default tsDecoratorScanner; +``` diff --git a/skills/rig/samples/262-dotfile-inventory.md b/skills/rig/samples/262-dotfile-inventory.md new file mode 100644 index 0000000..5d6c230 --- /dev/null +++ b/skills/rig/samples/262-dotfile-inventory.md @@ -0,0 +1,52 @@ +# 262 - Dotfile Inventory + +```rig +import { agent, p, s, defineTool, repair } from "rig"; + +const categorizeDotfile = defineTool("categorizeDotfile", { + description: "Categorize a dotfile by its filename into a purpose category.", + parameters: s.object({ filename: s.string }), + handler({ filename }) { + const name = filename.replace(/^\./, "").toLowerCase(); + if (/^(bash|zsh|fish|profile|bashrc|zshrc|bash_profile|zprofile|inputrc)/.test(name)) + return { category: "shell" }; + if (/^(vimrc|vim|nvim|emacs|nano|editorconfig|prettierrc|eslintrc)/.test(name)) + return { category: "editor" }; + if (/^(gitconfig|gitignore|gitattributes|gitmessage|git)/.test(name)) + return { category: "git" }; + if (/^(ssh|known_hosts|authorized_keys)/.test(name)) + return { category: "ssh" }; + if (/^(npmrc|yarnrc|pnpmfile|curlrc|wgetrc|tmux|screenrc|docker)/.test(name)) + return { category: "tool" }; + return { category: "other" }; + }, +}); + +// Agent role: Inventory dotfiles in the home directory and categorize each one. +const dotfileInventory = agent({ + model: "small", + addons: repair(), + instructions: p`Inventory dotfiles in the home directory and categorize each. + +Dotfiles found: +${p.bash("find ~ -maxdepth 1 -name '.*' -type f 2>/dev/null | head -40 || echo '(none found)'")} + +For each dotfile found, call categorizeDotfile with its filename (basename). +Build a record keyed by filename with category and a short purpose description. +Also compute categorySummary as a record of category → count of dotfiles in that category. +Return dotfiles record, totalFound (integer count), and categorySummary.`, + tools: [categorizeDotfile], + output: s.object({ + dotfiles: s.record( + s.object({ + category: s.enum("shell", "editor", "git", "ssh", "tool", "other"), + purpose: s.string, + }) + ), + totalFound: s.int, + categorySummary: s.record(s.int), + }), +}); + +export default dotfileInventory; +``` diff --git a/skills/rig/samples/263-regex-pattern-tester.md b/skills/rig/samples/263-regex-pattern-tester.md new file mode 100644 index 0000000..92135b9 --- /dev/null +++ b/skills/rig/samples/263-regex-pattern-tester.md @@ -0,0 +1,63 @@ +# 263 - Regex Pattern Tester + +```rig +import { agent, p, s, defineTool, repair } from "rig"; + +// Agent role: Run regex patterns against test cases 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: p`Run each regex pattern against its test cases and report results. + +Input patterns and test cases: +${p.json("input")} + +Use the runRegexTest tool for each pattern+testCase combination. +For each pattern, build a result with name, passed count, failed count, and allPassed. +Return results array, totalPatterns, passCount (patterns where allPassed=true), +failCount (patterns where allPassed=false), and overall allPassed.`, + tools: [ + 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 { + return { matched: new RegExp(regex).test(input) }; + } catch (e) { + return { matched: false, error: String(e) }; + } + }, + }), + ], + output: s.object({ + results: s.array( + s.object({ + name: s.string, + passed: s.int, + failed: s.int, + allPassed: s.boolean, + }) + ), + totalPatterns: s.int, + passCount: s.int, + failCount: s.int, + allPassed: s.boolean, + }), +}); + +export default regexPatternTester; +``` diff --git a/skills/rig/samples/264-commit-churn-classifier.md b/skills/rig/samples/264-commit-churn-classifier.md new file mode 100644 index 0000000..b9bb426 --- /dev/null +++ b/skills/rig/samples/264-commit-churn-classifier.md @@ -0,0 +1,31 @@ +# 264 - Commit Churn Classifier + +```rig +import { agent, p, s, steering } from "rig"; + +// Agent role: classify repository files by commit churn frequency and assign a risk level. +const commitChurnClassifier = agent({ + model: "small", + addons: steering({ message: "Assign riskLevel based on churnCount: >20=critical, >10=volatile, >5=active, else stable. Only include files from the churn output." }), + instructions: p`Classify repository files by commit churn (how frequently they change). + +File churn counts from git history: +${p.bash("git log --name-only --format='' 2>/dev/null | grep -v '^$' | sort | uniq -c | sort -rn | head -30")} + +For each file in the output, parse the churn count and assign a riskLevel: +- critical: churnCount > 20 +- volatile: churnCount > 10 +- active: churnCount > 5 +- stable: churnCount <= 5 + +Return a record keyed by file path with churnCount (integer) and riskLevel.`, + output: s.record( + s.object({ + churnCount: s.int, + riskLevel: s.enum("stable", "active", "volatile", "critical"), + }) + ), +}); + +export default commitChurnClassifier; +``` diff --git a/skills/rig/samples/265-npm-package-size.md b/skills/rig/samples/265-npm-package-size.md new file mode 100644 index 0000000..96ed1f7 --- /dev/null +++ b/skills/rig/samples/265-npm-package-size.md @@ -0,0 +1,45 @@ +# 265 - NPM Package Size + +```rig +import { agent, p, s, defineTool } from "rig"; + +// Agent role: estimate npm package publish size and rate it with a recommendation. +const npmPackageSize = agent({ + model: "small", + instructions: p`Estimate the npm package publish size and rate it. + +Files included in npm publish (dry run): +${p.bash("npm pack --dry-run 2>&1 | head -40")} + +Workspace size: +${p.bash("du -sh . 2>/dev/null | cut -f1")} + +Package metadata: +${p.readOptional("package.json", "{}")} + +Parse the npm pack output to extract individual file sizes. Use classifySize on the total. +List the top files by size. Provide a recommendation if the package is large. +Return estimatedSizeKb, topFiles (up to 5), sizeRating, and recommendation.`, + tools: [ + defineTool("classifySize", { + description: "Classify a package size in KB into a rating tier.", + parameters: s.object({ sizeKb: s.number }), + handler({ sizeKb }) { + if (sizeKb < 10) return { rating: "tiny" }; + if (sizeKb < 100) return { rating: "small" }; + if (sizeKb < 1000) return { rating: "medium" }; + if (sizeKb < 10000) return { rating: "large" }; + return { rating: "xlarge" }; + }, + }), + ], + output: s.object({ + estimatedSizeKb: s.number, + topFiles: s.array(s.object({ path: s.path, sizeKb: s.number })), + sizeRating: s.enum("tiny", "small", "medium", "large", "xlarge"), + recommendation: s.string, + }), +}); + +export default npmPackageSize; +``` diff --git a/skills/rig/samples/266-tsconfig-option-analyzer.md b/skills/rig/samples/266-tsconfig-option-analyzer.md new file mode 100644 index 0000000..726ac2d --- /dev/null +++ b/skills/rig/samples/266-tsconfig-option-analyzer.md @@ -0,0 +1,56 @@ +# 266 - TSConfig Option Analyzer + +```rig +import { agent, p, s, defineTool, repair } from "rig"; + +const parseTsConfigOption = defineTool("parseTsConfigOption", { + description: "Classify a tsconfig compilerOption by name and infer if it is recommended.", + parameters: s.object({ optionName: s.string }), + handler({ optionName }) { + const strictOptions = new Set(["strict", "noImplicitAny", "strictNullChecks", "strictFunctionTypes", "strictBindCallApply", "strictPropertyInitialization", "noImplicitThis", "alwaysStrict", "useUnknownInCatchVariables", "exactOptionalPropertyTypes", "noUncheckedIndexedAccess"]); + const perfOptions = new Set(["incremental", "composite", "tsBuildInfoFile", "isolatedModules", "skipLibCheck", "skipDefaultLibCheck"]); + const outputOptions = new Set(["outDir", "outFile", "rootDir", "declarationDir", "declaration", "declarationMap", "sourceMap", "inlineSources", "inlineSourceMap", "emitDeclarationOnly", "noEmit"]); + const pathOptions = new Set(["paths", "baseUrl", "rootDirs", "typeRoots", "types"]); + if (strictOptions.has(optionName)) return { category: "strict", recommended: true }; + if (perfOptions.has(optionName)) return { category: "perf", recommended: true }; + if (outputOptions.has(optionName)) return { category: "output", recommended: false }; + if (pathOptions.has(optionName)) return { category: "paths", recommended: false }; + return { category: "misc", recommended: false }; + }, +}); + +// Agent role: analyze tsconfig.json compiler options and classify each one. +const tsconfigOptionAnalyzer = agent({ + model: "small", + addons: repair(), + instructions: p`Analyze TypeScript compiler options from tsconfig files in this project. + +Main tsconfig.json: +${p.read("tsconfig.json")} + +Additional tsconfig files found: +${p.glob("tsconfig.*.json")} + +For each compilerOption key found in the tsconfig files, call parseTsConfigOption to get +its category and recommended status. +Build an options record keyed by option name with value (from config), category, and recommended. +Count strictCount (number of strict-category options enabled). +Set hasIsolatedModules to true if isolatedModules is set to true. +List all config file paths found in configFilesFound.`, + tools: [parseTsConfigOption], + output: s.object({ + options: s.record( + s.object({ + value: s.unknown, + category: s.enum("strict", "perf", "output", "paths", "misc"), + recommended: s.boolean, + }) + ), + strictCount: s.int, + hasIsolatedModules: s.boolean, + configFilesFound: s.array(s.string), + }), +}); + +export default tsconfigOptionAnalyzer; +``` diff --git a/skills/rig/samples/267-git-patch-summarizer.md b/skills/rig/samples/267-git-patch-summarizer.md new file mode 100644 index 0000000..fb38a3a --- /dev/null +++ b/skills/rig/samples/267-git-patch-summarizer.md @@ -0,0 +1,54 @@ +# 267 - Git Patch Summarizer + +```rig +import { agent, p, s, defineTool, steering } from "rig"; + +const extractPatchMetadata = defineTool("extractPatchMetadata", { + description: "Extract metadata (author, subject, stats) from a git format-patch block.", + parameters: s.object({ patchBlock: s.string }), + handler({ patchBlock }) { + const authorMatch = patchBlock.match(/^From:\s*(.+)$/m); + const subjectMatch = patchBlock.match(/^Subject:\s*(?:\[PATCH[^\]]*\]\s*)?(.+)$/m); + const statsMatch = patchBlock.match(/(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?/); + return { + author: authorMatch?.[1]?.trim() ?? "unknown", + subject: subjectMatch?.[1]?.trim() ?? "unknown", + filesChanged: statsMatch ? parseInt(statsMatch[1] ?? "0", 10) : 0, + insertions: statsMatch ? parseInt(statsMatch[2] ?? "0", 10) : 0, + deletions: statsMatch ? parseInt(statsMatch[3] ?? "0", 10) : 0, + }; + }, +}); + +// Agent role: summarize git patches from recent commits with author and diffstat info. +const gitPatchSummarizer = agent({ + model: "small", + addons: steering({ message: "Split the patch output by 'From ' header lines to identify individual patch blocks before calling extractPatchMetadata." }), + instructions: p`Summarize the most recent git commits as formatted patches. + +Recent patches (last 5 commits): +${p.bash("git format-patch -5 --stdout 2>/dev/null | head -300 || echo '(no commits)'")} + +Split the output into individual patch blocks (each starts with a 'From ' line). +Call extractPatchMetadata for each block. +Return patches array with author, subject, filesChanged, insertions, deletions. +Set totalPatches to the number of patches found. +Set topContributor to the author with the most patches (omit if all are the same or none found).`, + tools: [extractPatchMetadata], + output: s.object({ + patches: s.array( + s.object({ + author: s.string, + subject: s.string, + filesChanged: s.int, + insertions: s.int, + deletions: s.int, + }) + ), + totalPatches: s.int, + topContributor: s.optional(s.string), + }), +}); + +export default gitPatchSummarizer; +``` diff --git a/skills/rig/samples/268-npm-lifecycle-analyzer.md b/skills/rig/samples/268-npm-lifecycle-analyzer.md new file mode 100644 index 0000000..5b4f540 --- /dev/null +++ b/skills/rig/samples/268-npm-lifecycle-analyzer.md @@ -0,0 +1,51 @@ +# 268 - NPM Lifecycle Analyzer + +```rig +import { agent, p, s, defineTool } from "rig"; + +const classifyScript = defineTool("classifyScript", { + description: "Classify an npm script by name into a lifecycle category.", + parameters: s.object({ scriptName: s.string, command: s.string }), + handler({ scriptName }) { + const name = scriptName.toLowerCase(); + const isHook = /^(pre|post)/.test(name); + if (/build|compile|bundle|webpack|rollup|esbuild/.test(name)) return { category: "build", isHook }; + if (/test|jest|vitest|mocha|spec/.test(name)) return { category: "test", isHook }; + if (/lint|eslint|tslint|prettier|format/.test(name)) return { category: "lint", isHook }; + if (/release|publish|deploy|version|changelog/.test(name)) return { category: "release", isHook }; + if (isHook) return { category: "hook", isHook: true }; + return { category: "other", isHook: false }; + }, +}); + +// Agent role: analyze npm lifecycle scripts in package.json and classify each one. +const npmLifecycleAnalyzer = agent({ + model: "small", + instructions: p`Analyze the npm lifecycle scripts defined in package.json. + +package.json contents: +${p.read("package.json")} + +For each entry in the "scripts" field, call classifyScript with the script name and command. +Build a scripts record keyed by script name with command, category, and isHook. +Count hookCount (scripts where isHook is true). +List missingRecommended: which of ["test", "build", "lint"] are absent from scripts. +Set hasTestScript and hasBuildScript based on whether those categories exist.`, + tools: [classifyScript], + output: s.object({ + scripts: s.record( + s.object({ + command: s.string, + category: s.enum("build", "test", "lint", "release", "hook", "other"), + isHook: s.boolean, + }) + ), + hookCount: s.int, + missingRecommended: s.array(s.string), + hasTestScript: s.boolean, + hasBuildScript: s.boolean, + }), +}); + +export default npmLifecycleAnalyzer; +``` diff --git a/skills/rig/samples/269-ts-jsdoc-coverage.md b/skills/rig/samples/269-ts-jsdoc-coverage.md new file mode 100644 index 0000000..364b3bf --- /dev/null +++ b/skills/rig/samples/269-ts-jsdoc-coverage.md @@ -0,0 +1,74 @@ +# 269 - TypeScript JSDoc Coverage + +```rig +import { agent, p, s, defineTool, repair } from "rig"; + +const analyzeFunctionComments = defineTool("analyzeFunctionComments", { + description: "Count exported functions with and without JSDoc comments in a TypeScript file.", + parameters: s.object({ filePath: s.path }), + async handler({ filePath }) { + const { readFile } = await import("node:fs/promises"); + let content: string; + try { + content = await readFile(filePath, "utf8"); + } catch { + return { documentedCount: 0, undocumentedCount: 0, coverage: 0 }; + } + const lines = content.split("\n"); + let documentedCount = 0; + let undocumentedCount = 0; + for (let i = 0; i < lines.length; i++) { + const line = lines[i] ?? ""; + if (/^\s*export\s+(async\s+)?function\s+\w+|^\s*export\s+const\s+\w+\s*=\s*(async\s*)?\(/.test(line)) { + const prevLine = lines[i - 1] ?? ""; + const prevPrevLine = lines[i - 2] ?? ""; + if (/\*\//.test(prevLine) || /\*\//.test(prevPrevLine)) { + documentedCount++; + } else { + undocumentedCount++; + } + } + } + const total = documentedCount + undocumentedCount; + return { + documentedCount, + undocumentedCount, + coverage: total > 0 ? Math.round((documentedCount / total) * 100) / 100 : 1, + }; + }, +}); + +// Agent role: measure JSDoc comment coverage for exported functions across TypeScript files. +const tsJsdocCoverage = agent({ + model: "small", + addons: repair(), + instructions: p`Measure JSDoc coverage for exported functions in TypeScript source files. + +TypeScript files in this project: +${p.bash("find . -name '*.ts' -not -path '*/node_modules/*' -not -path '*/.git/*' 2>/dev/null | head -20")} + +For each file path listed, call analyzeFunctionComments to get documentedCount, +undocumentedCount, and coverage (0–1 ratio). +Build the files record keyed by file path. +Compute overall totals: totalFunctions, documentedFunctions, coveragePercent (0–100). +List wellDocumentedFiles: file paths where coverage >= 0.8.`, + tools: [analyzeFunctionComments], + output: s.object({ + files: s.record( + s.object({ + documentedCount: s.int, + undocumentedCount: s.int, + coverage: s.number, + }) + ), + overall: s.object({ + totalFunctions: s.int, + documentedFunctions: s.int, + coveragePercent: s.number, + }), + wellDocumentedFiles: s.array(s.path), + }), +}); + +export default tsJsdocCoverage; +```