diff --git a/skills/rig/samples/491-dotenv-process-drift-detector.md b/skills/rig/samples/491-dotenv-process-drift-detector.md new file mode 100644 index 0000000..1c62197 --- /dev/null +++ b/skills/rig/samples/491-dotenv-process-drift-detector.md @@ -0,0 +1,31 @@ +# 491 - Dotenv Process Drift Detector + +```rig +import { agent, defineTool, p, s, repair } from "rig"; + +const classifyEnvKey = defineTool("classifyEnvKey", { + description: "Classify an env key based on whether it's in .env.example and used in code", + parameters: s.object({ key: s.string, inExample: s.boolean, usedInCode: s.boolean }), + handler({ inExample, usedInCode }): "declared_and_used" | "declared_not_used" | "used_not_declared" { + if (inExample && usedInCode) return "declared_and_used" as const; + if (inExample && !usedInCode) return "declared_not_used" as const; + return "used_not_declared" as const; + }, +}); + +// Agent role: detect drift between .env.example declarations and actual process.env usages in source code. +const dotenvDriftDetector = agent({ + model: "small", + instructions: p`Compare declared env keys in ${p.read(".env.example")} with actual usages from ${p.bash("grep -rn 'process\\.env\\.' src/ 2>/dev/null || true")}. Use classifyEnvKey for each key found. Return the full drift report.`, + output: s.object({ + keys: s.record(s.object({ inExample: s.boolean, usedInCode: s.boolean, status: s.string })), + totalKeys: s.int, + missingFromExample: s.array(s.string), + unusedDeclarations: s.array(s.string), + }), + tools: [classifyEnvKey], + addons: [repair()], +}); + +export default dotenvDriftDetector; +``` diff --git a/skills/rig/samples/492-vitest-snapshot-count-workflow.md b/skills/rig/samples/492-vitest-snapshot-count-workflow.md new file mode 100644 index 0000000..dc871ab --- /dev/null +++ b/skills/rig/samples/492-vitest-snapshot-count-workflow.md @@ -0,0 +1,43 @@ +# 492 - Vitest Snapshot Count Workflow + +```rig +import { agent, workflow, p, s } from "rig"; + +// Agent role: scan for Vitest snapshot files and count lines. +const snapshotScanner = agent({ + model: "small", + instructions: p`Find snapshot files with ${p.bash("find . -path '*/__snapshots__/*.snap' -not -path '*/node_modules/*' 2>/dev/null | head -20 || true")} and count lines in each with ${p.bash("find . -path '*/__snapshots__/*.snap' -not -path '*/node_modules/*' 2>/dev/null | xargs wc -l 2>/dev/null || echo '0 total'")}. Return the file list and total count.`, + output: s.object({ + files: s.array(s.object({ path: s.path, lineCount: s.int })), + totalFiles: s.int, + }), +}); + +// Agent role: count snapshot export entries across snapshot files. +const snapshotParser = agent({ + model: "small", + instructions: p`Find snapshot files containing exports using ${p.bash("grep -rl 'exports\\[' . --include='*.snap' 2>/dev/null | head -20 || true")}. Count total export entries with ${p.bash("grep -r 'exports\\[' . --include='*.snap' 2>/dev/null | wc -l || echo 0")}. Return file list and count.`, + output: s.object({ + snapshotFiles: s.array(s.path), + exportCount: s.int, + }), +}); + +// Workflow role: run snapshot scanner and parser in parallel and combine results. +const vitestSnapshotWorkflow = workflow({ + meta: { name: "vitest-snapshot-count-workflow", description: "Count Vitest snapshots in parallel" }, + body: async ({ call }) => { + const [scanResult, parseResult] = await Promise.all([ + call(snapshotScanner, ""), + call(snapshotParser, ""), + ]); + return { + totalSnapshotFiles: scanResult?.totalFiles ?? 0, + totalLines: (scanResult?.files ?? []).reduce((sum: number, f: { lineCount: number }) => sum + f.lineCount, 0), + snapshotFiles: parseResult?.snapshotFiles ?? [], + }; + }, +}); + +export default vitestSnapshotWorkflow; +``` diff --git a/skills/rig/samples/493-git-stale-branch-reporter.md b/skills/rig/samples/493-git-stale-branch-reporter.md new file mode 100644 index 0000000..6e99ef9 --- /dev/null +++ b/skills/rig/samples/493-git-stale-branch-reporter.md @@ -0,0 +1,32 @@ +# 493 - Git Stale Branch Reporter + +```rig +import { agent, defineTool, p, s } from "rig"; + +const classifyBranchAge = defineTool("classifyBranchAge", { + description: "Classify a branch by age based on its last activity description", + parameters: s.object({ branch: s.string, lastActivity: s.string }), + handler({ lastActivity }): "fresh" | "aging" | "stale" | "ancient" { + const l = lastActivity.toLowerCase(); + if (l.includes("second") || l.includes("minute") || l.includes("hour")) return "fresh" as const; + if (l.includes("day") && !l.includes("week")) return "aging" as const; + if (l.includes("week") || l.includes("month")) return "stale" as const; + return "ancient" as const; + }, +}); + +// Agent role: report on git remote branches sorted by last commit date and classify their age. +const gitStaleBranchReporter = agent({ + model: "small", + instructions: p`Analyze git branches from ${p.bash("git branch -r --sort=-committerdate --format='%(refname:short) %(committerdate:relative)' 2>/dev/null | head -30 || true")}. Use classifyBranchAge for each branch to determine its freshness status. Return the full report.`, + output: s.object({ + branches: s.array(s.object({ name: s.string, lastActivity: s.string, status: s.string })), + staleBranches: s.array(s.string), + freshCount: s.int, + staleCount: s.int, + }), + tools: [classifyBranchAge], +}); + +export default gitStaleBranchReporter; +``` diff --git a/skills/rig/samples/494-ts-abstract-class-finder.md b/skills/rig/samples/494-ts-abstract-class-finder.md new file mode 100644 index 0000000..9288b15 --- /dev/null +++ b/skills/rig/samples/494-ts-abstract-class-finder.md @@ -0,0 +1,29 @@ +# 494 - Ts Abstract Class Finder + +```rig +import { agent, defineTool, p, s } from "rig"; + +const extractAbstractClass = defineTool("extractAbstractClass", { + description: "Extract abstract class name and export status from a declaration line", + parameters: s.object({ filePath: s.path, line: s.int, declaration: s.string }), + handler({ declaration }) { + const exported = declaration.trim().startsWith("export"); + const match = declaration.match(/abstract\s+class\s+(\w+)/); + return { className: match?.[1] ?? "Unknown", isExported: exported }; + }, +}); + +// Agent role: find all TypeScript abstract classes in the source tree and report their export status. +const tsAbstractClassFinder = agent({ + model: "small", + instructions: p`Search for TypeScript abstract classes using ${p.bash("grep -rn 'abstract class' src/ 2>/dev/null || true")}. Also list source files with ${p.glob("src/**/*.ts")}. Use extractAbstractClass for each match to get the class name and export status. Return the full list.`, + output: s.object({ + abstractClasses: s.array(s.object({ className: s.string, filePath: s.path, line: s.int, isExported: s.boolean })), + totalCount: s.int, + exportedCount: s.int, + }), + tools: [extractAbstractClass], +}); + +export default tsAbstractClassFinder; +``` diff --git a/skills/rig/samples/495-dockerfile-env-inspector.md b/skills/rig/samples/495-dockerfile-env-inspector.md new file mode 100644 index 0000000..d7dfc4c --- /dev/null +++ b/skills/rig/samples/495-dockerfile-env-inspector.md @@ -0,0 +1,30 @@ +# 495 - Dockerfile Env Inspector + +```rig +import { agent, defineTool, p, s } from "rig"; + +const parseEnvInstruction = defineTool("parseEnvInstruction", { + description: "Parse a Dockerfile ENV instruction line and extract key, value, and whether a default is set", + parameters: s.object({ file: s.path, line: s.int, raw: s.string }), + handler({ raw }) { + const parts = raw.replace(/^ENV\s+/, "").split(/\s+|=/, 2); + const key = parts[0] ?? ""; + const value = parts[1] ?? ""; + return { key, value, hasDefault: value.length > 0 }; + }, +}); + +// Agent role: locate Dockerfiles and extract all ENV instructions with their key-value details. +const dockerfileEnvInspector = agent({ + model: "small", + instructions: p`Find Dockerfiles using ${p.bash("find . -name 'Dockerfile*' -not -path '*/node_modules/*' 2>/dev/null | head -20 || true")}. Extract ENV instructions with ${p.bash("grep -rn '^ENV ' $(find . -name 'Dockerfile*' -not -path '*/node_modules/*' 2>/dev/null | head -10 | tr '\\n' ' ') 2>/dev/null || true")}. Use parseEnvInstruction for each ENV line found. Return all env vars.`, + output: s.object({ + envVars: s.array(s.object({ key: s.string, value: s.string, hasDefault: s.boolean, file: s.path, line: s.int })), + totalCount: s.int, + dockerfileCount: s.int, + }), + tools: [parseEnvInstruction], +}); + +export default dockerfileEnvInspector; +``` diff --git a/skills/rig/samples/496-npm-script-prefix-analyzer.md b/skills/rig/samples/496-npm-script-prefix-analyzer.md new file mode 100644 index 0000000..7feffe9 --- /dev/null +++ b/skills/rig/samples/496-npm-script-prefix-analyzer.md @@ -0,0 +1,33 @@ +# 496 - Npm Script Prefix Analyzer + +```rig +import { agent, defineTool, p, s } from "rig"; + +const classifyScriptPrefix = defineTool("classifyScriptPrefix", { + description: "Classify an npm script by its command prefix", + parameters: s.object({ scriptName: s.string, command: s.string }), + handler({ command }): "node" | "npm" | "npx" | "ts-node" | "sh" | "other" { + const c = command.trim().split(" ")[0] ?? ""; + if (c === "node") return "node" as const; + if (c === "npm") return "npm" as const; + if (c === "npx") return "npx" as const; + if (c === "ts-node" || c === "tsx") return "ts-node" as const; + if (c === "sh" || c === "bash") return "sh" as const; + return "other" as const; + }, +}); + +// Agent role: read package.json scripts and classify each by command prefix, returning counts per prefix. +const npmScriptPrefixAnalyzer = agent({ + model: "small", + instructions: p`Read scripts from ${p.read("package.json")}. Use classifyScriptPrefix for each script entry to determine its command prefix. Return the per-script details and prefix counts.`, + output: s.object({ + scripts: s.record(s.object({ command: s.string, prefix: s.string })), + prefixCounts: s.record(s.int), + totalScripts: s.int, + }), + tools: [classifyScriptPrefix], +}); + +export default npmScriptPrefixAnalyzer; +``` diff --git a/skills/rig/samples/497-ts-default-param-extractor.md b/skills/rig/samples/497-ts-default-param-extractor.md new file mode 100644 index 0000000..86da310 --- /dev/null +++ b/skills/rig/samples/497-ts-default-param-extractor.md @@ -0,0 +1,34 @@ +# 497 - Ts Default Param Extractor + +```rig +import { agent, defineTool, p, s, repair } from "rig"; + +const extractDefaultParam = defineTool("extractDefaultParam", { + description: "Extract function name, parameter name, and default value from a TypeScript line", + parameters: s.object({ file: s.path, line: s.int, raw: s.string }), + handler({ raw }) { + const fnMatch = raw.match(/(?:function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=)/); + const paramMatch = raw.match(/(\w+)\s*=\s*([^,)]+)/); + return { + functionName: fnMatch?.[1] ?? fnMatch?.[2] ?? "anonymous", + paramName: paramMatch?.[1] ?? "unknown", + defaultValue: paramMatch?.[2]?.trim() ?? "unknown", + }; + }, +}); + +// Agent role: find TypeScript functions with default parameter values and report them. +const tsDefaultParamExtractor = agent({ + model: "small", + instructions: p`Find TypeScript files with ${p.glob("src/**/*.ts")}. Search for function parameters with defaults using ${p.bash("grep -rn 'function\\|const.*=>' src/ 2>/dev/null | grep '=' | grep -v '=>' | head -50 || true")}. Use extractDefaultParam for each match. Return the full list of default parameters.`, + output: s.object({ + defaults: s.array(s.object({ functionName: s.string, paramName: s.string, defaultValue: s.string, file: s.path, line: s.int })), + totalDefaults: s.int, + filesWithDefaults: s.array(s.path), + }), + tools: [extractDefaultParam], + addons: [repair()], +}); + +export default tsDefaultParamExtractor; +``` diff --git a/skills/rig/samples/498-git-branch-risk-classifier.md b/skills/rig/samples/498-git-branch-risk-classifier.md new file mode 100644 index 0000000..6af64a8 --- /dev/null +++ b/skills/rig/samples/498-git-branch-risk-classifier.md @@ -0,0 +1,32 @@ +# 498 - Git Branch Risk Classifier + +```rig +import { agent, defineTool, p, s, steering, repair } from "rig"; + +const classifyBranchRisk = defineTool("classifyBranchRisk", { + description: "Classify a branch merge risk based on ahead/behind commit counts", + parameters: s.object({ branch: s.string, ahead: s.int, behind: s.int }), + handler({ ahead, behind }): "safe" | "caution" | "risky" | "critical" { + const total = ahead + behind; + if (total === 0) return "safe" as const; + if (total <= 5) return "caution" as const; + if (total <= 20) return "risky" as const; + return "critical" as const; + }, +}); + +// Agent role: list local git branches with ahead/behind counts and classify each by merge risk. +const gitBranchRiskClassifier = agent({ + model: "small", + instructions: p`List branches with ${p.bash("git branch --format='%(refname:short) %(ahead-behind:HEAD)' 2>/dev/null | head -20 || true")}. Use classifyBranchRisk for each branch with its ahead/behind numbers. Return the risk classification for every branch.`, + output: s.object({ + branches: s.array(s.object({ name: s.string, ahead: s.int, behind: s.int, risk: s.string })), + riskyCritical: s.array(s.string), + summary: s.string, + }), + tools: [classifyBranchRisk], + addons: [steering(), repair()], +}); + +export default gitBranchRiskClassifier; +``` diff --git a/skills/rig/samples/499-test-file-coverage-pipeline.md b/skills/rig/samples/499-test-file-coverage-pipeline.md new file mode 100644 index 0000000..d2c4987 --- /dev/null +++ b/skills/rig/samples/499-test-file-coverage-pipeline.md @@ -0,0 +1,58 @@ +# 499 - Test File Coverage Pipeline + +```rig +import { agent, defineTool, workflow, p, s } from "rig"; + +// Agent role: discover all test and spec files in the workspace. +const testDiscoverer = agent({ + model: "small", + instructions: p`Find test files with ${p.glob("**/*.test.ts")} and spec files with ${p.bash("find . -name '*.spec.ts' -not -path '*/node_modules/*' 2>/dev/null | head -30 || true")}. Return distinct lists and total count.`, + output: s.object({ + testFiles: s.array(s.path), + specFiles: s.array(s.path), + totalCount: s.int, + }), +}); + +const classifyTest = defineTool("classifyTest", { + description: "Classify a test file as unit, integration, or e2e based on its path and name", + parameters: s.object({ file: s.path, lineCount: s.int }), + handler({ file }): "unit" | "integration" | "e2e" { + if (file.includes("e2e") || file.includes("end-to-end")) return "e2e" as const; + if (file.includes("integration") || file.includes("integ")) return "integration" as const; + return "unit" as const; + }, +}); + +// Agent role: classify test files by type and produce counts. +const testClassifier = agent({ + model: "small", + input: s.object({ testFiles: s.array(s.path), specFiles: s.array(s.path), totalCount: s.int }), + instructions: p`Use classifyTest for each file in input.testFiles and input.specFiles. Also check file sizes using ${p.bash("wc -l $(find . -name '*.test.ts' -o -name '*.spec.ts' 2>/dev/null | head -10 | tr '\\n' ' ') 2>/dev/null || true")}. Return classification map and counts.`, + output: s.object({ + classifications: s.record(s.string), + unitCount: s.int, + integrationCount: s.int, + e2eCount: s.int, + }), + tools: [classifyTest], +}); + +// Workflow role: discover test files, classify them, and produce a report. +const testFileCoveragePipeline = workflow({ + meta: { name: "test-file-coverage-pipeline", description: "Discover and classify test files in a three-stage pipeline" }, + body: async ({ call }) => { + const discovered = await call(testDiscoverer, ""); + const classified = await call(testClassifier, discovered ?? { testFiles: [], specFiles: [], totalCount: 0 }); + return { + totalFiles: discovered?.totalCount ?? 0, + unitCount: classified?.unitCount ?? 0, + integrationCount: classified?.integrationCount ?? 0, + e2eCount: classified?.e2eCount ?? 0, + classifications: classified?.classifications ?? {}, + }; + }, +}); + +export default testFileCoveragePipeline; +``` diff --git a/skills/rig/samples/500-json-schema-type-distribution.md b/skills/rig/samples/500-json-schema-type-distribution.md new file mode 100644 index 0000000..69ac9e0 --- /dev/null +++ b/skills/rig/samples/500-json-schema-type-distribution.md @@ -0,0 +1,39 @@ +# 500 - Json Schema Type Distribution + +```rig +import { agent, defineTool, p, s } from "rig"; + +const analyzeSchemaTypes = defineTool("analyzeSchemaTypes", { + description: "Analyze a JSON schema file and count property types", + parameters: s.object({ schemaFile: s.path, content: s.string }), + handler({ content }) { + try { + const schema = JSON.parse(content); + const props = schema.properties ?? {}; + const typeCounts: Record = {}; + for (const v of Object.values(props) as Array<{ type?: string }>) { + const t = v.type ?? "unknown"; + typeCounts[t] = (typeCounts[t] ?? 0) + 1; + } + return { propertyCount: Object.keys(props).length, typeCounts }; + } catch { + return { propertyCount: 0, typeCounts: {} }; + } + }, +}); + +// Agent role: discover JSON schema files and compute the distribution of property types across all schemas. +const jsonSchemaTypeDistribution = agent({ + model: "small", + instructions: p`Find JSON schema files with ${p.glob("**/*.schema.json")} and also using ${p.bash("find . -name '*.schema.json' -not -path '*/node_modules/*' 2>/dev/null | head -10 || true")}. Read and analyze each file using analyzeSchemaTypes. Aggregate property type counts across all schemas. Return the type distribution and most common type.`, + output: s.object({ + schemaFiles: s.array(s.path), + totalProperties: s.int, + typeDistribution: s.record(s.int), + mostCommonType: s.string, + }), + tools: [analyzeSchemaTypes], +}); + +export default jsonSchemaTypeDistribution; +```