Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions skills/rig/samples/290-commit-churn-classifier.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# 290-commit-churn-classifier - Commit Churn Classifier

```rig
import { agent, p, s, defineTool, steering, repair } from "rig";

const classifyRisk = defineTool("classifyRisk", {
description: "Classify churn risk level based on commit count",
parameters: s.object({ churnCount: s.int }),
handler: ({ churnCount }) => {
if (churnCount <= 2) return "stable" as const;
if (churnCount <= 5) return "active" as const;
if (churnCount <= 10) return "volatile" as const;
return "critical" as const;
},
});

// Agent role: analyze git commit history to classify file churn and risk levels
const commitChurnClassifier = agent({
model: "small",
instructions: p`Analyze commit history from ${p.bash("git log --name-only --format='' | grep -v '^$' | sort | uniq -c | sort -rn | head -30")} and classify each file's churn risk using the classifyRisk tool. Return a record keyed by file path.`,
output: s.record(s.object({
churnCount: s.int,
riskLevel: s.enum("stable", "active", "volatile", "critical"),
})),
tools: [classifyRisk],
addons: [steering(), repair()],
});

export default commitChurnClassifier;
```
37 changes: 37 additions & 0 deletions skills/rig/samples/291-npm-package-size-estimator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# 291-npm-package-size-estimator - Npm Package Size Estimator

```rig
import { agent, p, s, defineTool, repair } from "rig";

const classifySize = defineTool("classifySize", {
description: "Classify a package size in KB into a tier rating",
parameters: s.object({ sizeKb: s.number }),
handler: ({ sizeKb }) => {
if (sizeKb < 50) return "tiny" as const;
if (sizeKb < 200) 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 the npm package size and classify it into a tier rating
const npmPackageSizeEstimator = agent({
model: "small",
instructions: p`Estimate this package's size using:
- Pack dry-run: ${p.bash("npm pack --dry-run 2>&1 | head -40")}
- Directory size: ${p.bash("du -sh . 2>/dev/null | head -5")}

Use the classifySize tool to determine the tier rating. Return estimatedSizeKb, topFiles (up to 5 files with their sizes), sizeRating, and a recommendation.`,
output: s.object({
estimatedSizeKb: s.number,
topFiles: s.array(s.object({ name: s.string, sizeKb: s.number })),
sizeRating: s.enum("tiny", "small", "medium", "large", "xlarge"),
recommendation: s.string,
}),
tools: [classifySize],
addons: [repair()],
});

export default npmPackageSizeEstimator;
```
42 changes: 42 additions & 0 deletions skills/rig/samples/292-ts-branch-coverage-analyzer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# 292-ts-branch-coverage-analyzer - Ts Branch Coverage Analyzer

```rig
import { agent, p, s } from "rig";

// Agent role: analyze TypeScript source for branch coverage patterns
const branchAnalyzer = agent({
name: "branchAnalyzer",
model: "small",
instructions: p`Analyze the TypeScript source provided in the input and identify all branches (if/ternary/switch/logical). For each branch, determine the function name, line number, branch type, and whether it is likely covered based on test presence. Return a detailed array of branch objects.`,
input: s.object({ source: s.string }),
output: s.array(s.object({
functionName: s.string,
line: s.int,
branchType: s.enum("if", "ternary", "switch", "logical"),
covered: s.boolean,
})),
});

// Agent role: coordinate branch coverage analysis for a TypeScript file
const tsBranchCoverageAnalyzer = agent({
model: "small",
instructions: p`Read the TypeScript file at the path in the input using the readInput intent and delegate branch analysis to the branchAnalyzer subagent. Compute totalBranches, coveredBranches, and uncoveredBranches from the results.`,
input: s.object({ filePath: s.path }),
output: s.object({
branches: s.array(s.object({
functionName: s.string,
line: s.int,
branchType: s.enum("if", "ternary", "switch", "logical"),
covered: s.boolean,
})),
summary: s.object({
totalBranches: s.int,
coveredBranches: s.int,
uncoveredBranches: s.int,
}),
}),
agents: { branchAnalyzer },
});

export default tsBranchCoverageAnalyzer;
```
35 changes: 35 additions & 0 deletions skills/rig/samples/293-env-variable-type-inferrer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# 293-env-variable-type-inferrer - Env Variable Type Inferrer

```rig
import { agent, p, s, defineTool, repair } from "rig";

const inferType = defineTool("inferType", {
description: "Infer the type of an environment variable value",
parameters: s.object({ key: s.string, value: s.string }),
handler: ({ value }) => {
if (value === "true" || value === "false") return "boolean" as const;
if (/^\d+(\.\d+)?$/.test(value)) return "number" as const;
if (/^https?:\/\//.test(value)) return "url" as const;
if (/^[./~]|^\$HOME/.test(value)) return "path" as const;
if (value.length === 0 || value.startsWith("<") || value.startsWith("#")) return "unknown" as const;
return "string" as const;
},
});

// Agent role: infer types of environment variables from .env.example
const envVariableTypeInferrer = agent({
model: "small",
instructions: p`Analyze the environment variable definitions from ${p.readOptional(".env.example", "# no .env.example found")} and use the inferType tool for each variable. Produce a record of variable names to their inferred type and description, plus allDocumented indicating whether all variables have non-empty descriptions.`,
output: s.object({
variables: s.record(s.object({
type: s.enum("string", "number", "boolean", "url", "path", "unknown"),
description: s.string,
})),
allDocumented: s.boolean,
}),
tools: [inferType],
addons: [repair()],
});

export default envVariableTypeInferrer;
```
41 changes: 41 additions & 0 deletions skills/rig/samples/294-makefile-target-extractor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# 294-makefile-target-extractor - Makefile Target Extractor

```rig
import { agent, p, s, defineTool, repair } from "rig";

const parseTarget = defineTool("parseTarget", {
description: "Parse a Makefile target line and return its metadata",
parameters: s.object({ line: s.string, phonyTargets: s.string }),
handler: ({ line, phonyTargets }) => {
const match = line.match(/^([a-zA-Z0-9_.-]+)\s*:/);
if (!match) return null;
const name = match[1];
const isPhony = phonyTargets.includes(name);
const hasHelp = line.includes("##");
const descMatch = line.match(/##\s*(.+)/);
return { name, isPhony, hasHelp, description: descMatch ? descMatch[1].trim() : undefined };
},
});

// Agent role: extract and classify targets from a Makefile
const makefileTargetExtractor = agent({
model: "small",
instructions: p`Parse the Makefile content: ${p.readOptional("Makefile", "# no Makefile found")}

Use the parseTarget tool for each target line. Collect all targets and their metadata, count phony vs real targets, and return the structured result.`,
output: s.object({
targets: s.array(s.object({
name: s.string,
isPhony: s.boolean,
hasHelp: s.boolean,
description: s.optional(s.string),
})),
totalCount: s.int,
phonyCount: s.int,
}),
tools: [parseTarget],
addons: [repair()],
});

export default makefileTargetExtractor;
```
37 changes: 37 additions & 0 deletions skills/rig/samples/295-test-fixture-generator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# 295-test-fixture-generator - Test Fixture Generator

```rig
import { agent, p, s } from "rig";

// Agent role: extract function signature from a TypeScript source file
const signatureAnalyzer = agent({
name: "signatureAnalyzer",
model: "small",
instructions: p`Given the TypeScript source in the input, extract the signature for the function named in functionName. Return the parameter types, return type, and any imports needed to use this function in a test.`,
input: s.object({ source: s.string, functionName: s.string }),
output: s.object({
params: s.array(s.object({ name: s.string, type: s.string })),
returnType: s.string,
imports: s.array(s.string),
}),
});

// Agent role: generate test fixtures for a TypeScript function
const testFixtureGenerator = agent({
model: "small",
instructions: p`Generate a test fixture for the function specified in the input.

Read the source file: ${p.readInput("sourceFile")}

Delegate signature extraction to the signatureAnalyzer subagent for the function named in the input's functionName field. Then generate a complete test fixture file with sample inputs, expected outputs, and required imports. Write the fixture to the path in suggestedFileName.`,
input: s.object({ sourceFile: s.path, functionName: s.string }),
output: s.object({
fixtureCode: s.string,
imports: s.array(s.string),
suggestedFileName: s.path,
}),
agents: { signatureAnalyzer },
});

export default testFixtureGenerator;
```
48 changes: 48 additions & 0 deletions skills/rig/samples/296-git-file-ownership-mapper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# 296-git-file-ownership-mapper - Git File Ownership Mapper

```rig
import { agent, p, s, defineTool, steering } from "rig";
import { execSync } from "node:child_process";

const getFileOwner = defineTool("getFileOwner", {
description: "Get git ownership info for a file path",
parameters: s.object({ filePath: s.path }),
handler: async ({ filePath }) => {
try {
const output = execSync(`git log --format="%ae" -- "${filePath}" 2>/dev/null`, { encoding: "utf8" });
const emails = output.trim().split("\n").filter((e: string) => e.length > 0);
const counts: Record<string, number> = {};
for (const email of emails) {
counts[email] = (counts[email] ?? 0) + 1;
}
const sorted = Object.entries(counts).sort((a: [string, number], b: [string, number]) => b[1] - a[1]);
const primaryOwner = sorted[0]?.[0] ?? "unknown";
const commitCount = emails.length;
const contributors = sorted.map((e: [string, number]) => e[0]);
return { primaryOwner, commitCount, contributors };
} catch {
return { primaryOwner: "unknown", commitCount: 0, contributors: [] };
}
},
});

// Agent role: map git file ownership by analyzing commit history for TypeScript files
const gitFileOwnershipMapper = agent({
model: "small",
instructions: p`Discover TypeScript source files: ${p.glob("src/**/*.ts")}

Use the getFileOwner tool for each discovered file path to collect ownership data. Build a complete ownership record and identify the mostActiveContributor across all files.`,
output: s.object({
ownership: s.record(s.object({
primaryOwner: s.string,
commitCount: s.int,
contributors: s.array(s.string),
})),
mostActiveContributor: s.string,
}),
tools: [getFileOwner],
addons: [steering()],
});

export default gitFileOwnershipMapper;
```
52 changes: 52 additions & 0 deletions skills/rig/samples/297-workflow-input-validator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# 297-workflow-input-validator - Workflow Input Validator

```rig
import { agent, p, s, defineTool, repair } from "rig";
import { readFile } from "node:fs/promises";

const validateWorkflowInputs = defineTool("validateWorkflowInputs", {
description: "Parse workflow_dispatch inputs from a GitHub Actions workflow YAML file",
parameters: s.object({ filePath: s.path }),
handler: async ({ filePath }) => {
const content = await readFile(filePath, "utf8");
const inputSection = content.match(/workflow_dispatch:[\s\S]*?inputs:([\s\S]*?)(?=\n\S|\n\w+:|\n [a-z]+:(?!\s*\n\s+type:)|\Z)/)?.[1] ?? "";
const inputMatches = [...inputSection.matchAll(/^\s{6}(\w[\w-]*):/gm)];
const inputs = inputMatches.map((m: RegExpMatchArray) => {
const name = m[1];
const block = inputSection.slice(m.index ?? 0, (m.index ?? 0) + 300);
const type = block.match(/type:\s*(\w+)/)?.[1] ?? "string";
const required = /required:\s*true/.test(block);
const hasDefault = /default:/.test(block);
return { name, type, required, hasDefault };
});
return {
inputs,
inputCount: inputs.length,
hasRequiredWithoutDefault: inputs.some((i: { required: boolean; hasDefault: boolean }) => i.required && !i.hasDefault),
};
},
});

// Agent role: validate GitHub Actions workflow_dispatch inputs across all workflow files
const workflowInputValidator = agent({
model: "small",
instructions: p`Validate workflow_dispatch inputs in all workflow files discovered at ${p.glob(".github/workflows/*.yml")}. Use the validateWorkflowInputs tool for each file path. Return a record keyed by filename and the total workflow count.`,
output: s.object({
workflows: s.record(s.object({
inputs: s.array(s.object({
name: s.string,
type: s.string,
required: s.boolean,
hasDefault: s.boolean,
})),
inputCount: s.int,
hasRequiredWithoutDefault: s.boolean,
})),
totalWorkflows: s.int,
}),
tools: [validateWorkflowInputs],
addons: [repair()],
});

export default workflowInputValidator;
```
48 changes: 48 additions & 0 deletions skills/rig/samples/298-ts-dead-export-finder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# 298-ts-dead-export-finder - Ts Dead Export Finder

```rig
import { agent, p, s, defineTool, steering, repair } from "rig";
import { readFile } from "node:fs/promises";

const findUnusedExports = defineTool("findUnusedExports", {
description: "Find exported symbols in a TypeScript file that are not imported elsewhere",
parameters: s.object({ filePath: s.path, allFiles: s.array(s.string) }),
handler: async ({ filePath, allFiles }) => {
const content = await readFile(filePath, "utf8");
const exportMatches = [...content.matchAll(/export\s+(?:const|function|class|type|interface|enum)\s+(\w+)/g)];
const exportedNames = exportMatches.map((m: RegExpMatchArray) => m[1]);
const unusedNames: string[] = [];
for (const name of exportedNames) {
let isImported = false;
for (const other of allFiles) {
if (other === filePath) continue;
try {
const otherContent = await readFile(other, "utf8");
if (otherContent.includes(name)) { isImported = true; break; }
} catch { /* skip */ }
}
if (!isImported) unusedNames.push(name);
}
return unusedNames;
},
});

// Agent role: find unused TypeScript exports across the src directory
const tsDeadExportFinder = agent({
model: "small",
instructions: p`Find potentially dead (unused) TypeScript exports.
Files with exports: ${p.bash("grep -rl 'export ' src/ --include='*.ts' 2>/dev/null | head -20 || echo 'none'")}
All TS files: ${p.glob("src/**/*.ts")}

Use the findUnusedExports tool for each file that has exports, passing all discovered files. Build a record of unused exports keyed by file path, the total count, and whether dead code was found.`,
output: s.object({
unusedExports: s.record(s.array(s.string)),
totalUnused: s.int,
hasDeadCode: s.boolean,
}),
tools: [findUnusedExports],
addons: [steering(), repair()],
});

export default tsDeadExportFinder;
```
Loading