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
31 changes: 31 additions & 0 deletions skills/rig/samples/491-dotenv-process-drift-detector.md
Original file line number Diff line number Diff line change
@@ -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;
```
43 changes: 43 additions & 0 deletions skills/rig/samples/492-vitest-snapshot-count-workflow.md
Original file line number Diff line number Diff line change
@@ -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;
```
32 changes: 32 additions & 0 deletions skills/rig/samples/493-git-stale-branch-reporter.md
Original file line number Diff line number Diff line change
@@ -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;
```
29 changes: 29 additions & 0 deletions skills/rig/samples/494-ts-abstract-class-finder.md
Original file line number Diff line number Diff line change
@@ -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;
```
30 changes: 30 additions & 0 deletions skills/rig/samples/495-dockerfile-env-inspector.md
Original file line number Diff line number Diff line change
@@ -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;
```
33 changes: 33 additions & 0 deletions skills/rig/samples/496-npm-script-prefix-analyzer.md
Original file line number Diff line number Diff line change
@@ -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;
```
34 changes: 34 additions & 0 deletions skills/rig/samples/497-ts-default-param-extractor.md
Original file line number Diff line number Diff line change
@@ -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;
```
32 changes: 32 additions & 0 deletions skills/rig/samples/498-git-branch-risk-classifier.md
Original file line number Diff line number Diff line change
@@ -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;
```
58 changes: 58 additions & 0 deletions skills/rig/samples/499-test-file-coverage-pipeline.md
Original file line number Diff line number Diff line change
@@ -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;
```
39 changes: 39 additions & 0 deletions skills/rig/samples/500-json-schema-type-distribution.md
Original file line number Diff line number Diff line change
@@ -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<string, number> = {};
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;
```