diff --git a/skills/rig/samples/150-git-hotspot-analyzer-v3.md b/skills/rig/samples/150-git-hotspot-analyzer-v3.md new file mode 100644 index 0000000..903d73b --- /dev/null +++ b/skills/rig/samples/150-git-hotspot-analyzer-v3.md @@ -0,0 +1,43 @@ +# 150 - Git Hotspot Analyzer V3 + +```rig +import { agent, defineTool, p, s } from "rig"; +import { steering } from "rig/addons"; + +// Agent role: identify hot-spot files by git churn and classify risk level. +const gitHotspotAnalyzerV3 = agent({ + model: "small", + addons: steering({ message: "Focus on files that appear most frequently. Assign riskLevel based on churnScore: >20=critical, >10=high, >5=medium, else low." }), + instructions: p`Analyze git history to find frequently changed files and classify their risk. + +Recently changed files (with churn counts): +${p.bash("git log --name-only --format='' HEAD~50..HEAD | grep -v '^$' | sort | uniq -c | sort -rn | head -30")} + +Top contributors overall: +${p.bash("git shortlog -sn HEAD~50..HEAD 2>/dev/null | head -10")} + +Use the countChurn tool to parse the churn output for each file. For each file, determine +the churnScore, assign topContributors from the shortlog output, and classify riskLevel. +Return a record keyed by file path with the declared output shape.`, + tools: [ + defineTool("countChurn", { + description: "Parse a line of uniq -c output to extract count and filename", + parameters: s.object({ line: s.string }), + handler({ line }) { + const m = line.trim().match(/^(\d+)\s+(.+)$/); + if (!m) return { count: 0, file: "" }; + return { count: parseInt(m[1], 10), file: m[2].trim() }; + }, + }), + ], + output: s.record( + s.object({ + churnScore: s.int, + topContributors: s.array(s.string), + riskLevel: s.enum("low", "medium", "high", "critical"), + }) + ), +}); + +export default gitHotspotAnalyzerV3; +``` diff --git a/skills/rig/samples/151-loc-statistics-v2.md b/skills/rig/samples/151-loc-statistics-v2.md new file mode 100644 index 0000000..79d8334 --- /dev/null +++ b/skills/rig/samples/151-loc-statistics-v2.md @@ -0,0 +1,56 @@ +# 151 - Loc Statistics V2 + +```rig +import { agent, defineTool, p, s } from "rig"; + +// Agent role: gather lines-of-code statistics per file extension. +const locStatisticsV2 = agent({ + model: "small", + instructions: p`Count lines of code per file extension in this workspace. + +TypeScript files found: +${p.bash("find . -name '*.ts' -not -path '*/node_modules/*' | head -50")} + +Line counts for TypeScript files: +${p.bash("find . -name '*.ts' -not -path '*/node_modules/*' | head -20 | xargs wc -l 2>/dev/null | tail -1")} + +JavaScript files found: +${p.bash("find . -name '*.js' -not -path '*/node_modules/*' | head -20")} + +Use the aggregateExtension tool to compute totals. Then classify complexity for each +extension: xlarge > 10000 lines, large > 5000, medium > 1000, small otherwise. +Return a record keyed by extension (e.g. ".ts", ".js") with lineCount, fileCount, +and complexity.`, + tools: [ + defineTool("aggregateExtension", { + description: "Count lines in files matching a given extension", + parameters: s.object({ extension: s.string, sampleFile: s.string }), + async handler({ extension, sampleFile }) { + const { execSync } = await import("node:child_process"); + try { + const count = execSync( + `find . -name '*${extension}' -not -path '*/node_modules/*' | xargs wc -l 2>/dev/null | tail -1 | awk '{print $1}'`, + { encoding: "utf-8" } + ).trim(); + const files = execSync( + `find . -name '*${extension}' -not -path '*/node_modules/*' | wc -l`, + { encoding: "utf-8" } + ).trim(); + return { extension, lineCount: parseInt(count) || 0, fileCount: parseInt(files) || 0, sampleFile }; + } catch { + return { extension, lineCount: 0, fileCount: 0, sampleFile }; + } + }, + }), + ], + output: s.record( + s.object({ + lineCount: s.int, + fileCount: s.int, + complexity: s.enum("small", "medium", "large", "xlarge"), + }) + ), +}); + +export default locStatisticsV2; +``` diff --git a/skills/rig/samples/152-import-cycle-detector-v3.md b/skills/rig/samples/152-import-cycle-detector-v3.md new file mode 100644 index 0000000..17145ea --- /dev/null +++ b/skills/rig/samples/152-import-cycle-detector-v3.md @@ -0,0 +1,39 @@ +# 152 - Import Cycle Detector V3 + +```rig +import { agent, p, s } from "rig"; +import { repair } from "rig/addons"; + +// Agent role: detect circular import cycles in TypeScript source and classify severity. +const importCycleDetectorV3 = agent({ + model: "small", + maxTurns: 3, + addons: repair(), + instructions: p`Detect circular import cycles in this TypeScript project. + +Circular dependency analysis: +${p.bash("npx madge --circular --extensions ts src 2>/dev/null || echo 'No cycles found or madge not available'")} + +TypeScript configuration: +${p.readOptional("tsconfig.json", "{}")} + +Analyze the output above. Each cycle is a group of files that import each other in a +circle. Classify severity: high if the cycle involves more than 3 files or core modules, +medium for 2-3 files, low for simple two-file cycles. + +Return hasCycles (true if any cycles found), the cycles array with path (array of file +strings in the cycle) and severity, and totalCycles count.`, + output: s.object({ + hasCycles: s.boolean, + cycles: s.array( + s.object({ + path: s.array(s.string), + severity: s.enum("high", "medium", "low"), + }) + ), + totalCycles: s.int, + }), +}); + +export default importCycleDetectorV3; +``` diff --git a/skills/rig/samples/153-coverage-badge-updater-v2.md b/skills/rig/samples/153-coverage-badge-updater-v2.md new file mode 100644 index 0000000..6060b38 --- /dev/null +++ b/skills/rig/samples/153-coverage-badge-updater-v2.md @@ -0,0 +1,39 @@ +# 153 - Coverage Badge Updater V2 + +```rig +import { agent, p, s } from "rig"; + +// Agent role: read coverage summary and update README with shields.io badge links. +const coverageBadgeUpdaterV2 = agent({ + model: "small", + instructions: p`Read coverage data and update the README with shields.io coverage badges. + +Coverage summary (JSON): +${p.readOptional("coverage/coverage-summary.json", "{}")} + +Current README: +${p.readOptional("README.md", "# Project\n")} + +Compute the coverage percentage for each category (statements, branches, functions, lines) +from the total section. Compute overallPct as the average. Classify rating: green >= 80%, +yellow >= 60%, red < 60%. + +Generate shields.io badge markdown for each category using URL format: +https://img.shields.io/badge/coverage-XX%25-green + +Write the updated README with badges added at the top using p.write. + +${p.write("README.md", "")} + +Return coverageByCategory (record of category to percentage), overallPct, rating, and +badgesWritten (true if README was updated).`, + output: s.object({ + coverageByCategory: s.record(s.number), + overallPct: s.number, + rating: s.enum("green", "yellow", "red"), + badgesWritten: s.boolean, + }), +}); + +export default coverageBadgeUpdaterV2; +``` diff --git a/skills/rig/samples/154-dep-license-auditor-v2.md b/skills/rig/samples/154-dep-license-auditor-v2.md new file mode 100644 index 0000000..c88a753 --- /dev/null +++ b/skills/rig/samples/154-dep-license-auditor-v2.md @@ -0,0 +1,41 @@ +# 154 - Dep License Auditor V2 + +```rig +import { agent, p, s } from "rig"; + +// Agent role: audit npm dependency licenses and flag copyleft packages. +const depLicenseAuditorV2 = agent({ + model: "small", + instructions: p`Audit the licenses of npm dependencies and classify each one. + +Installed dependencies (JSON tree): +${p.bash("npm ls --json --depth=0 2>/dev/null || echo '{}'")} + +Package dependency names: +${p.bash("node -e \"try{const p=require('./package.json');console.log(JSON.stringify(Object.keys(p.dependencies||{})))}catch(e){console.log('[]')}\"")} + +Package license fields from node_modules: +${p.bash("node -e \"const fs=require('fs');const d='./node_modules';if(fs.existsSync(d)){const pkgs=fs.readdirSync(d).filter(x=>!x.startsWith('.'));pkgs.slice(0,30).forEach(p=>{try{const m=JSON.parse(fs.readFileSync(d+'/'+p+'/package.json','utf8'));console.log(p+':'+m.license)}catch(e){}});}\" 2>/dev/null || echo 'unavailable'")} + +For each dependency, determine its license. Classify as: +- permissive: MIT, ISC, BSD, Apache, CC0, Unlicense, 0BSD +- copyleft: GPL, LGPL, AGPL, MPL, EUPL, CC-BY-SA +- unknown: anything else or unspecified + +Return the packages array with name, license, and classification, hasCopyleft (true if any +copyleft found), and totalCount.`, + output: s.object({ + packages: s.array( + s.object({ + name: s.string, + license: s.string, + classification: s.enum("permissive", "copyleft", "unknown"), + }) + ), + hasCopyleft: s.boolean, + totalCount: s.int, + }), +}); + +export default depLicenseAuditorV2; +``` diff --git a/skills/rig/samples/155-test-coverage-mapper-v2.md b/skills/rig/samples/155-test-coverage-mapper-v2.md new file mode 100644 index 0000000..bccf674 --- /dev/null +++ b/skills/rig/samples/155-test-coverage-mapper-v2.md @@ -0,0 +1,56 @@ +# 155 - Test Coverage Mapper V2 + +```rig +import { agent, defineTool, p, s } from "rig"; +import { repair } from "rig/addons"; + +// Agent role: map source files to their test files using filename heuristics. +const testCoverageMapperV2 = agent({ + model: "small", + maxTurns: 2, + addons: repair(), + instructions: p`Map TypeScript source files to their corresponding test files. + +Source files (non-test TypeScript): +${p.bash("find . -name '*.ts' -not -name '*.test.ts' -not -name '*.spec.ts' -not -path '*/node_modules/*' -not -path '*/.git/*' | head -30")} + +Test files: +${p.bash("find . -name '*.test.ts' -o -name '*.spec.ts' | grep -v node_modules | head -30")} + +Use the matchTestFile tool for each source file to find its corresponding test file using +filename heuristics (e.g., src/foo.ts -> src/foo.test.ts or tests/foo.test.ts). + +Return a record keyed by source file path with coverage ("covered" if test found, +"uncovered" if none, "partial" if indirect match), testFiles (array of matched test paths), +and reason explaining the determination.`, + tools: [ + defineTool("matchTestFile", { + description: "Find test files that match a source file using naming heuristics", + parameters: s.object({ sourceFile: s.string, testFiles: s.array(s.string) }), + handler({ sourceFile, testFiles }) { + const base = sourceFile.replace(/\.tsx?$/, "").replace(/^.*\//, ""); + const direct = testFiles.filter((t) => + t.includes(`${base}.test`) || t.includes(`${base}.spec`) + ); + const partial = direct.length === 0 + ? testFiles.filter((t) => t.toLowerCase().includes(base.toLowerCase())) + : []; + return { + matched: direct, + partial, + coverage: direct.length > 0 ? "covered" : partial.length > 0 ? "partial" : "uncovered", + }; + }, + }), + ], + output: s.record( + s.object({ + coverage: s.enum("covered", "uncovered", "partial"), + testFiles: s.array(s.string), + reason: s.string, + }) + ), +}); + +export default testCoverageMapperV2; +``` diff --git a/skills/rig/samples/156-regex-pattern-tester.md b/skills/rig/samples/156-regex-pattern-tester.md new file mode 100644 index 0000000..205ab8d --- /dev/null +++ b/skills/rig/samples/156-regex-pattern-tester.md @@ -0,0 +1,64 @@ +# 156 - Regex Pattern Tester + +```rig +import { agent, defineTool, p, s } from "rig"; +import { repair } from "rig/addons"; + +// 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, + expected: 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, input) pair. Then build the results array +with patternName, input, expected, actual (from tool), and passed (expected === actual). +Count passCount and failCount. Set allPassed to true only if failCount is 0.`, + tools: [ + defineTool("runRegexTest", { + description: "Test a regex pattern against an input string", + parameters: s.object({ pattern: s.string, input: s.string }), + handler({ pattern, input }) { + try { + const matched = new RegExp(pattern).test(input); + return { matched }; + } catch { + return { matched: false }; + } + }, + }), + ], + output: s.object({ + results: s.array( + s.object({ + patternName: s.string, + input: s.string, + expected: s.boolean, + actual: s.boolean, + passed: s.boolean, + }) + ), + passCount: s.int, + failCount: s.int, + allPassed: s.boolean, + }), +}); + +export default regexPatternTester; +``` diff --git a/skills/rig/samples/157-commit-churn-classifier.md b/skills/rig/samples/157-commit-churn-classifier.md new file mode 100644 index 0000000..5fea348 --- /dev/null +++ b/skills/rig/samples/157-commit-churn-classifier.md @@ -0,0 +1,32 @@ +# 157 - Commit Churn Classifier + +```rig +import { agent, p, s } from "rig"; +import { steering } from "rig/addons"; + +// Agent role: classify files by commit churn frequency and assign a risk level. +const commitChurnClassifier = agent({ + model: "small", + addons: steering({ message: "Be precise: assign riskLevel based on churnCount: >20=critical, >10=volatile, >5=active, else stable." }), + instructions: p`Classify repository files by how frequently they are committed (churn). + +File churn counts from last 100 commits (count file): +${p.bash("git log --name-only --format='' HEAD~100..HEAD 2>/dev/null | grep -v '^$' | sort | uniq -c | sort -rn | head -30")} + +For each file in the output above, 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/158-npm-package-size.md b/skills/rig/samples/158-npm-package-size.md new file mode 100644 index 0000000..7ecdd3b --- /dev/null +++ b/skills/rig/samples/158-npm-package-size.md @@ -0,0 +1,48 @@ +# 158 - Npm Package Size + +```rig +import { agent, defineTool, p, s } 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 provide a recommendation. + +Files that would be included in npm publish (dry run): +${p.bash("npm pack --dry-run 2>&1 | head -40")} + +Overall workspace size: +${p.bash("du -sh . 2>/dev/null | cut -f1")} + +Package metadata: +${p.readOptional("package.json", "{}")} + +Parse the npm pack output to extract file sizes. Use the classifySize tool to rate the +total estimated size. Provide a recommendation based on the rating (e.g., suggest adding +files to .npmignore if large/xlarge). + +Return estimatedSizeKb (total in KB), topFiles (up to 5 largest files with file and +sizeKb), sizeRating, and a recommendation string.`, + 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 < 50) return { rating: "small" }; + if (sizeKb < 200) return { rating: "medium" }; + if (sizeKb < 1000) return { rating: "large" }; + return { rating: "xlarge" }; + }, + }), + ], + output: s.object({ + estimatedSizeKb: s.number, + topFiles: s.array(s.object({ file: s.string, sizeKb: s.number })), + sizeRating: s.enum("tiny", "small", "medium", "large", "xlarge"), + recommendation: s.string, + }), +}); + +export default npmPackageSize; +``` diff --git a/skills/rig/samples/159-ts-branch-coverage.md b/skills/rig/samples/159-ts-branch-coverage.md new file mode 100644 index 0000000..8e2bc0b --- /dev/null +++ b/skills/rig/samples/159-ts-branch-coverage.md @@ -0,0 +1,62 @@ +# 159 - Ts Branch Coverage + +```rig +import { agent, p, s } from "rig"; + +// Agent role: identify branch statements in a TypeScript file and estimate coverage. +const branchAnalyzer = agent({ + name: "branchAnalyzer", + model: "nano", + input: s.string, + instructions: p`Analyze the TypeScript source code provided and identify all branch points. + +For each branch (if statements, ternaries, switch cases, nullish coalescing), determine: +- functionName: the enclosing function name (or "module" if top-level) +- line: approximate line number +- branchType: "if", "ternary", "switch", or "nullish" +- covered: false (assume uncovered unless the code contains obvious test guards) + +Return only the declared output array.`, + output: s.array( + s.object({ + functionName: s.string, + line: s.int, + branchType: s.enum("if", "ternary", "switch", "nullish"), + covered: s.boolean, + }) + ), +}); + +// Agent role: coordinate branch coverage analysis by delegating to branchAnalyzer subagent. +const tsBranchCoverage = agent({ + model: "small", + input: s.object({ filePath: s.path }), + instructions: p`Analyze TypeScript branch coverage for the file at the provided path. + +File content: +${p.readInput("filePath")} + +Delegate the analysis to the branchAnalyzer subagent. Then aggregate results into: +- branches: the full array returned by branchAnalyzer +- summary: totalBranches (length of array), coveredBranches (count where covered=true), + uncoveredBranches (count where covered=false)`, + agents: { branchAnalyzer }, + output: s.object({ + branches: s.array( + s.object({ + functionName: s.string, + line: s.int, + branchType: s.enum("if", "ternary", "switch", "nullish"), + covered: s.boolean, + }) + ), + summary: s.object({ + totalBranches: s.int, + coveredBranches: s.int, + uncoveredBranches: s.int, + }), + }), +}); + +export default tsBranchCoverage; +```