Skip to content
Merged
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
40 changes: 40 additions & 0 deletions skills/rig/samples/451-shell-shebang-glob-validator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# 451 - Shell Shebang Glob Validator

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

const checkShebangLine = defineTool("checkShebangLine", {
description: "Check if a shell script file has a shebang line and whether it is standard",
parameters: s.object({ filePath: s.path }),
handler: async ({ filePath }: { filePath: string }) => {
const content = await readFile(filePath, "utf8");
const firstLine = content.split("\n")[0] ?? "";
const hasShebang = firstLine.startsWith("#!");
const shebangLine = hasShebang ? firstLine : undefined;
const isStandard = hasShebang && (firstLine === "#!/bin/sh" || firstLine === "#!/bin/bash" || firstLine === "#!/usr/bin/env bash" || firstLine === "#!/usr/bin/env sh");
return { hasShebang, shebangLine, isStandard };
},
});

// Agent role: Scan all shell scripts in the workspace and report shebang line status for each.
const shellShebangValidator = agent({
model: "small",
instructions: p`You are a shell script auditor. The workspace contains these shell scripts: ${p.glob("**/*.sh")}. For each file path listed, call the checkShebangLine tool. Then return the full result object.`,
output: s.object({
files: s.record(s.object({
hasShebang: s.boolean,
shebangLine: s.optional(s.string),
isStandard: s.boolean,
})),
missingShebangCount: s.int,
standardShebangCount: s.int,
totalFiles: s.int,
}),
tools: [checkShebangLine],
addons: [repair()],
});

export default shellShebangValidator;

```
36 changes: 36 additions & 0 deletions skills/rig/samples/452-git-log-graph-summarizer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# 452 - Git Log Graph Summarizer

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

const parseGraphLine = defineTool("parseGraphLine", {
description: "Classify a git log graph line as merge, commit, or branch-point",
parameters: s.object({ line: s.string }),
handler: ({ line }: { line: string }): "merge" | "commit" | "branch-point" => {
if (line.includes("Merge")) return "merge" as const;
if (/\*/.test(line) && /[0-9a-f]{7}/.test(line)) return "commit" as const;
return "branch-point" as const;
},
});

// Agent role: Summarize the git log graph by classifying each line and counting merges, commits, and branch-points.
const gitLogGraphSummarizer = agent({
model: "small",
instructions: p`Analyze the following git log graph output: ${p.bash("git log --oneline --graph -20")}. For each line, call parseGraphLine to classify it. Return the structured summary.`,
output: s.object({
lines: s.array(s.object({
type: s.enum("merge", "commit", "branch-point"),
hash: s.optional(s.string),
message: s.optional(s.string),
})),
mergeCount: s.int,
commitCount: s.int,
branchPoints: s.int,
}),
tools: [parseGraphLine],
addons: [steering(), repair()],
});

export default gitLogGraphSummarizer;

```
44 changes: 44 additions & 0 deletions skills/rig/samples/453-ts-complexity-scorer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# 453 - TypeScript Complexity Scorer

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

const scoreFileComplexity = defineTool("scoreFileComplexity", {
description: "Score the complexity of a TypeScript file by counting nested braces, ternaries, and callbacks",
parameters: s.object({ filePath: s.path }),
handler: async ({ filePath }: { filePath: string }) => {
const content = await readFile(filePath, "utf8");
const nestedBraces = (content.match(/\{[^{}]*\{/g) ?? []).length;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/grill-with-docs] The regex /?[^:]+:/g for ternary detection will false-positive on object literals, type annotations, and template strings — common in TypeScript. The scoring formula thus systematically over-counts complexity in type-heavy files.

💡 Suggested fix

This is a sample, so perfect accuracy isn't required, but a brief comment acknowledging the limitation would help readers calibrate expectations:

// Heuristic only — type annotations and object literals may inflate ternary count
const ternaries = (content.match(/\?[^:]+:/g) ?? []).length;

const ternaries = (content.match(/\?[^:]+:/g) ?? []).length;
const callbacks = (content.match(/=>\s*\{/g) ?? []).length;
const score = nestedBraces + ternaries * 0.5 + callbacks * 0.5;
const complexity: "low" | "medium" | "high" = score < 5 ? "low" : score < 15 ? "medium" : "high";
const topContributors: string[] = [];
if (nestedBraces > 0) topContributors.push(`nestedBraces:${nestedBraces}`);
if (ternaries > 0) topContributors.push(`ternaries:${ternaries}`);
if (callbacks > 0) topContributors.push(`callbacks:${callbacks}`);
return { score, complexity, topContributors };
},
});

// Agent role: Score complexity of all TypeScript source files in src/ and return a ranked summary.
const tsComplexityScorer = agent({
model: "small",
instructions: p`Score the complexity of these TypeScript files: ${p.glob("src/**/*.ts")}. Call scoreFileComplexity for each file path. Then return the full result.`,
output: s.object({
files: s.record(s.object({
score: s.number,
complexity: s.enum("low", "medium", "high"),
topContributors: s.array(s.string),
})),
averageScore: s.number,
mostComplexFile: s.optional(s.string),
}),
tools: [scoreFileComplexity],
addons: [repair()],
});

export default tsComplexityScorer;

```
41 changes: 41 additions & 0 deletions skills/rig/samples/454-parallel-multi-tool-workflow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# 454 - Parallel Multi Tool Workflow

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

// Agent role: Count files grouped by extension in the workspace.
const fileCountAgent = agent({
model: "small",
instructions: p`Count files by extension using: ${p.bash("find . -type f -not -path './.git/*' | sed 's/.*\\.//' | sort | uniq -c | sort -rn | head -20")}. Return extCounts mapping each extension to its count.`,
output: s.object({
extCounts: s.record(s.int),
}),
});

// Agent role: Count environment variables grouped by prefix category.
const envHealthAgent = agent({
model: "small",
instructions: p`Analyze environment variables using: ${p.bash("env | cut -d= -f1 | sed 's/_.*$//' | sort | uniq -c | sort -rn | head -20")}. Return categories mapping each prefix to its count.`,
output: s.object({
categories: s.record(s.int),
}),
});

// Workflow role: Run fileCountAgent and envHealthAgent then synthesize an overall health assessment.
const parallelMultiToolWorkflow = workflow({
meta: { name: "parallel-multi-tool-workflow", description: "Run file count and env health agents and combine results" },
body: async ({ call, phase }) => {
phase("Gather");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] The name parallelMultiToolWorkflow and file name 454-parallel-multi-tool-workflow imply parallel execution, but both call() invocations are sequential await — there is no parallelism. This is a naming/expectation mismatch that could confuse readers looking for a parallel pattern.

💡 Suggested fix

Either rename to 454-sequential-multi-agent-workflow, or if parallel execution is desired, check whether Promise.all([call(...), call(...)]) is supported by the runtime for concurrent agent calls.

const fileSummary = await call(fileCountAgent, "Analyze", { label: "file-count" });
const envSummary = await call(envHealthAgent, "Analyze", { label: "env-health" });
phase("Synthesize");
const totalFiles = Object.values(fileSummary?.extCounts ?? {}).reduce((a: number, b: unknown) => a + (b as number), 0);
const totalEnv = Object.values(envSummary?.categories ?? {}).reduce((a: number, b: unknown) => a + (b as number), 0);
const overallHealth: "healthy" | "degraded" | "unknown" = totalFiles > 0 && totalEnv > 0 ? "healthy" : totalFiles > 0 || totalEnv > 0 ? "degraded" : "unknown";
return { fileSummary: fileSummary ?? { extCounts: {} }, envSummary: envSummary ?? { categories: {} }, overallHealth };
},
});

export default parallelMultiToolWorkflow;

```
48 changes: 48 additions & 0 deletions skills/rig/samples/455-zlib-compression-analyzer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# 455 - Zlib Compression Analyzer

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

const measureCompressionRatio = defineTool("measureCompressionRatio", {
description: "Measure the zlib compression ratio of a file and classify it",
parameters: s.object({ filePath: s.path }),
handler: async ({ filePath }: { filePath: string }) => {
const buf = await readFile(filePath);
const originalSize = buf.length;
if (originalSize === 0) {
return { ratio: 1, compressionClass: "incompressible" as const, originalSize: 0, compressedSize: 0 };
}
const compressed = deflateSync(buf);
const compressedSize = compressed.length;
const ratio = compressedSize / originalSize;
const compressionClass: "excellent" | "good" | "poor" | "incompressible" =
ratio < 0.3 ? "excellent" : ratio < 0.6 ? "good" : ratio < 0.9 ? "poor" : "incompressible";
return { ratio, compressionClass, originalSize, compressedSize };
},
});

// Agent role: Analyze zlib compression ratios for files in targetDir and report compressibility.
const zlibCompressionAnalyzer = agent({
model: "small",
input: s.object({ targetDir: s.string }),
instructions: p`List files to analyze using: ${p.bash("find . -maxdepth 3 -type f -not -path './.git/*' | head -30")}. Call measureCompressionRatio for each file path. Return the full analysis.`,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/grill-with-docs] targetDir is declared in input but never used — the instruction hardcodes find . regardless. This makes the input a no-op, misleading readers who use this as a pattern.

💡 Suggested fix

Either remove the targetDir input field and document that the agent scans from cwd, or thread it into the command with p.readInput("targetDir").

output: s.object({
files: s.record(s.object({
ratio: s.number,
compressionClass: s.enum("excellent", "good", "poor", "incompressible"),
originalSize: s.int,
compressedSize: s.int,
})),
totalFiles: s.int,
averageRatio: s.number,
mostCompressibleFile: s.optional(s.string),
}),
tools: [measureCompressionRatio],
addons: [repair()],
});

export default zlibCompressionAnalyzer;

```
38 changes: 38 additions & 0 deletions skills/rig/samples/456-file-crypto-hash-reporter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# 456 - File Crypto Hash Reporter

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

const computeFileHash = defineTool("computeFileHash", {
description: "Compute a cryptographic hash of a file",
parameters: s.object({ filePath: s.path, algorithm: s.optional(s.string) }),
handler: async ({ filePath, algorithm = "sha256" }: { filePath: string; algorithm?: string }) => {
const buf = await readFile(filePath);
const hash = createHash(algorithm).update(buf).digest("hex");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/grill-with-docs] p.glob("**/*") will match all files including binaries, node_modules, .git objects, etc. Without a maxdepth or exclusion, this risks flooding the LLM with thousands of paths and potentially hashing very large binary files.

💡 Suggested fix

Bound the glob or use a bash command with exclusions, similar to 455:

instructions: p`Find files to hash: ${p.bash("find . -maxdepth 3 -type f -not -path './.git/*' -not -path './node_modules/*' | head -30")}`,

return { hash, sizeBytes: buf.length, algorithm };
},
});

// Agent role: Compute cryptographic hashes for all files in targetDir.
const fileCryptoHashReporter = agent({
model: "small",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/grill-with-docs] Same issue as 455: targetDir and algorithm are declared in input but p.glob("**/*") ignores targetDir, and the instruction says to use algorithm from input but it is only passed via prompt text — the LLM may not reliably forward it to the tool.

💡 Suggested fix

For targetDir: scope the glob to p.readInput("targetDir") or remove the input field. For algorithm: since tool parameter defaults handle it, either remove it from input or wire it explicitly with p.readInput("algorithm").

input: s.object({ targetDir: s.string, algorithm: s.optional(s.string) }),
instructions: p`Find files to hash in the workspace: ${p.glob("**/*")}. For each file, call computeFileHash. Use the algorithm from input if provided (default sha256). Return the result.`,
output: s.object({
files: s.record(s.object({
hash: s.string,
sizeBytes: s.int,
algorithm: s.string,
})),
totalFiles: s.int,
algorithm: s.string,
}),
tools: [computeFileHash],
addons: [repair()],
});

export default fileCryptoHashReporter;

```
49 changes: 49 additions & 0 deletions skills/rig/samples/457-source-line-length-auditor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# 457 - Source Line Length Auditor

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

const auditFileLengths = defineTool("auditFileLengths", {
description: "Audit a file for lines exceeding the maximum length limit",
parameters: s.object({ filePath: s.path, maxLen: s.optional(s.number) }),
handler: async ({ filePath, maxLen = 100 }: { filePath: string; maxLen?: number }) => {
const content = await readFile(filePath, "utf8");
const lines = content.split("\n");
let longLineCount = 0;
let longestLine = 0;
let worstLineNumber = 0;
lines.forEach((line: string, idx: number) => {
if (line.length > maxLen) {
longLineCount++;
if (line.length > longestLine) {
longestLine = line.length;
worstLineNumber = idx + 1;
}
}
});
return { longLineCount, longestLine, worstLineNumber };
},
});

// Agent role: Audit TypeScript source files for lines exceeding maxLen and report violations.
const sourceLineLengthAuditor = agent({
model: "small",
input: s.object({ dir: s.string, maxLen: s.optional(s.number) }),
instructions: p`Audit these TypeScript files for long lines: ${p.glob("**/*.ts")}. Call auditFileLengths for each file, passing maxLen from input. Return the full audit.`,
output: s.object({
files: s.record(s.object({
longLineCount: s.int,
longestLine: s.int,
worstLineNumber: s.int,
})),
totalViolations: s.int,
worstFile: s.string,
}),
tools: [auditFileLengths],
addons: [repair()],
});

export default sourceLineLengthAuditor;

```
36 changes: 36 additions & 0 deletions skills/rig/samples/458-workspace-symlink-inventory.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# 458 - Workspace Symlink Inventory

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

const resolveSymlink = defineTool("resolveSymlink", {
description: "Resolve a symlink path and check if it is broken",
parameters: s.object({ symlinkPath: s.path }),
handler: async ({ symlinkPath }: { symlinkPath: string }) => {
const target = await readlink(symlinkPath).catch(() => "");
if (!target) return { path: symlinkPath, target: "", broken: true };
const broken = await realpath(symlinkPath).then(() => false).catch(() => true);
return { path: symlinkPath, target, broken };
},
});

// Agent role: Inventory all symlinks in the workspace and report whether each is broken.
const workspaceSymlinkInventory = agent({
model: "small",
instructions: p`Find all symlinks in the workspace: ${p.bash("find . -type l 2>/dev/null | head -50")}. Call resolveSymlink for each path. Return the inventory.`,
output: s.object({
symlinks: s.array(s.object({
path: s.path,
target: s.string,
broken: s.boolean,
})),
totalSymlinks: s.int,
brokenCount: s.int,
}),
tools: [resolveSymlink],
});

export default workspaceSymlinkInventory;

```
48 changes: 48 additions & 0 deletions skills/rig/samples/459-xml-attribute-extractor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# 459 - XML Attribute Extractor

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

const parseXmlAttributes = defineTool("parseXmlAttributes", {
description: "Extract element names and their attribute names from XML content",
parameters: s.object({ xmlContent: s.string }),
handler: ({ xmlContent }: { xmlContent: string }) => {
const openTagPattern = /<([A-Za-z][A-Za-z0-9_:-]*)([^>]*?)(?:\/?>)/g;
const elements: Record<string, string[]> = {};
let tagMatch: RegExpExecArray | null;
while ((tagMatch = openTagPattern.exec(xmlContent)) !== null) {
const tagName = tagMatch[1];
const attrStr = tagMatch[2] ?? "";
const attrs: string[] = [];
const ap = /([A-Za-z][A-Za-z0-9_:-]*)=/g;
let attrMatch: RegExpExecArray | null;
while ((attrMatch = ap.exec(attrStr)) !== null) {
attrs.push(attrMatch[1]);
}
if (!elements[tagName]) elements[tagName] = [];
attrs.forEach((a: string) => {
if (!elements[tagName].includes(a)) elements[tagName].push(a);
});
}
const uniqueElements = Object.keys(elements).length;
const uniqueAttributes = new Set(Object.values(elements).flat()).size;
return { elements, uniqueElements, uniqueAttributes };
},
});

// Agent role: Extract all XML element names and their attribute names from the given XML file.
const xmlAttributeExtractor = agent({
model: "small",
input: s.object({ xmlFile: s.string }),
instructions: p`Parse the XML file content: ${p.readInput("xmlFile")}. Call parseXmlAttributes with the full XML content. Return the extracted structure.`,
output: s.object({
elements: s.record(s.array(s.string)),
uniqueElements: s.int,
uniqueAttributes: s.int,
}),
tools: [parseXmlAttributes],
});

export default xmlAttributeExtractor;

```
Loading