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
47 changes: 47 additions & 0 deletions skills/rig/samples/431-jsonl-file-analyzer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# jsonl-file-analyzer - JSONL File Analyzer

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

const analyzeJsonLine = defineTool("analyzeJsonLine", {
description: "Parse a single JSONL line and return validity, keys, and value types.",
parameters: s.object({
line: s.string,
}),
handler: async ({ line }) => {
try {
const obj = JSON.parse(line);
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) {
return { valid: false, keys: [] as string[], valueTypes: {} as Record<string, string> };
}
const keys = Object.keys(obj);
const valueTypes: Record<string, string> = {};
for (const k of keys) {
const v = obj[k];
valueTypes[k] = Array.isArray(v) ? "array" : typeof v;
}
return { valid: true, keys, valueTypes };
} catch {
return { valid: false, keys: [] as string[], valueTypes: {} as Record<string, string> };
}
},
});

// Agent role: analyze a JSONL file, report per-line validity, top keys, and schema consistency.
const jsonlFileAnalyzer = agent({
model: "small",
input: s.object({ inputFile: s.path }),
output: s.object({
totalLines: s.int,
validLines: s.int,
invalidLines: s.int,
topKeys: s.array(s.string),
schemaConsistent: s.boolean,
}),
instructions: p`Read the JSONL file at ${p.readInput("inputFile")} line by line. For each non-empty line call analyzeJsonLine. Count total, valid, and invalid lines. Identify the top 5 most frequent keys across all valid lines. Determine if all valid lines share the same key set (schemaConsistent). Return the declared output.`,
tools: [analyzeJsonLine],
addons: [repair()],
});

export default jsonlFileAnalyzer;
```
45 changes: 45 additions & 0 deletions skills/rig/samples/432-npm-peer-dep-checker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# npm-peer-dep-checker - NPM Peer Dep Checker

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

const checkPeerConflict = defineTool("checkPeerConflict", {
description: "Classify a peer dependency as conflicting, warn, or ok.",
parameters: s.object({
package: s.string,
required: s.string,
installed: s.optional(s.string),
}),
handler: async ({ installed, required }) => {
if (!installed) {
return { conflicting: true, reason: "Package not installed", severity: "error" as const };
}
const [reqMajor] = required.replace(/[\^~>=<]/g, "").split(".");

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 version comparison strips all range specifiers and compares only major version strings, which silently passes a required: ">=2.0.0" against installed: "1.5.0" as compatible because both become "1" after splitting on .. It also ignores pre-release and patch constraints.

💡 Suggestion

Since this is illustrative sample code, add a comment noting the intentional simplification:

// Simplified: compares only the leading major version digit; does not handle pre-release or complex ranges
const [reqMajor] = required.replace(/[^\d.]/g, "").split(".");

This prevents readers from copy-pasting the logic into production expecting full semver compliance.

const [instMajor] = installed.replace(/[\^~>=<]/g, "").split(".");
if (reqMajor !== instMajor) {
return { conflicting: true, reason: `Major version mismatch: required ${required}, installed ${installed}`, severity: "error" as const };
}
return { conflicting: false, reason: "Compatible", severity: "ok" as const };
},
});

// Agent role: check npm peer dependency conflicts in the current project.
const npmPeerDepChecker = agent({
model: "small",
output: s.object({
conflicts: s.array(s.object({
package: s.string,
reason: s.string,
severity: s.enum("error", "warning", "ok"),
})),
totalConflicts: s.int,
hasErrors: s.boolean,
hasPeerDeps: s.boolean,
}),
instructions: p`Review ${p.read("package.json")} and run ${p.bash("npm ls --json 2>&1 || true")} to identify peer dependency conflicts. For each peerDependency call checkPeerConflict with the package name, required version, and installed version. Return the list of conflicts, totalConflicts, hasErrors, and hasPeerDeps.`,
tools: [checkPeerConflict],
addons: [repair()],
});

export default npmPeerDepChecker;
```
40 changes: 40 additions & 0 deletions skills/rig/samples/433-ts-barrel-module-writer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# ts-barrel-module-writer - TypeScript Barrel Module Writer

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

const validateExportName = defineTool("validateExportName", {
description: "Validate that a name is a valid TypeScript identifier for export.",
parameters: s.object({
name: s.string,
}),
handler: async ({ name }) => {
const valid = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
return {
valid,
reason: valid ? undefined : `"${name}" is not a valid TypeScript identifier`,
};
},
});

// Agent role: write a TypeScript barrel module file exporting named members.
const tsBarrelModuleWriter = agent({
model: "small",
input: s.object({
targetFile: s.path,
moduleName: s.string,
exports: s.array(s.string),
}),
output: s.object({
outputFile: s.path,
exportsWritten: s.array(s.string),
moduleLines: s.int,
success: s.boolean,
}),
instructions: p`You are writing a TypeScript barrel module. For each name in input.exports, call validateExportName to verify it is a valid identifier. Then write a barrel module to ${p.writeOutput("outputFile", "targetFile")} that re-exports each valid name. The module should contain one export line per valid identifier: \`export { name } from "./name";\`. Return outputFile, exportsWritten (the valid exports written), moduleLines (total lines in the file), and success (true if at least one export was written).`,
tools: [validateExportName],
addons: [repair()],
});

export default tsBarrelModuleWriter;
```
35 changes: 35 additions & 0 deletions skills/rig/samples/434-git-remote-metadata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# git-remote-metadata - Git Remote Metadata Inspector

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

const classifyRemote = defineTool("classifyRemote", {
description: "Classify a git remote URL by hosting provider.",
parameters: s.object({
name: s.string,
url: s.string,
}),
handler: async ({ url }) => {
if (/github\.com/i.test(url)) return { type: "github" as const };
if (/gitlab\.com/i.test(url)) return { type: "gitlab" as const };
if (/bitbucket\.org/i.test(url)) return { type: "bitbucket" as const };
return { type: "other" as const };
},
});

// Agent role: inspect git remotes and report their URLs, types, and branch counts.
const gitRemoteMetadataInspector = agent({
model: "small",
output: s.record(s.object({
url: s.string,
type: s.enum("github", "gitlab", "bitbucket", "other"),
branchCount: s.int,
})),
instructions: p`List git remotes with ${p.bash("git remote -v")} then for each unique remote call classifyRemote with its name and fetch URL. Count remote branches with ${p.bash("git ls-remote --heads --quiet 2>/dev/null | wc -l")}. Return a record keyed by remote name containing url, type, and branchCount.`,

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] branchCount is fetched once with a single git ls-remote --heads call that returns the total across all remotes, then the same integer is assigned to every remote in the output record. If there are multiple remotes the counts will all be identical and misleading.

💡 Suggested fix

Scope the branch count per remote:

git ls-remote --heads <remote-name> 2>/dev/null | wc -l

Update the instructions to pass each remote's name to the count command, or document the limitation with a comment:

// Note: branchCount reflects total refs from all remotes, not per-remote

tools: [classifyRemote],
addons: [steering()],
maxTurns: 3,
});

export default gitRemoteMetadataInspector;
```
50 changes: 50 additions & 0 deletions skills/rig/samples/435-os-env-scanner.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# os-env-scanner - OS Environment Variable Scanner

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

const classifyEnvVar = defineTool("classifyEnvVar", {
description: "Classify an OS environment variable by category.",
parameters: s.object({
name: s.string,
value: s.string,
}),
handler: async ({ name }) => {
if (/^(PATH|LD_LIBRARY_PATH|DYLD_LIBRARY_PATH|MANPATH|PKG_CONFIG_PATH)$/i.test(name)) {
return { category: "path" as const };
}
if (/^(LANG|LANGUAGE|LC_\w+)$/i.test(name)) {
return { category: "locale" as const };
}
if (/^(HOME|USER|LOGNAME|SHELL|USERNAME)$/i.test(name)) {
return { category: "home" as const };
}
if (/^(EDITOR|VISUAL|PAGER|BROWSER)$/i.test(name)) {
return { category: "editor" as const };
}
if (/^(CI|GITHUB_|JENKINS_|TRAVIS|CIRCLECI|GITLAB_CI|BUILD_|RUNNER_)/i.test(name)) {
return { category: "ci" as const };
}
return { category: "custom" as const };
},
});

// Agent role: scan OS environment variables and categorize them.
const osEnvScanner = agent({
model: "small",
output: s.object({
vars: s.record(s.object({
value: s.string,
category: s.enum("path", "locale", "home", "editor", "ci", "custom"),
})),
totalVars: s.int,
ciEnvCount: s.int,
customCount: s.int,
}),
instructions: p`List all environment variables with ${p.bash("env")}. For each variable call classifyEnvVar with its name and value. Return a vars record keyed by variable name, plus totalVars, ciEnvCount (count of ci-category vars), and customCount (count of custom-category vars).`,
tools: [classifyEnvVar],
addons: [repair()],
});

export default osEnvScanner;
```
60 changes: 60 additions & 0 deletions skills/rig/samples/436-sequential-commit-pipeline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# sequential-commit-pipeline - Sequential Commit Pipeline

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

// Agent role: collect recent git commits as structured data.
const commitCollector = agent({
model: "small",
output: s.object({
commits: s.array(s.object({ hash: s.string, message: s.string })),
}),
instructions: p`Run ${p.bash("git log --oneline -20")} and parse each line into hash and message. Return the commits array.`,
});

// Agent role: classify each commit by conventional commit type.
const commitClassifier = agent({
model: "small",
input: s.object({
commits: s.array(s.object({ hash: s.string, message: s.string })),
}),
output: s.object({
classified: s.array(s.object({
hash: s.string,
message: s.string,
type: s.enum("feat", "fix", "chore", "docs", "other"),
})),
}),
instructions: `Classify each commit by its conventional commit type prefix. feat → "feat", fix → "fix", chore/build/ci/refactor/test/style/perf → "chore", docs → "docs", everything else → "other". Return classified array.`,
});

// Agent role: aggregate classified commits into a summary record.
const commitAggregator = agent({
model: "small",
input: s.object({
classified: s.array(s.object({
hash: s.string,
message: s.string,
type: s.enum("feat", "fix", "chore", "docs", "other"),
})),
}),
output: s.object({
summary: s.record(s.int),
totalCommits: s.int,
topCategory: s.string,
}),
instructions: `Count the number of commits per type. Return summary as a record of type→count, totalCommits, and topCategory (the type with the highest count).`,
});

// Workflow role: pipeline git commits through collection, classification, and aggregation.
export default workflow({
meta: { name: "sequential-commit-pipeline", description: "Collect, classify, and aggregate git commits in sequence." },
body: async ({ call }) => {
const r1 = await call(commitCollector, "Collect recent commits.");
if (!r1) return null;
const r2 = await call(commitClassifier, { commits: r1.commits });
if (!r2) return null;
return call(commitAggregator, r2);
},
});
```
48 changes: 48 additions & 0 deletions skills/rig/samples/437-ts-tuple-pattern-extractor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# ts-tuple-pattern-extractor - TypeScript Tuple Pattern Extractor

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

const extractTuplePatternsFromFile = defineTool("extractTuplePatternsFromFile", {
description: "Count tuple type patterns in a TypeScript file.",
parameters: s.object({
filePath: s.string,
}),
handler: async ({ filePath }) => {
const { readFile } = await import("node:fs/promises");
const content = await readFile(filePath, "utf8");
const tuplePattern = /:\s*\[[\w\s,.|&?<>]+\]/g;
const readonlyPattern = /readonly\s*\[[\w\s,.|&?<>\.\.\.]+\]/g;
const spreadPattern = /\.\.\.\w+/g;

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 tuplePattern regex :/\s*\[[\w\s,.|&?<>]+\]/g also matches array access and type annotations that are not tuples (e.g. const x: number[] = ... would not match, but : [string] on a variable with a non-tuple array type would). The comment says "Count tuple type patterns" but the regex is ambiguous about what qualifies as a tuple vs a single-element array type annotation.

💡 Suggestion

Add a brief comment in the sample clarifying the intentional approximation:

// Approximation: matches `: [...]` type annotations; some false positives expected
const tuplePattern = /:\s*\[[\w\s,.|&?<>]+\]/g;

Since this is a sample, being explicit about tradeoffs teaches good practice.

const tuples: string[] = content.match(tuplePattern) ?? [];
const readonlyTuples: string[] = content.match(readonlyPattern) ?? [];
const allTuples = tuples.concat(readonlyTuples);
const hasSpreads = allTuples.some((t) => spreadPattern.test(t));

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.

[/diagnosing-bugs] spreadPattern uses the g (global) flag and is reused across .some() iterations — after the first positive .test() call, lastIndex advances and subsequent calls may miss matches or produce false results.

💡 Suggested fix

Drop the g flag (unnecessary for a boolean check):

const spreadPattern = /\.\.\.\w+/;
const hasSpreads = allTuples.some((t) => spreadPattern.test(t));

As a sample, this currently teaches a common silent bug. Removing g fixes the behaviour and makes the intent clear.

return {
tupleCount: allTuples.length,
hasSpreads,
readonlyCount: readonlyTuples.length,
};
},
});

// Agent role: scan TypeScript source files for tuple type patterns and report statistics.
const tsTuplePatternExtractor = agent({
model: "small",
output: s.object({
files: s.record(s.object({
tupleCount: s.int,
hasSpreads: s.boolean,
readonlyCount: s.int,
})),
totalTuples: s.int,
totalFiles: s.int,
mostTupledFile: s.optional(s.string),
}),
instructions: p`Find TypeScript files with ${p.glob("src/**/*.ts")}. For each file call extractTuplePatternsFromFile. Return a files record keyed by path, totalTuples (sum of all tupleCount), totalFiles, and mostTupledFile (path with highest tupleCount, or omit if none found).`,
tools: [extractTuplePatternsFromFile],
addons: [repair()],
});

export default tsTuplePatternExtractor;
```
40 changes: 40 additions & 0 deletions skills/rig/samples/438-ts-interface-stub-writer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# ts-interface-stub-writer - TypeScript Interface Stub Writer

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

const validateMethodSignature = defineTool("validateMethodSignature", {
description: "Validate a TypeScript method signature string.",
parameters: s.object({
signature: s.string,
}),
handler: async ({ signature }) => {
const valid = /^\s*\w+\s*\([^)]*\)\s*:\s*\S+\s*$/.test(signature);
return {
valid,
reason: valid ? undefined : `"${signature}" is not a valid TypeScript method signature (expected: name(args): ReturnType)`,
};
},
});

// Agent role: generate a TypeScript interface stub file from a name and method list.
const tsInterfaceStubWriter = agent({
model: "small",
input: s.object({
interfaceName: s.string,
methods: s.array(s.string),
outputFile: s.path,
}),
output: s.object({
outputFile: s.path,
methodsWritten: s.array(s.string),
isValid: s.boolean,
linesEmitted: s.int,
}),
instructions: p`For each method signature in input.methods, call validateMethodSignature to check it is valid. Collect only the valid method signatures. Write a TypeScript interface file to ${p.writeOutput("outputFile", "outputFile")} with the format:\n\nexport interface InterfaceName {\n method1(args): ReturnType;\n ...\n}\n\nReturn outputFile, methodsWritten (the valid signatures included), isValid (true if all input methods were valid), and linesEmitted (total lines written).`,
tools: [validateMethodSignature],
addons: [repair()],
});

export default tsInterfaceStubWriter;
```
Loading
Loading