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
54 changes: 54 additions & 0 deletions skills/rig/samples/371-npm-lifecycle-script-analyzer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# 371 - NPM Lifecycle Script Analyzer

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

const classifyScript = defineTool("classifyScript", {
description: "Classify an npm script by name and command into a lifecycle category.",
parameters: s.object({ scriptName: s.string, command: s.string }),
handler({ scriptName }) {
const name = scriptName.toLowerCase();
const isHook = /^(pre|post)/.test(name);
if (/build|compile|bundle|webpack|rollup|esbuild|tsc/.test(name)) return { category: "build" as const, isHook };
if (/test|jest|vitest|mocha|spec|coverage/.test(name)) return { category: "test" as const, isHook };
if (/lint|eslint|tslint|prettier|format|check/.test(name)) return { category: "lint" as const, isHook };
if (/release|publish|deploy|version|changelog/.test(name)) return { category: "release" as const, isHook };
if (isHook) return { category: "hook" as const, isHook: true };
return { category: "other" as const, isHook: false };
},
});

// Agent role: analyze npm lifecycle scripts in package.json and classify each one.
const npmLifecycleScriptAnalyzer = agent({
model: "small",
instructions: p`Analyze the npm lifecycle scripts defined in package.json.

package.json contents:
${p.read("package.json")}

For each entry in the "scripts" field, call classifyScript with the script name and command.
Build a scripts record keyed by script name with command, category, and isHook fields.
Count hookCount (total scripts where isHook is true).
List missingRecommended: which of ["test", "build", "lint"] category names are absent from the scripts.
Set hasTestScript to true if any script has category "test", hasBuildScript if any has category "build".`,
tools: [classifyScript],
output: s.object({
scripts: s.record(
s.object({
command: s.string,
category: s.enum("build", "test", "lint", "release", "hook", "other"),
isHook: s.boolean,
})
),
hookCount: s.int,
missingRecommended: s.array(s.string),
hasTestScript: s.boolean,
hasBuildScript: s.boolean,
}),
maxTurns: 4,
addons: repair(),
});

export default npmLifecycleScriptAnalyzer;

```
64 changes: 64 additions & 0 deletions skills/rig/samples/372-ts-jsdoc-coverage-checker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# 372 - TS JSDoc Coverage Checker

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

const analyzeFunctionComments = defineTool("analyzeFunctionComments", {
description: "Count exported functions with and without JSDoc in a TypeScript file.",
parameters: s.object({ filePath: s.string }),
async handler({ filePath }) {
const { readFile } = await import("node:fs/promises");
const content = await readFile(filePath, "utf8");
const lines = content.split("\n");
let documentedCount = 0;
let undocumentedCount = 0;
for (let i = 0; i < lines.length; i++) {
if (/^export\s+(async\s+)?function|^export\s+const\s+\w+\s*=\s*(async\s+)?\(/.test(lines[i])) {
const preceding = lines.slice(Math.max(0, i - 3), i).join("\n");
if (/\/\*\*/.test(preceding)) {
documentedCount++;
} else {
undocumentedCount++;
}
}
}
const total = documentedCount + undocumentedCount;
return { documentedCount, undocumentedCount, coverage: total > 0 ? documentedCount / total : 0 };
},
});

// Agent role: check JSDoc coverage for exported functions across TypeScript files.
const tsJsdocCoverageChecker = agent({
model: "small",
instructions: p`Check JSDoc documentation coverage for exported TypeScript functions.

TypeScript source files:
${p.bash("find . -name '*.ts' -not -path '*/node_modules/*' -not -path '*/.git/*' | head -20")}

For each TypeScript file found, call analyzeFunctionComments with the file path.
Build a files record keyed by file path with documentedCount, undocumentedCount, and coverage.
Compute overall: totalFunctions (sum of all), documentedFunctions (sum of documented), coveragePercent (as 0-100).
List wellDocumentedFiles: files where coverage >= 0.8 (as paths).`,
tools: [analyzeFunctionComments],
output: s.object({
files: s.record(
s.object({
documentedCount: s.int,
undocumentedCount: s.int,
coverage: s.number,
})
),
overall: s.object({
totalFunctions: s.int,
documentedFunctions: s.int,
coveragePercent: s.number,
}),
wellDocumentedFiles: s.array(s.path),
}),
maxTurns: 6,
addons: repair(),
});

export default tsJsdocCoverageChecker;

```
52 changes: 52 additions & 0 deletions skills/rig/samples/373-git-hook-installer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# 373 - Git Hook Installer

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

const checkHooksDir = defineTool("checkHooksDir", {
description: "Check whether .git/hooks directory exists and is writable.",
parameters: s.object({}),
async handler() {
const { access, constants } = await import("node:fs/promises");
try {
await access(".git/hooks", constants.W_OK);
return { exists: true, writable: true };
} catch {
return { exists: false, writable: false };
}
},
});

// Agent role: install git hooks into the .git/hooks directory from the provided hook specs.
const gitHookInstaller = agent({
model: "small",
input: s.object({
hooks: s.array(
s.object({
name: s.string,
script: s.string,
description: s.string,
})
),
}),
instructions: p`Install git hooks into .git/hooks from the provided input.

First call checkHooksDir to verify the hooks directory is accessible.
For each hook in input.hooks, write the script to .git/hooks/<name> using p.write.
Track which hooks were written successfully and which were skipped (if directory not found).
Return writtenHooks (names of installed hooks), skippedHooks (names not installed),
totalWritten (count of written), and allWritten (true if writtenHooks.length === input.hooks.length).`,
tools: [checkHooksDir],
output: s.object({
writtenHooks: s.array(s.string),
skippedHooks: s.array(s.string),
totalWritten: s.int,
allWritten: s.boolean,
}),
maxTurns: 6,
addons: repair(),
});

export default gitHookInstaller;

```
46 changes: 46 additions & 0 deletions skills/rig/samples/374-ts-type-guard-generator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# 374 - TS Type Guard Generator

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

const extractInterfaces = defineTool("extractInterfaces", {
description: "Extract interface names from a TypeScript file using regex.",
parameters: s.object({ filePath: s.path }),
async handler({ filePath }) {
const { readFile } = await import("node:fs/promises");
const content = await readFile(filePath, "utf8");
const matches = [...content.matchAll(/^(?:export\s+)?interface\s+(\w+)/gm)];
return matches.map((m: RegExpMatchArray) => m[1]);
},
});

// Agent role: generate TypeScript type guard functions for interfaces found in a source file.
const tsTypeGuardGenerator = agent({
model: "small",
input: s.object({
sourceFile: s.path,
outputFile: s.path,
}),
instructions: p`Generate TypeScript type guard functions for interfaces in the source file.

Source file contents:
${p.readInput("sourceFile")}

1. Call extractInterfaces with the sourceFile path to get the list of interface names.
2. For each interface, generate a type guard function: \`export function is<Name>(val: unknown): val is <Name> { ... }\`
3. Write all generated type guards as valid TypeScript source to the output field "generatedSource".
4. Return generatedGuards (list of interface names), outputFile (the outputFile from input), totalGenerated (count).`,
tools: [extractInterfaces],
output: s.object({
generatedGuards: s.array(s.string),
outputFile: s.path,
totalGenerated: s.int,
generatedSource: s.string,
}),
maxTurns: 5,
addons: repair(),
});

export default tsTypeGuardGenerator;

```
53 changes: 53 additions & 0 deletions skills/rig/samples/375-json-fixture-anonymizer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# 375 - JSON Fixture Anonymizer

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

const anonymizeValue = defineTool("anonymizeValue", {
description: "Anonymize a value based on field name heuristics.",
parameters: s.object({ fieldName: s.string, value: s.string }),
handler({ fieldName, value }) {
const name = fieldName.toLowerCase();
if (/email|mail/.test(name)) return "anonymized@example.com";
if (/password|secret|token|key|auth/.test(name)) return "***REDACTED***";
if (/name|user|first|last/.test(name)) return "Anonymous";
if (/phone|mobile|tel/.test(name)) return "+1-000-000-0000";
if (/address|street|city|zip|postal/.test(name)) return "123 Redacted St";
if (/ssn|id|number/.test(name)) return "XXX-XX-XXXX";
return value;
},
});

// 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 the JSON fixture file.

Input file contents:
${p.readInput("inputFile")}

For each field listed in input.fieldsToAnonymize, call anonymizeValue with the field name and its value.
Also apply anonymization heuristics to any other fields with sensitive-sounding names (email, password, token, name, etc.).
Write the anonymized JSON to the "result" output field.
Return fieldsAnonymized (count of fields changed), totalRecords (if array: length; if object: 1),
outputPath (same as input.outputFile), anonymizedFields (list of field names that were changed).`,
tools: [anonymizeValue],
output: s.object({
fieldsAnonymized: s.int,
totalRecords: s.int,
outputPath: s.path,
anonymizedFields: s.array(s.string),
result: s.string,
}),
maxTurns: 4,
addons: repair(),
});

export default jsonFixtureAnonymizer;

```
61 changes: 61 additions & 0 deletions skills/rig/samples/376-csv-to-markdown-table.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# 376 - CSV to Markdown Table

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

const parseCSVRow = defineTool("parseCSVRow", {
description: "Parse a single CSV row into an array of cell values, handling quoted fields.",
parameters: s.object({ row: s.string, delimiter: s.string }),
handler({ row, delimiter }) {
const cells: string[] = [];
let current = "";
let inQuotes = false;
for (let i = 0; i < row.length; i++) {
const ch = row[i];
if (ch === '"') {
inQuotes = !inQuotes;
} else if (ch === delimiter && !inQuotes) {
cells.push(current.trim());
current = "";
} else {
current += ch;
}
}
cells.push(current.trim());
return cells;
},
});

// Agent role: convert a CSV file to a Markdown table with optional statistics.
const csvToMarkdownTable = agent({
model: "small",
input: s.object({
csvFile: s.path,
outputFile: s.path,
includeStats: s.boolean,
}),
instructions: p`Convert the CSV file to a Markdown table.

CSV file contents:
${p.readInput("csvFile")}

1. Use parseCSVRow to parse the header row (first line) and each data row, using "," as delimiter.
2. Build a Markdown table with the header row and all data rows.
3. If input.includeStats is true, append a stats section with row count and column count.
4. Write the complete Markdown to the "markdownTable" output field.
5. Return rowCount (data rows only, not header), columnCount, outputFile (from input), headers (list of column names).`,
tools: [parseCSVRow],
output: s.object({
rowCount: s.int,
columnCount: s.int,
outputFile: s.path,
headers: s.array(s.string),
markdownTable: s.string,
}),
maxTurns: 4,
addons: repair(),
});

export default csvToMarkdownTable;

```
58 changes: 58 additions & 0 deletions skills/rig/samples/377-ts-interface-method-counter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# 377 - TS Interface Method Counter

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

const countInterfaceMethods = defineTool("countInterfaceMethods", {
description: "Count method signatures in TypeScript interfaces within a file.",
parameters: s.object({ filePath: s.path }),
async handler({ filePath }) {
const { readFile } = await import("node:fs/promises");
const content = await readFile(filePath, "utf8");
const results: Record<string, { methodCount: number; hasOptionalMethods: boolean; sourceFile: string }> = {};
const ifaceRe = /interface\s+(\w+)[^{]*\{([^}]*)\}/gs;
let match: RegExpExecArray | null;
while ((match = ifaceRe.exec(content)) !== null) {
const name = match[1];
const body = match[2];
const methods = (body.match(/\w+\??\s*\([^)]*\)/g) || []);
const hasOptional = /\w+\?\s*\(/.test(body);
results[name] = { methodCount: methods.length, hasOptionalMethods: hasOptional, sourceFile: filePath };
}
return results;
},
});

// Agent role: count method signatures in TypeScript interfaces across the source tree.
const tsInterfaceMethodCounter = agent({
model: "small",
instructions: p`Count method signatures in TypeScript interfaces.

TypeScript source files:
${p.glob("src/**/*.ts")}

For each file path listed, call countInterfaceMethods to extract interface method counts.
Merge all results into a single interfaces record keyed by interface name.
Compute totalInterfaces (total count of interface names found).
Compute averageMethodCount (total methods / totalInterfaces, or 0 if none).
Set largestInterface to the interface name with the most methods, or omit if no interfaces found.`,
tools: [countInterfaceMethods],
output: s.object({
interfaces: s.record(
s.object({
methodCount: s.int,
hasOptionalMethods: s.boolean,
sourceFile: s.string,
})
),
totalInterfaces: s.int,
averageMethodCount: s.number,
largestInterface: s.optional(s.string),
}),
maxTurns: 6,
addons: steering({ message: "Ensure every interface found is included in the interfaces record." }),
});

export default tsInterfaceMethodCounter;

```
Loading