diff --git a/skills/rig/samples/270-dotfile-inventory-mapper.md b/skills/rig/samples/270-dotfile-inventory-mapper.md new file mode 100644 index 0000000..6258a62 --- /dev/null +++ b/skills/rig/samples/270-dotfile-inventory-mapper.md @@ -0,0 +1,57 @@ +# 270 - Dotfile Inventory Mapper + +```rig +import { agent, p, s, defineTool, repair } from "rig"; + +const categorizeDotfile = defineTool("categorizeDotfile", { + description: "Categorize a dotfile by its filename into a purpose and category.", + parameters: s.object({ filename: s.string }), + handler({ filename }) { + const f = filename.toLowerCase(); + if (f.includes("bash") || f.includes("zsh") || f.includes("fish") || f.includes("profile") || f.includes("aliases")) { + return { purpose: "Shell configuration and aliases", category: "shell" as const }; + } + if (f.includes("vim") || f.includes("emacs") || f.includes("vscode") || f.includes("editorconfig") || f.includes("nvim")) { + return { purpose: "Editor settings and configuration", category: "editor" as const }; + } + if (f.includes("git") || f.includes("gitconfig") || f.includes("gitignore")) { + return { purpose: "Git version control configuration", category: "git" as const }; + } + if (f.includes("ssh") || f.includes("gnupg") || f.includes("gpg")) { + return { purpose: "SSH or GPG security configuration", category: "ssh" as const }; + } + if (f.includes("npm") || f.includes("yarn") || f.includes("pip") || f.includes("cargo") || f.includes("gem")) { + return { purpose: "Package manager settings", category: "package-manager" as const }; + } + return { purpose: "Miscellaneous dotfile configuration", category: "misc" as const }; + }, +}); + +// Agent role: inventory dotfiles in the home directory and categorize them by purpose. +const dotfileInventoryMapper = agent({ + model: "small", + addons: repair(), + instructions: p`Inventory dotfiles found in the home directory. + +Discovered dotfiles: +${p.bash("find ~ -maxdepth 2 -name '.*' -type f 2>/dev/null | head -50")} + +For each file path, call categorizeDotfile with just the filename (basename). +Build the dotfiles record keyed by filename with purpose and category. +Count totalFound (number of entries in dotfiles). +Build categorySummary as a record of category -> count of files in that category.`, + tools: [categorizeDotfile], + output: s.object({ + dotfiles: s.record( + s.object({ + purpose: s.string, + category: s.enum("shell", "editor", "git", "ssh", "package-manager", "misc"), + }) + ), + totalFound: s.int, + categorySummary: s.record(s.int), + }), +}); + +export default dotfileInventoryMapper; +``` diff --git a/skills/rig/samples/271-regex-pattern-tester.md b/skills/rig/samples/271-regex-pattern-tester.md new file mode 100644 index 0000000..ef05e7b --- /dev/null +++ b/skills/rig/samples/271-regex-pattern-tester.md @@ -0,0 +1,58 @@ +# 271 - Regex Pattern Tester + +```rig +import { agent, s, defineTool, repair } from "rig"; + +const runRegexTest = 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 { + const matched = new RegExp(regex).test(input); + return { matched, error: undefined }; + } catch (e) { + return { matched: false, error: String(e) }; + } + }, +}); + +// Agent role: run all test cases for each regex pattern 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: `For each pattern in the input, call runRegexTest for every test case. +A test case passes when the matched result equals shouldMatch. +Collect failedCases (input, expected, got) for each pattern. +A pattern passes when all test cases pass. +Count passCount (patterns where passed=true) and failCount. +allPassed is true when failCount === 0.`, + tools: [runRegexTest], + output: s.object({ + results: s.array( + s.object({ + name: s.string, + passed: s.boolean, + failedCases: s.array( + s.object({ input: s.string, expected: s.boolean, got: s.boolean }) + ), + }) + ), + passCount: s.int, + failCount: s.int, + allPassed: s.boolean, + }), +}); + +export default regexPatternTester; +``` diff --git a/skills/rig/samples/272-commit-churn-classifier.md b/skills/rig/samples/272-commit-churn-classifier.md new file mode 100644 index 0000000..225a373 --- /dev/null +++ b/skills/rig/samples/272-commit-churn-classifier.md @@ -0,0 +1,40 @@ +# 272 - Commit Churn Classifier + +```rig +import { agent, p, s, defineTool, steering } from "rig"; + +const classifyRisk = defineTool("classifyRisk", { + description: "Classify a file's churn risk level based on how many times it has changed.", + parameters: s.object({ churnCount: s.int }), + handler({ churnCount }) { + if (churnCount <= 3) return "stable" as const; + if (churnCount <= 10) return "active" as const; + if (churnCount <= 25) return "volatile" as const; + return "critical" as const; + }, +}); + +// Agent role: analyze git commit history to classify file churn risk. +const commitChurnClassifier = agent({ + model: "small", + addons: steering(), + instructions: p`Analyze file churn from git commit history. + +File change frequency (count + filename): +${p.bash("git log --name-only --format='' | grep -v '^$' | sort | uniq -c | sort -rn | head -30")} + +For each line, parse the count and file path. +Call classifyRisk with the churnCount to get the riskLevel. +Build a record keyed by file path with churnCount and riskLevel. +Include only files that appear in the output above.`, + tools: [classifyRisk], + output: s.record( + s.object({ + churnCount: s.int, + riskLevel: s.enum("stable", "active", "volatile", "critical"), + }) + ), +}); + +export default commitChurnClassifier; +``` diff --git a/skills/rig/samples/273-npm-package-size-estimator.md b/skills/rig/samples/273-npm-package-size-estimator.md new file mode 100644 index 0000000..4215c9f --- /dev/null +++ b/skills/rig/samples/273-npm-package-size-estimator.md @@ -0,0 +1,43 @@ +# 273 - Npm Package Size Estimator + +```rig +import { agent, p, s, defineTool } from "rig"; + +const classifySize = defineTool("classifySize", { + description: "Classify a package size into a tier based on kilobytes.", + parameters: s.object({ sizeKb: s.number }), + handler({ sizeKb }) { + if (sizeKb < 10) return "tiny" as const; + if (sizeKb < 100) return "small" as const; + if (sizeKb < 500) return "medium" as const; + if (sizeKb < 2000) return "large" as const; + return "xlarge" as const; + }, +}); + +// Agent role: estimate NPM package publish size and classify it. +const npmPackageSizeEstimator = agent({ + model: "small", + instructions: p`Estimate the published NPM package size. + +npm pack dry-run output: +${p.bash("npm pack --dry-run 2>&1")} + +Parse the output to find: +1. The total package size in KB (look for "package size:" or "Tarball Details" lines). +2. The top individual files with their sizes. + +Call classifySize with the estimatedSizeKb to get the sizeRating. +List the top files (up to 10) as topFiles array with path and sizeKb. +Write a brief recommendation about the package size.`, + tools: [classifySize], + output: s.object({ + estimatedSizeKb: s.number, + topFiles: s.array(s.object({ path: s.string, sizeKb: s.number })), + sizeRating: s.enum("tiny", "small", "medium", "large", "xlarge"), + recommendation: s.string, + }), +}); + +export default npmPackageSizeEstimator; +``` diff --git a/skills/rig/samples/274-ts-branch-coverage-analyzer.md b/skills/rig/samples/274-ts-branch-coverage-analyzer.md new file mode 100644 index 0000000..e2bf49b --- /dev/null +++ b/skills/rig/samples/274-ts-branch-coverage-analyzer.md @@ -0,0 +1,56 @@ +# 274 - Ts Branch Coverage Analyzer + +```rig +import { agent, p, s } from "rig"; + +// Agent role: analyze TypeScript file content to identify branches and their coverage. +const branchAnalyzer = agent({ + model: "small", + instructions: `Given the TypeScript file content below, identify all branch points. +For each branch, extract: functionName (surrounding function or "module-level"), line number, +branchType (if/ternary/switch/logical/nullish), and covered (true if the branch has a test or always executes). +Return the branches array.`, + input: s.object({ fileContent: s.string, filePath: s.string }), + output: s.array( + s.object({ + functionName: s.string, + line: s.int, + branchType: s.enum("if", "ternary", "switch", "logical", "nullish"), + covered: s.boolean, + }) + ), +}); + +// Agent role: coordinate branch coverage analysis for a TypeScript file. +const tsBranchCoverageAnalyzer = agent({ + model: "small", + input: s.object({ filePath: s.string }), + instructions: p`Analyze TypeScript branch coverage for the file at the path provided in input. + +Read the file content: +${p.readInput("filePath")} + +Delegate analysis to the branchAnalyzer subagent passing fileContent and filePath. +Aggregate the results: count totalBranches, coveredBranches, uncoveredBranches. +Compute coveragePercent as (coveredBranches / totalBranches * 100), 0 if no branches.`, + agents: { branchAnalyzer }, + output: s.object({ + branches: s.array( + s.object({ + functionName: s.string, + line: s.int, + branchType: s.enum("if", "ternary", "switch", "logical", "nullish"), + covered: s.boolean, + }) + ), + summary: s.object({ + totalBranches: s.int, + coveredBranches: s.int, + uncoveredBranches: s.int, + coveragePercent: s.number, + }), + }), +}); + +export default tsBranchCoverageAnalyzer; +``` diff --git a/skills/rig/samples/275-env-variable-type-inferrer.md b/skills/rig/samples/275-env-variable-type-inferrer.md new file mode 100644 index 0000000..a8afacc --- /dev/null +++ b/skills/rig/samples/275-env-variable-type-inferrer.md @@ -0,0 +1,45 @@ +# 275 - Env Variable Type Inferrer + +```rig +import { agent, p, s, defineTool } from "rig"; + +const inferType = defineTool("inferType", { + description: "Infer the type of an environment variable value using regex patterns.", + parameters: s.object({ key: s.string, value: s.string }), + handler({ value }) { + if (/^\d+(\.\d+)?$/.test(value)) return "number" as const; + if (/^(true|false)$/i.test(value)) return "boolean" as const; + if (/^https?:\/\//.test(value)) return "url" as const; + if (/^[/~.]/.test(value)) return "path" as const; + if (value.length === 0) return "unknown" as const; + return "string" as const; + }, +}); + +// Agent role: read .env.example and infer the type of each variable. +const envVariableTypeInferrer = agent({ + model: "small", + instructions: p`Infer types for environment variables from .env.example. + +File contents: +${p.readOptional(".env.example", "# no .env.example found")} + +For each non-comment, non-empty line, parse KEY=VALUE. +Call inferType with the key and value to determine the type. +Write a one-line description for each variable based on its key name and inferred type. +Build the variables record keyed by variable name. +Set allDocumented to true if all variables have non-empty descriptions.`, + tools: [inferType], + output: s.object({ + variables: s.record( + s.object({ + type: s.enum("string", "number", "boolean", "url", "path", "unknown"), + description: s.string, + }) + ), + allDocumented: s.boolean, + }), +}); + +export default envVariableTypeInferrer; +``` diff --git a/skills/rig/samples/276-git-hook-installer.md b/skills/rig/samples/276-git-hook-installer.md new file mode 100644 index 0000000..32191a7 --- /dev/null +++ b/skills/rig/samples/276-git-hook-installer.md @@ -0,0 +1,54 @@ +# 276 - Git Hook Installer + +```rig +import { agent, p, s, defineTool, repair } from "rig"; + +const checkHooksDir = defineTool("checkHooksDir", { + description: "Check if the .git/hooks directory exists.", + parameters: s.object({}), + async handler() { + const { access } = await import("node:fs/promises"); + try { + await access(".git/hooks"); + return { exists: true }; + } catch { + return { exists: false }; + } + }, +}); + +// Agent role: install git hook scripts into .git/hooks for the current repository. +const gitHookInstaller = agent({ + model: "small", + addons: repair(), + input: s.object({ + hooks: s.array( + s.object({ + name: s.string, + script: s.string, + description: s.string, + }) + ), + }), + instructions: p`Install git hook scripts into the .git/hooks directory. + +Existing hooks directory: +${p.bash("ls .git/hooks 2>/dev/null || echo 'no .git/hooks directory'")} + +First call checkHooksDir to verify the directory exists. +For each hook in input.hooks: +- If .git/hooks exists, write the script to .git/hooks/ using a write operation, then run chmod +x via bash. +- If .git/hooks does not exist, add the hook name to skippedHooks. +Track writtenHooks as array of { name, path } for each successfully written hook. +Set totalWritten and allWritten accordingly.`, + tools: [checkHooksDir], + output: s.object({ + writtenHooks: s.array(s.object({ name: s.string, path: s.string })), + skippedHooks: s.array(s.string), + totalWritten: s.int, + allWritten: s.boolean, + }), +}); + +export default gitHookInstaller; +``` diff --git a/skills/rig/samples/277-ts-type-guard-generator.md b/skills/rig/samples/277-ts-type-guard-generator.md new file mode 100644 index 0000000..177c547 --- /dev/null +++ b/skills/rig/samples/277-ts-type-guard-generator.md @@ -0,0 +1,46 @@ +# 277 - Ts Type Guard Generator + +```rig +import { agent, p, s, defineTool, repair } from "rig"; + +const extractInterfaces = defineTool("extractInterfaces", { + description: "Extract interface names from a TypeScript source file.", + parameters: s.object({ filePath: s.path }), + async handler({ filePath }) { + const { readFile } = await import("node:fs/promises"); + try { + const content = await readFile(filePath, "utf8"); + const matches = [...content.matchAll(/interface\s+(\w+)/g)]; + return { interfaceNames: matches.map((m: RegExpMatchArray) => m[1] as string) }; + } catch { + return { interfaceNames: [] }; + } + }, +}); + +// Agent role: generate TypeScript type guard functions for each interface in a source file. +const tsTypeGuardGenerator = agent({ + model: "small", + addons: repair(), + input: s.object({ sourceFile: s.path, outputFile: s.path }), + instructions: p`Generate TypeScript type guard functions for interfaces found in a source file. + +Source file content: +${p.readInput("sourceFile")} + +1. Call extractInterfaces with the sourceFile path to get all interface names. +2. For each interface, generate a type guard function: \`function is(val: unknown): val is \`. +3. Write the generated guard functions to the outputFile path. +4. Return generatedGuards listing interfaceName and guardFunctionName for each guard.`, + tools: [extractInterfaces], + output: s.object({ + generatedGuards: s.array( + s.object({ interfaceName: s.string, guardFunctionName: s.string }) + ), + outputFile: s.path, + totalGenerated: s.int, + }), +}); + +export default tsTypeGuardGenerator; +``` diff --git a/skills/rig/samples/278-json-fixture-anonymizer.md b/skills/rig/samples/278-json-fixture-anonymizer.md new file mode 100644 index 0000000..9b62909 --- /dev/null +++ b/skills/rig/samples/278-json-fixture-anonymizer.md @@ -0,0 +1,50 @@ +# 278 - Json Fixture Anonymizer + +```rig +import { agent, p, s, defineTool } from "rig"; + +const anonymizeValue = defineTool("anonymizeValue", { + description: "Return an anonymized replacement for a field value based on its name and type.", + parameters: s.object({ fieldName: s.string, value: s.unknown, fieldType: s.string }), + handler({ fieldName, fieldType }) { + const name = fieldName.toLowerCase(); + if (name.includes("email")) return "[email@example.com]"; + if (name.includes("phone") || name.includes("tel")) return "[000-000-0000]"; + if (name.includes("name") || name.includes("user")) return "[REDACTED_NAME]"; + if (name.includes("address") || name.includes("street")) return "[REDACTED_ADDRESS]"; + if (name.includes("ip")) return "0.0.0.0"; + if (fieldType === "number") return 0; + if (fieldType === "boolean") return false; + return "[REDACTED]"; + }, +}); + +// Agent role: anonymize sensitive fields in a JSON fixture file. +const jsonFixtureAnonymizer = agent({ + model: "small", + input: s.object({ + inputFile: s.path, + outputFile: s.path, + fieldsToAnonymize: s.array(s.string), + }), + instructions: p`Anonymize sensitive fields in a JSON fixture file. + +Input file content: +${p.readInput("inputFile")} + +For each field name in input.fieldsToAnonymize, call anonymizeValue with the field name, +current value, and value type to get the replacement. +Replace all matching field values throughout the JSON structure. +Write the anonymized JSON to input.outputFile. +Count fieldsAnonymized (total replacements made) and totalRecords (array length or 1 for object).`, + tools: [anonymizeValue], + output: s.object({ + fieldsAnonymized: s.int, + totalRecords: s.int, + outputPath: s.path, + anonymizedFields: s.array(s.string), + }), +}); + +export default jsonFixtureAnonymizer; +``` diff --git a/skills/rig/samples/279-csv-to-markdown-table.md b/skills/rig/samples/279-csv-to-markdown-table.md new file mode 100644 index 0000000..c919138 --- /dev/null +++ b/skills/rig/samples/279-csv-to-markdown-table.md @@ -0,0 +1,44 @@ +# 279 - Csv To Markdown Table + +```rig +import { agent, p, s, defineTool, repair } from "rig"; + +const parseCSVRow = defineTool("parseCSVRow", { + description: "Split a CSV line into trimmed cell values.", + parameters: s.object({ line: s.string, delimiter: s.string }), + handler({ line, delimiter }) { + return { cells: line.split(delimiter).map((cell: string) => cell.trim()) }; + }, +}); + +// Agent role: convert a CSV file to a Markdown table and write it to an output file. +const csvToMarkdownTable = agent({ + model: "small", + addons: repair(), + input: s.object({ + csvFile: s.path, + outputFile: s.path, + includeStats: s.boolean, + }), + instructions: p`Convert a CSV file to a Markdown table. + +CSV file content: +${p.readInput("csvFile")} + +1. Call parseCSVRow for the first line with delimiter "," to get the headers. +2. Call parseCSVRow for each subsequent non-empty line to get row cells. +3. Build a Markdown table: header row, separator row (---), then data rows. +4. If input.includeStats is true, append a stats section with row/column counts. +5. Write the complete Markdown output to input.outputFile. +6. Return rowCount (data rows), columnCount, outputFile path, and headers array.`, + tools: [parseCSVRow], + output: s.object({ + rowCount: s.int, + columnCount: s.int, + outputFile: s.path, + headers: s.array(s.string), + }), +}); + +export default csvToMarkdownTable; +```