From 7f035bc0c81b91fd065ca7d081533e81240ca36f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 8 Aug 2026 07:51:06 +0000 Subject: [PATCH] =?UTF-8?q?Add=2010=20rig=20samples=20371=E2=80=93380=20?= =?UTF-8?q?=E2=80=94=202026-08-08?= 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> --- .../rig/samples/371-ts-dead-export-finder.md | 59 ++++++++++++++++++ .../samples/372-package-scripts-documenter.md | 47 ++++++++++++++ .../samples/373-git-diff-stats-summarizer.md | 44 +++++++++++++ .../samples/374-dotenv-template-generator.md | 48 +++++++++++++++ .../375-ts-class-hierarchy-extractor.md | 57 +++++++++++++++++ skills/rig/samples/376-git-bisect-helper.md | 46 ++++++++++++++ .../377-yaml-key-presence-validator.md | 60 ++++++++++++++++++ .../samples/378-git-tag-release-aggregator.md | 61 +++++++++++++++++++ .../rig/samples/379-npm-outdated-reporter.md | 49 +++++++++++++++ .../samples/380-ts-string-literal-unions.md | 51 ++++++++++++++++ 10 files changed, 522 insertions(+) create mode 100644 skills/rig/samples/371-ts-dead-export-finder.md create mode 100644 skills/rig/samples/372-package-scripts-documenter.md create mode 100644 skills/rig/samples/373-git-diff-stats-summarizer.md create mode 100644 skills/rig/samples/374-dotenv-template-generator.md create mode 100644 skills/rig/samples/375-ts-class-hierarchy-extractor.md create mode 100644 skills/rig/samples/376-git-bisect-helper.md create mode 100644 skills/rig/samples/377-yaml-key-presence-validator.md create mode 100644 skills/rig/samples/378-git-tag-release-aggregator.md create mode 100644 skills/rig/samples/379-npm-outdated-reporter.md create mode 100644 skills/rig/samples/380-ts-string-literal-unions.md diff --git a/skills/rig/samples/371-ts-dead-export-finder.md b/skills/rig/samples/371-ts-dead-export-finder.md new file mode 100644 index 0000000..1140787 --- /dev/null +++ b/skills/rig/samples/371-ts-dead-export-finder.md @@ -0,0 +1,59 @@ +# 371 - TypeScript Dead Export Finder + +```rig +import { agent, defineTool, p, s, repair, steering } from "rig"; +import { readFile } from "node:fs/promises"; + +const findUnusedExports = defineTool("findUnusedExports", { + description: "Given a list of TS file paths and their exported symbols, return which exports are not imported in any other file", + parameters: s.object({ + files: s.array(s.string("file path")), + }), + async handler({ files }) { + const exportedSymbols: Record = {}; + const allContent: string[] = []; + for (const f of files) { + try { + const src = await readFile(f, "utf8"); + allContent.push(src); + const matches = [...src.matchAll(/^export\s+(?:const|function|class|type|interface|enum)\s+(\w+)/gm)]; + exportedSymbols[f] = matches.map((m) => m[1]); + } catch { + exportedSymbols[f] = []; + } + } + const combinedContent = allContent.join("\n"); + const unusedExports: Record = {}; + for (const [file, symbols] of Object.entries(exportedSymbols)) { + const unused = symbols.filter((sym) => { + const importPattern = new RegExp(`import[^;]+\\b${sym}\\b`); + return !importPattern.test(combinedContent); + }); + if (unused.length > 0) unusedExports[file] = unused; + } + return JSON.stringify(unusedExports); + }, +}); + +// Agent role: find TypeScript exported symbols that are not imported anywhere else in the project. +const deadExportFinder = agent({ + model: "small", + instructions: p`You are analyzing a TypeScript project for dead exports. + +Files in the project: +${p.glob("**/*.ts")} + +Use the findUnusedExports tool with all discovered .ts file paths (excluding node_modules) to identify exported symbols that are never imported. + +Return the results in the declared output schema.`, + output: s.object({ + unusedExports: s.record(s.array(s.string)), + totalUnused: s.int, + hasDeadCode: s.boolean, + }), + tools: [findUnusedExports], + addons: [steering(), repair()], +}); + +export default deadExportFinder; +``` diff --git a/skills/rig/samples/372-package-scripts-documenter.md b/skills/rig/samples/372-package-scripts-documenter.md new file mode 100644 index 0000000..cfe8415 --- /dev/null +++ b/skills/rig/samples/372-package-scripts-documenter.md @@ -0,0 +1,47 @@ +# 372 - Package Scripts Documenter + +```rig +import { agent, defineTool, p, s, repair } from "rig"; + +const inferScriptPurpose = defineTool("inferScriptPurpose", { + description: "Classify a npm script command into a category", + parameters: s.object({ + name: s.string("script name"), + command: s.string("script command"), + }), + handler({ name, command }) { + const cmd = command.toLowerCase(); + const nm = name.toLowerCase(); + if (/\bbuild\b|\btsc\b|\brollup\b|\bvite build\b/.test(cmd) || nm.includes("build")) return "build" as const; + if (/\btest\b|\bvitest\b|\bjest\b|\bmocha\b/.test(cmd) || nm.includes("test")) return "test" as const; + if (/\blint\b|\beslint\b|\bprettier\b/.test(cmd) || nm.includes("lint") || nm.includes("format")) return "lint" as const; + if (/\brelease\b|\bpublish\b|\bchangeset\b/.test(cmd) || nm.includes("release")) return "release" as const; + if (/\bdev\b|\bwatch\b|\bstart\b/.test(cmd) || nm.includes("dev") || nm.includes("start")) return "dev" as const; + return "other" as const; + }, +}); + +// Agent role: document all npm scripts from package.json into a SCRIPTS.md file. +const scriptsDocumenter = agent({ + model: "small", + instructions: p`Read the project package.json: +${p.read("package.json")} + +Use inferScriptPurpose to classify each script. Then write a SCRIPTS.md file documenting each script with its purpose, category, and command using ${p.write("SCRIPTS.md", "# Scripts\n\n\n")}. + +Return the output schema with scripts metadata and the outputFile path.`, + output: s.object({ + scripts: s.record(s.object({ + purpose: s.enum("build", "test", "lint", "release", "dev", "other"), + category: s.string, + command: s.string, + })), + documentedCount: s.int, + outputFile: s.path, + }), + tools: [inferScriptPurpose], + addons: [repair()], +}); + +export default scriptsDocumenter; +``` diff --git a/skills/rig/samples/373-git-diff-stats-summarizer.md b/skills/rig/samples/373-git-diff-stats-summarizer.md new file mode 100644 index 0000000..c0e81fd --- /dev/null +++ b/skills/rig/samples/373-git-diff-stats-summarizer.md @@ -0,0 +1,44 @@ +# 373 - Git Diff Stats Summarizer + +```rig +import { agent, defineTool, p, s, repair } from "rig"; + +const classifyDiffEntry = defineTool("classifyDiffEntry", { + description: "Classify a diff file entry as added, modified, deleted, or renamed based on its path and stats", + parameters: s.object({ + path: s.string("file path from diff"), + additions: s.int, + deletions: s.int, + }), + handler({ path, additions, deletions }) { + if (path.includes(" => ")) return "renamed" as const; + if (additions > 0 && deletions === 0) return "added" as const; + if (deletions > 0 && additions === 0) return "deleted" as const; + return "modified" as const; + }, +}); + +// Agent role: summarize git diff stats between HEAD~1 and HEAD. +const diffStatsSummarizer = agent({ + model: "small", + instructions: p`Analyze the git diff stats for the most recent commit: +${p.bash("git diff --numstat HEAD~1 HEAD 2>/dev/null || git diff --numstat HEAD 2>/dev/null || echo 'no diff available'")} + +For each file entry, use classifyDiffEntry to determine its classification. Return the full structured summary.`, + output: s.object({ + files: s.array(s.object({ + path: s.string, + additions: s.int, + deletions: s.int, + classification: s.enum("added", "modified", "deleted", "renamed"), + })), + totalAdditions: s.int, + totalDeletions: s.int, + mostChangedFile: s.optional(s.string), + }), + tools: [classifyDiffEntry], + addons: [repair()], +}); + +export default diffStatsSummarizer; +``` diff --git a/skills/rig/samples/374-dotenv-template-generator.md b/skills/rig/samples/374-dotenv-template-generator.md new file mode 100644 index 0000000..f71592f --- /dev/null +++ b/skills/rig/samples/374-dotenv-template-generator.md @@ -0,0 +1,48 @@ +# 374 - Dotenv Template Generator + +```rig +import { agent, defineTool, p, s, repair } from "rig"; +import { readFile } from "node:fs/promises"; + +const extractEnvReferences = defineTool("extractEnvReferences", { + description: "Extract all process.env.VARNAME references from a TypeScript file", + parameters: s.object({ filePath: s.string("path to TypeScript file") }), + async handler({ filePath }) { + try { + const src = await readFile(filePath, "utf8"); + const matches = [...src.matchAll(/process\.env\.([A-Z_][A-Z0-9_]*)/g)]; + return [...new Set(matches.map((m) => m[1]))].join(","); + } catch { + return ""; + } + }, +}); + +// Agent role: generate a .env.template file from process.env references found in source files. +const dotenvTemplateGenerator = agent({ + model: "small", + instructions: p`Generate a .env.template for this project. + +Existing .env (if any): +${p.readOptional(".env", "# no .env found")} + +TypeScript source files: +${p.glob("src/**/*.ts")} + +For each TypeScript file path listed above, call extractEnvReferences to find process.env.VAR_NAME references. +Collect all unique variable names, compare with those already in .env, identify undocumented ones. +Then write a .env.template file using ${p.write(".env.template", "# Environment variables\n")}. + +Return the output schema with templatePath, envKeys, undocumentedKeys, and templateGenerated.`, + output: s.object({ + templatePath: s.path, + envKeys: s.array(s.string), + undocumentedKeys: s.array(s.string), + templateGenerated: s.boolean, + }), + tools: [extractEnvReferences], + addons: [repair()], +}); + +export default dotenvTemplateGenerator; +``` diff --git a/skills/rig/samples/375-ts-class-hierarchy-extractor.md b/skills/rig/samples/375-ts-class-hierarchy-extractor.md new file mode 100644 index 0000000..eae4fee --- /dev/null +++ b/skills/rig/samples/375-ts-class-hierarchy-extractor.md @@ -0,0 +1,57 @@ +# 375 - TypeScript Class Hierarchy Extractor + +```rig +import { agent, defineTool, p, s, steering } from "rig"; +import { readFile } from "node:fs/promises"; + +const extractClassInfo = defineTool("extractClassInfo", { + description: "Extract class declarations with extends/implements from a TypeScript file", + parameters: s.object({ filePath: s.string("path to TypeScript file") }), + async handler({ filePath }) { + try { + const src = await readFile(filePath, "utf8"); + const classPattern = /(?:abstract\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?(?:\s+implements\s+([\w,\s]+))?/g; + const results: Array<{ name: string; parent: string | null; interfaces: string[]; isAbstract: boolean }> = []; + for (const m of src.matchAll(classPattern)) { + results.push({ + name: m[1], + parent: m[2] ?? null, + interfaces: m[3] ? m[3].split(",").map((s: string) => s.trim()).filter(Boolean) : [], + isAbstract: src.slice(Math.max(0, m.index! - 10), m.index!).includes("abstract"), + }); + } + return JSON.stringify(results); + } catch { + return "[]"; + } + }, +}); + +// Agent role: build a class hierarchy map from all TypeScript files in the project. +const classHierarchyExtractor = agent({ + model: "small", + maxTurns: 6, + instructions: p`Extract the class hierarchy from this TypeScript project. + +TypeScript files found: +${p.bash("find . -name '*.ts' -not -path '*/node_modules/*' -not -path '*/.git/*' 2>/dev/null | head -50")} + +For each file, use extractClassInfo to find class declarations. Collect all class info, build a hierarchy by computing inheritance depth (root classes = depth 0, subclasses = parent depth + 1). + +Return the complete class hierarchy in the output schema.`, + output: s.object({ + classes: s.record(s.object({ + parent: s.optional(s.string), + interfaces: s.array(s.string), + isAbstract: s.boolean, + depth: s.int, + })), + maxDepth: s.int, + rootClasses: s.array(s.string), + }), + tools: [extractClassInfo], + addons: [steering()], +}); + +export default classHierarchyExtractor; +``` diff --git a/skills/rig/samples/376-git-bisect-helper.md b/skills/rig/samples/376-git-bisect-helper.md new file mode 100644 index 0000000..c9b1abd --- /dev/null +++ b/skills/rig/samples/376-git-bisect-helper.md @@ -0,0 +1,46 @@ +# 376 - Git Bisect Helper + +```rig +import { agent, defineTool, p, s, steering } from "rig"; + +const selectMidpoint = defineTool("selectMidpoint", { + description: "Select the midpoint commit from a range of commits for binary search", + parameters: s.object({ + commits: s.array(s.string("commit hash oneline")), + }), + handler({ commits }) { + if (commits.length === 0) return JSON.stringify({ hash: null, index: -1 }); + const mid = Math.floor(commits.length / 2); + const line = commits[mid]; + const hash = line.split(" ")[0]; + return JSON.stringify({ hash, index: mid, total: commits.length }); + }, +}); + +// Agent role: perform a simulated git bisect to identify a suspect bad commit. +const gitBisectHelper = agent({ + model: "small", + maxTurns: 8, + instructions: p`Perform a git bisect analysis on the recent commit history. + +Recent commits: +${p.bash("git log --oneline -20 2>/dev/null || echo 'no git history available'")} + +Use selectMidpoint to perform binary search steps over the commit list. Simulate a bisect by selecting midpoints to narrow down the suspect commit range. After enough steps (3-4), pick the most suspect commit based on the bisect pattern. + +Return the results in the output schema.`, + output: s.object({ + suspectCommit: s.optional(s.string), + stepsRun: s.int, + commitRange: s.object({ + start: s.string, + end: s.string, + }), + confidence: s.enum("high", "medium", "low"), + }), + tools: [selectMidpoint], + addons: [steering()], +}); + +export default gitBisectHelper; +``` diff --git a/skills/rig/samples/377-yaml-key-presence-validator.md b/skills/rig/samples/377-yaml-key-presence-validator.md new file mode 100644 index 0000000..47a3c4e --- /dev/null +++ b/skills/rig/samples/377-yaml-key-presence-validator.md @@ -0,0 +1,60 @@ +# 377 - YAML Key Presence Validator + +```rig +import { agent, defineTool, p, s, repair } from "rig"; + +const checkYamlKey = defineTool("checkYamlKey", { + description: "Check whether a dot-notation key path exists in YAML content", + parameters: s.object({ + content: s.string("YAML file content"), + keyPath: s.string("dot-notation key path like 'server.port'"), + }), + handler({ content, keyPath }) { + const parts = keyPath.split("."); + let present = false; + // Check if all parts of the key path appear in order as indented keys + let remaining = content; + for (const part of parts) { + const pattern = new RegExp(`(?:^|\\n)\\s*${part}\\s*:`, "m"); + if (pattern.test(remaining)) { + const idx = remaining.search(pattern); + remaining = remaining.slice(idx); + present = true; + } else { + present = false; + break; + } + } + return present ? "present" : "missing"; + }, +}); + +// Agent role: validate that required keys are present in a YAML configuration file. +const yamlKeyValidator = agent({ + model: "small", + input: s.object({ + yamlFile: s.path, + requiredKeys: s.array(s.string), + }), + instructions: p`Validate a YAML file for required key presence. + +YAML file content: +${p.readInput("yamlFile")} + +Required keys to check: use the input.requiredKeys array. + +For each required key, call checkYamlKey with the full file content and the key path. Collect which keys are present and which are missing. + +Return the output schema with missingKeys, presentKeys, allPresent, and checkedKeys.`, + output: s.object({ + missingKeys: s.array(s.string), + presentKeys: s.array(s.string), + allPresent: s.boolean, + checkedKeys: s.int, + }), + tools: [checkYamlKey], + addons: [repair()], +}); + +export default yamlKeyValidator; +``` diff --git a/skills/rig/samples/378-git-tag-release-aggregator.md b/skills/rig/samples/378-git-tag-release-aggregator.md new file mode 100644 index 0000000..6d6ea3b --- /dev/null +++ b/skills/rig/samples/378-git-tag-release-aggregator.md @@ -0,0 +1,61 @@ +# 378 - Git Tag Release Aggregator + +```rig +import { agent, defineTool, p, s, steering } from "rig"; + +const fetchTagCommits = defineTool("fetchTagCommits", { + description: "Get commit count and date range between two git tags or refs", + parameters: s.object({ + fromTag: s.string("earlier tag or ref"), + toTag: s.string("later tag or ref (use HEAD for latest)"), + }), + handler({ fromTag, toTag }) { + const { execSync } = require("node:child_process"); + try { + const log = execSync( + `git log ${fromTag}..${toTag} --oneline --format="%H %ai" 2>/dev/null | head -100`, + { encoding: "utf8" } + ).trim(); + const lines = log ? log.split("\n").filter(Boolean) : []; + const dates = lines.map((l: string) => l.split(" ")[1]).filter(Boolean); + return JSON.stringify({ + commitCount: lines.length, + start: dates[dates.length - 1] ?? "", + end: dates[0] ?? "", + }); + } catch { + return JSON.stringify({ commitCount: 0, start: "", end: "" }); + } + }, +}); + +// Agent role: aggregate release metadata for each git tag in the repository. +const gitTagReleaseAggregator = agent({ + model: "small", + maxTurns: 5, + instructions: p`Aggregate release information for git tags. + +Available tags (newest first): +${p.bash("git tag -l --sort=-version:refname 2>/dev/null | head -20 || echo 'no tags found'")} + +For each pair of adjacent tags, call fetchTagCommits to get commit count and date range between them. For the most recent tag, fetch commits from it to HEAD. + +Return the output schema with tags array, latestTag, and totalTags.`, + output: s.object({ + tags: s.array(s.object({ + name: s.string, + commitCount: s.int, + dateRange: s.object({ + start: s.string, + end: s.string, + }), + })), + latestTag: s.string, + totalTags: s.int, + }), + tools: [fetchTagCommits], + addons: [steering()], +}); + +export default gitTagReleaseAggregator; +``` diff --git a/skills/rig/samples/379-npm-outdated-reporter.md b/skills/rig/samples/379-npm-outdated-reporter.md new file mode 100644 index 0000000..5e54a65 --- /dev/null +++ b/skills/rig/samples/379-npm-outdated-reporter.md @@ -0,0 +1,49 @@ +# 379 - NPM Outdated Reporter + +```rig +import { agent, defineTool, p, s, repair } from "rig"; + +const classifyUpdateSeverity = defineTool("classifyUpdateSeverity", { + description: "Classify a package version update as major, minor, or patch", + parameters: s.object({ + current: s.string("current installed version"), + latest: s.string("latest available version"), + }), + handler({ current, latest }) { + const parse = (v: string) => v.replace(/^[^0-9]*/, "").split(".").map(Number); + const [curMajor] = parse(current); + const [latMajor, latMinor] = parse(latest); + const [, curMinor] = parse(current); + if (latMajor > curMajor) return "major" as const; + if (latMinor > curMinor) return "minor" as const; + return "patch" as const; + }, +}); + +// Agent role: report outdated npm packages and classify each update by severity. +const npmOutdatedReporter = agent({ + model: "small", + instructions: p`Check for outdated npm packages: +${p.bash("npm outdated --json 2>/dev/null || echo '{}'")} + +For each outdated package, use classifyUpdateSeverity with its current and latest versions. Determine if it is a breaking change (major updates are breaking). + +Return the output schema with packages record, majorCount, minorCount, and patchCount.`, + output: s.object({ + packages: s.record(s.object({ + current: s.string, + wanted: s.string, + latest: s.string, + severity: s.enum("major", "minor", "patch"), + isBreaking: s.boolean, + })), + majorCount: s.int, + minorCount: s.int, + patchCount: s.int, + }), + tools: [classifyUpdateSeverity], + addons: [repair()], +}); + +export default npmOutdatedReporter; +``` diff --git a/skills/rig/samples/380-ts-string-literal-unions.md b/skills/rig/samples/380-ts-string-literal-unions.md new file mode 100644 index 0000000..8c304d9 --- /dev/null +++ b/skills/rig/samples/380-ts-string-literal-unions.md @@ -0,0 +1,51 @@ +# 380 - TypeScript String Literal Unions + +```rig +import { agent, defineTool, p, s, steering } from "rig"; +import { readFile } from "node:fs/promises"; + +const extractStringLiteralUnions = defineTool("extractStringLiteralUnions", { + description: "Extract string literal union type aliases from a TypeScript file", + parameters: s.object({ filePath: s.string("path to TypeScript file") }), + async handler({ filePath }) { + try { + const src = await readFile(filePath, "utf8"); + const pattern = /type\s+(\w+)\s*=\s*((?:'[^']*'|"[^"]*")\s*(?:\|\s*(?:'[^']*'|"[^"]*")\s*)+);/g; + const results: Record = {}; + for (const m of src.matchAll(pattern)) { + const name = m[1]; + const valueStr = m[2]; + const values = [...valueStr.matchAll(/['"]([^'"]+)['"]/g)].map((v) => v[1]); + if (values.length > 0) results[name] = values; + } + return JSON.stringify(results); + } catch { + return "{}"; + } + }, +}); + +// Agent role: extract all string literal union type aliases from TypeScript source files. +const stringLiteralUnionExtractor = agent({ + model: "small", + maxTurns: 5, + instructions: p`Extract string literal union types from TypeScript files. + +TypeScript source files: +${p.glob("src/**/*.ts")} + +For each file path above, call extractStringLiteralUnions to find type aliases that are string literal unions (e.g., type Status = 'active' | 'inactive'). Collect all results, count total types and total values, and identify the largest union by value count. + +Return the output schema.`, + output: s.object({ + types: s.record(s.array(s.string)), + totalTypes: s.int, + totalValues: s.int, + largestUnion: s.optional(s.string), + }), + tools: [extractStringLiteralUnions], + addons: [steering()], +}); + +export default stringLiteralUnionExtractor; +```