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
53 changes: 53 additions & 0 deletions skills/rig/samples/260-git-submodule-health.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# 260 - Git Submodule Health

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

const parseSubmoduleStatus = defineTool("parseSubmoduleStatus", {
description: "Parse a git submodule status line into structured fields.",
parameters: s.object({ line: s.string }),
handler({ line }) {
const statusChar = line[0] ?? " ";
const rest = line.slice(1).trim();
const parts = rest.split(/\s+/);
const sha = parts[0] ?? "";
const path = parts[1] ?? "";
let status: "clean" | "modified" | "uninitialized" | "conflict" = "clean";
if (statusChar === "+") status = "modified";
else if (statusChar === "-") status = "uninitialized";
else if (statusChar === "U") status = "conflict";
const describeMatch = rest.match(/\(([^)]+)\)$/);
return { path, sha, status, describe: describeMatch?.[1] };
},
});

// Agent role: Inventory git submodules and report their health status.
const gitSubmoduleHealth = agent({
model: "small",
instructions: p`Check the health of all git submodules.

Submodule status:
${p.bash("git submodule status 2>/dev/null || echo '(no submodules)'")}

Submodule config:
${p.readOptional(".gitmodules", "(no .gitmodules file)")}

For each non-empty status line, call parseSubmoduleStatus to extract path, sha, status, and optional describe.
Return submodules array, allClean (true if every status is "clean"), and totalCount.`,
tools: [parseSubmoduleStatus],
output: s.object({
submodules: s.array(
s.object({
path: s.path,
sha: s.string,
status: s.enum("clean", "modified", "uninitialized", "conflict"),
describe: s.optional(s.string),
})
),
allClean: s.boolean,
totalCount: s.int,
}),
});

export default gitSubmoduleHealth;
```
49 changes: 49 additions & 0 deletions skills/rig/samples/261-ts-decorator-scanner.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# 261 - TypeScript Decorator Scanner

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

const classifyDecorator = defineTool("classifyDecorator", {
description: "Classify a TypeScript decorator by its name into a usage type.",
parameters: s.object({ name: s.string }),
handler({ name }) {
const lower = name.toLowerCase();
if (/^(component|module|injectable|controller|service|directive|pipe)$/.test(lower))
return { type: "class" };
if (/^(get|post|put|delete|patch|route|httpcode|header|body|query|param)$/.test(lower))
return { type: "method" };
if (/^(column|primarycolumn|primarygeneratedcolumn|onetomany|manytoone|manyto)/.test(lower))
return { type: "property" };
if (/^(param|body|query|request|response|headers|ip|session|uploadedfile)$/.test(lower))

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 for property decorators (manyto suffix) has a typo — it tests for manyto but the actual TypeORM decorators are ManyToMany and ManyToOne. The pattern /^(column|...|manyto)/ will never match ManyToMany or ManyToOne because they end in many/one, not manyto.

💡 Fix the pattern
if (/^(column|primarycolumn|primarygeneratedcolumn|onetomany|manytoone|manytomany)/.test(lower))
  return { type: "property" };

As a sample demonstrating defineTool, the classification table is the core of the example — having a subtle bug here undermines its value as reference material.

return { type: "parameter" };
return { type: "unknown" };
},
});

// Agent role: Scan TypeScript files for decorator usage and classify each decorator.
const tsDecoratorScanner = agent({
model: "small",
instructions: p`Scan for TypeScript decorator patterns in the repository.

Decorator occurrences:
${p.bash("grep -rn '@[A-Z][A-Za-z]*' --include='*.ts' . 2>/dev/null | head -60 || echo 'none found'")}

For each unique decorator name found, call classifyDecorator to get its type.
Build a record keyed by decorator name (without @) with count and type.
Set hasExperimental to true if any decorators from experimental frameworks (e.g., legacy Angular, old NestJS) are found.
Return decorators record, totalCount (sum of all counts), and hasExperimental.`,
tools: [classifyDecorator],
output: s.object({
decorators: s.record(
s.object({
count: s.int,
type: s.enum("class", "method", "property", "parameter", "unknown"),
})
),
totalCount: s.int,
hasExperimental: s.boolean,
}),
});

export default tsDecoratorScanner;
```
52 changes: 52 additions & 0 deletions skills/rig/samples/262-dotfile-inventory.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# 262 - Dotfile Inventory

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

const categorizeDotfile = defineTool("categorizeDotfile", {
description: "Categorize a dotfile by its filename into a purpose category.",
parameters: s.object({ filename: s.string }),
handler({ filename }) {
const name = filename.replace(/^\./, "").toLowerCase();
if (/^(bash|zsh|fish|profile|bashrc|zshrc|bash_profile|zprofile|inputrc)/.test(name))
return { category: "shell" };
if (/^(vimrc|vim|nvim|emacs|nano|editorconfig|prettierrc|eslintrc)/.test(name))
return { category: "editor" };
if (/^(gitconfig|gitignore|gitattributes|gitmessage|git)/.test(name))
return { category: "git" };
if (/^(ssh|known_hosts|authorized_keys)/.test(name))
return { category: "ssh" };
if (/^(npmrc|yarnrc|pnpmfile|curlrc|wgetrc|tmux|screenrc|docker)/.test(name))
return { category: "tool" };
return { category: "other" };
},
});

// Agent role: Inventory dotfiles in the home directory and categorize each one.
const dotfileInventory = agent({
model: "small",
addons: repair(),
instructions: p`Inventory dotfiles in the home directory and categorize each.

Dotfiles found:
${p.bash("find ~ -maxdepth 1 -name '.*' -type f 2>/dev/null | head -40 || echo '(none found)'")}

For each dotfile found, call categorizeDotfile with its filename (basename).
Build a record keyed by filename with category and a short purpose description.
Also compute categorySummary as a record of category → count of dotfiles in that category.
Return dotfiles record, totalFound (integer count), and categorySummary.`,
tools: [categorizeDotfile],
output: s.object({
dotfiles: s.record(
s.object({
category: s.enum("shell", "editor", "git", "ssh", "tool", "other"),
purpose: s.string,
})
),
totalFound: s.int,
categorySummary: s.record(s.int),
}),
});

export default dotfileInventory;
```
63 changes: 63 additions & 0 deletions skills/rig/samples/263-regex-pattern-tester.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# 263 - Regex Pattern Tester

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

// Agent role: Run regex patterns against test cases and report pass/fail results.
const regexPatternTester = agent({
model: "small",
addons: repair(),
input: s.object({
patterns: s.array(
s.object({
name: s.string,
regex: s.string,
testCases: s.array(
s.object({
input: s.string,
shouldMatch: s.boolean,
})
),
})
),
}),
instructions: p`Run each regex pattern against its test cases and report results.

Input patterns and test cases:
${p.json("input")}

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.json("input") is incorrect here — p.json takes a JS value and serializes it, so passing the string "input" embeds the literal JSON string "\"input\"" in the prompt, not the agent's runtime input.

💡 How to reference structured input

The input: schema declared on the agent makes the input available to the model at runtime through the framework's own injection. You do not need to explicitly embed it via p.json. Instead, just describe how to use it in the instructions prose:

instructions: p`Run each regex pattern against its test cases.

Patterns and test cases are provided in the input above.
Use the runRegexTest tool for each pattern+testCase combination.`,

If you do need to embed a static value, p.json(someVariable) works — but a string literal "input" is not the right argument.


Use the runRegexTest tool for each pattern+testCase combination.
For each pattern, build a result with name, passed count, failed count, and allPassed.
Return results array, totalPatterns, passCount (patterns where allPassed=true),
failCount (patterns where allPassed=false), and overall allPassed.`,
tools: [
defineTool("runRegexTest", {
description: "Test a regex pattern against an input string and return whether it matches.",
parameters: s.object({ regex: s.string, input: s.string }),
handler({ regex, input }) {
try {
return { matched: new RegExp(regex).test(input) };
} catch (e) {
return { matched: false, error: String(e) };
}
},
}),
],
output: s.object({
results: s.array(
s.object({
name: s.string,
passed: s.int,
failed: s.int,
allPassed: s.boolean,
})
),
totalPatterns: s.int,
passCount: s.int,
failCount: s.int,
allPassed: s.boolean,
}),
});

export default regexPatternTester;
```
31 changes: 31 additions & 0 deletions skills/rig/samples/264-commit-churn-classifier.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# 264 - Commit Churn Classifier

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

// Agent role: classify repository files by commit churn frequency and assign a risk level.
const commitChurnClassifier = agent({
model: "small",
addons: steering({ message: "Assign riskLevel based on churnCount: >20=critical, >10=volatile, >5=active, else stable. Only include files from the churn output." }),

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 steering() addon duplicates the risk-level classification logic that is already spelled out in the instructions text. Having two conflicting sources of truth (the prose in instructions vs the steering message) can confuse the model and cause inconsistent output.

💡 Pick one canonical source

Either put the thresholds in instructions (and drop the steering addon), or put them in steering alone and simplify the instructions. The steering() addon is most useful for behavioural nudges not already described in the instructions, not for repeating the same rule twice:

// Option A: instructions only (drop steering addon entirely)
instructions: p`...Assign riskLevel: critical >20, volatile >10, active >5, stable <=5.`,

// Option B: steering only (keep instructions brief)
addons: steering({ message: "riskLevel thresholds: >20=critical, >10=volatile, >5=active, <=5=stable." }),
instructions: p`Classify repository files by commit churn. Assign a riskLevel per file.`,

instructions: p`Classify repository files by commit churn (how frequently they change).

File churn counts from git history:
${p.bash("git log --name-only --format='' 2>/dev/null | grep -v '^$' | sort | uniq -c | sort -rn | head -30")}

For each file in the output, parse the churn count and assign a riskLevel:
- critical: churnCount > 20
- volatile: churnCount > 10
- active: churnCount > 5
- stable: churnCount <= 5

Return a record keyed by file path with churnCount (integer) and riskLevel.`,
output: s.record(
s.object({
churnCount: s.int,
riskLevel: s.enum("stable", "active", "volatile", "critical"),
})
),
});

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

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

// Agent role: estimate npm package publish size and rate it with a recommendation.
const npmPackageSize = agent({
model: "small",
instructions: p`Estimate the npm package publish size and rate it.

Files included in npm publish (dry run):
${p.bash("npm pack --dry-run 2>&1 | head -40")}

Workspace size:
${p.bash("du -sh . 2>/dev/null | cut -f1")}

Package metadata:
${p.readOptional("package.json", "{}")}

Parse the npm pack output to extract individual file sizes. Use classifySize on the total.
List the top files by size. Provide a recommendation if the package is large.
Return estimatedSizeKb, topFiles (up to 5), sizeRating, and recommendation.`,
tools: [
defineTool("classifySize", {
description: "Classify a package size in KB into a rating tier.",
parameters: s.object({ sizeKb: s.number }),
handler({ sizeKb }) {
if (sizeKb < 10) return { rating: "tiny" };
if (sizeKb < 100) return { rating: "small" };
if (sizeKb < 1000) return { rating: "medium" };
if (sizeKb < 10000) return { rating: "large" };
return { rating: "xlarge" };
},
}),
],
output: s.object({
estimatedSizeKb: s.number,
topFiles: s.array(s.object({ path: s.path, sizeKb: s.number })),
sizeRating: s.enum("tiny", "small", "medium", "large", "xlarge"),
recommendation: s.string,
}),
});

export default npmPackageSize;
```
56 changes: 56 additions & 0 deletions skills/rig/samples/266-tsconfig-option-analyzer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# 266 - TSConfig Option Analyzer

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

const parseTsConfigOption = defineTool("parseTsConfigOption", {
description: "Classify a tsconfig compilerOption by name and infer if it is recommended.",
parameters: s.object({ optionName: s.string }),
handler({ optionName }) {
const strictOptions = new Set(["strict", "noImplicitAny", "strictNullChecks", "strictFunctionTypes", "strictBindCallApply", "strictPropertyInitialization", "noImplicitThis", "alwaysStrict", "useUnknownInCatchVariables", "exactOptionalPropertyTypes", "noUncheckedIndexedAccess"]);
const perfOptions = new Set(["incremental", "composite", "tsBuildInfoFile", "isolatedModules", "skipLibCheck", "skipDefaultLibCheck"]);
const outputOptions = new Set(["outDir", "outFile", "rootDir", "declarationDir", "declaration", "declarationMap", "sourceMap", "inlineSources", "inlineSourceMap", "emitDeclarationOnly", "noEmit"]);
const pathOptions = new Set(["paths", "baseUrl", "rootDirs", "typeRoots", "types"]);
if (strictOptions.has(optionName)) return { category: "strict", recommended: true };
if (perfOptions.has(optionName)) return { category: "perf", recommended: true };
if (outputOptions.has(optionName)) return { category: "output", recommended: false };
if (pathOptions.has(optionName)) return { category: "paths", recommended: false };
return { category: "misc", recommended: false };
},
});

// Agent role: analyze tsconfig.json compiler options and classify each one.
const tsconfigOptionAnalyzer = agent({
model: "small",
addons: repair(),
instructions: p`Analyze TypeScript compiler options from tsconfig files in this project.

Main tsconfig.json:
${p.read("tsconfig.json")}

Additional tsconfig files found:
${p.glob("tsconfig.*.json")}

For each compilerOption key found in the tsconfig files, call parseTsConfigOption to get
its category and recommended status.
Build an options record keyed by option name with value (from config), category, and recommended.
Count strictCount (number of strict-category options enabled).
Set hasIsolatedModules to true if isolatedModules is set to true.
List all config file paths found in configFilesFound.`,
tools: [parseTsConfigOption],
output: s.object({
options: s.record(
s.object({
value: s.unknown,
category: s.enum("strict", "perf", "output", "paths", "misc"),
recommended: s.boolean,
})
),
strictCount: s.int,
hasIsolatedModules: s.boolean,
configFilesFound: s.array(s.string),
}),
});

export default tsconfigOptionAnalyzer;
```
Loading
Loading