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
36 changes: 36 additions & 0 deletions skills/rig/samples/68-changelog-generator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# 68 - Changelog Generator

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

const validateSemver = defineTool("validateSemver", {
description: "Validate that a bump type is major, minor, or patch",
parameters: s.object({ bump: s.string }),
handler({ bump }) {
const valid = ["major", "minor", "patch"].includes(bump);
return { valid };
},
});

// Agent role: read recent git changes and produce a structured changelog entry with semver bump classification.
const changelogGenerator = agent({

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] All 10 new samples use model: "small", but the SKILL.md construction rules say to use "large", "mini", or "nano" in examples, and the two immediately preceding samples (66, 67) both use "mini". Using "small" drifts from the documented vocabulary and could confuse readers learning from these samples.

Consider updating all new samples to "mini" (or "nano" for the subagent delegates) to stay consistent with the project's example conventions.

model: "small",
instructions: p`Review ${p.bash("git diff HEAD~1 HEAD --stat")} and ${p.bash("git log HEAD~1..HEAD --oneline")} to produce a changelog entry. Classify each change by category and determine the semver bump type. Use the validateSemver tool to confirm the bump value. Write the markdown changelog to CHANGELOG.md via ${p.write("CHANGELOG.md", "<!-- changelog -->")}`,
output: s.object({

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.write("CHANGELOG.md", "<!-- changelog -->") writes a static placeholder, not the LLM-generated markdown field — the file will always contain the literal string <!-- changelog -->.

💡 Fix: use `p.writeOutput`

Replace the write intent so the generated markdown field is persisted:

instructions: p`...via ${p.writeOutput("markdown", "CHANGELOG.md")}`,

p.write(path, content) injects a static string; only p.writeOutput(field, path) writes a generated output field to disk after the run completes.

version: s.string,
bump: s.enum("major", "minor", "patch"),
entries: s.array(s.object({
category: s.enum("feat", "fix", "chore", "docs", "refactor"),
description: s.string,
})),
markdown: s.string,
}),
tools: [validateSemver],
maxTurns: 6,
addons: repair(),
});

export default changelogGenerator;

```
24 changes: 24 additions & 0 deletions skills/rig/samples/69-todo-comment-tracker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# 69 - Todo Comment Tracker

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

// Agent role: scan source files for TODO/FIXME/HACK comments and produce a structured report.
const todoCommentTracker = agent({
model: "small",
instructions: p`Scan source files using ${p.bash("grep -rn 'TODO\\|FIXME\\|HACK' --include='*.ts' . 2>/dev/null || true")} and produce a structured list of all found comments. Write a markdown report to todo-report.md via ${p.write("todo-report.md", "<!-- report -->")}`,

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] Two issues on this line:

  1. p.write("todo-report.md", "<!-- report -->") writes a static placeholder — the generated markdown field is never persisted. Use p.writeOutput("markdown", "todo-report.md") instead.

  2. The backslash \| alternation in a p.bash string goes through TypeScript string escaping before reaching the shell. The regex may not behave as intended. Use the tagged-template form p.bashRaw`grep -rn 'TODO\|FIXME\|HACK' ...` to pass the command verbatim.

💡 Suggested fix
instructions: p`Scan source files using ${p.bashRaw`grep -rn 'TODO\|FIXME\|HACK' --include='*.ts' . 2>/dev/null || true`} and produce a structured list. Write a markdown report via ${p.writeOutput("markdown", "todo-report.md")}`,

output: s.object({
items: s.array(s.object({
file: s.path,
line: s.int,
kind: s.enum("TODO", "FIXME", "HACK"),
message: s.string,
})),
totalCount: s.int,
markdown: s.string,
}),
});

export default todoCommentTracker;

```
25 changes: 25 additions & 0 deletions skills/rig/samples/70-multi-file-subagent-summarizer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# 70 - Multi File Subagent Summarizer

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

// Agent role: summarize a single TypeScript file.
const fileSummarizer = agent({
name: "fileSummarizer",
model: "nano",
input: s.object({ filePath: s.path }),
instructions: p`Summarize the TypeScript file at ${p.readInput("filePath")} in one concise sentence.`,
output: s.object({ summary: s.string }),
});

// Agent role: find TypeScript source files and delegate to fileSummarizer to summarize each one, then aggregate results.
const multiFileSummarizer = agent({
model: "small",
instructions: p`Find TypeScript files using ${p.bash("find src -name '*.ts' -not -path '*/node_modules/*' 2>/dev/null | head -10 || echo 'no files'")} then delegate each file path to the fileSummarizer subagent and collect summaries keyed by file path.`,
output: s.record(s.string),

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.bash("find src -name '*.ts' ...") for file discovery can be replaced with p.glob("src/**/*.ts"), which is the idiomatic rig pattern (see sample 67 directly above this one).

💡 Suggested fix
instructions: p`Find TypeScript files: ${p.glob("src/**/*.ts")}. For each path delegate to fileSummarizer and collect summaries keyed by file path.`,

p.glob handles the discovery as a declarative intent, keeping the instructions cleaner and consistent with the project's established style.

agents: { fileSummarizer },
});

export default multiFileSummarizer;

```
36 changes: 36 additions & 0 deletions skills/rig/samples/71-package-script-health.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# 71 - Package Script Health

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

const validateScriptName = defineTool("validateScriptName", {
description: "Validate that a package.json script name follows conventional naming (lowercase, hyphens only)",
parameters: s.object({ name: s.string }),
handler({ name }) {
const valid = /^[a-z][a-z0-9:-]*$/.test(name);
const reason = valid ? "Name is conventional" : "Name should be lowercase with hyphens/colons only";
return { valid, reason };
},
});

// Agent role: analyze package.json scripts for naming and structural health issues.
const packageScriptHealth = agent({
model: "small",
instructions: p`Read ${p.read("package.json")} and analyze all scripts entries. Use the validateScriptName tool for each script name. Classify each issue by severity and determine overall health.`,
output: s.object({
issues: s.array(s.object({
script: s.string,
issue: s.string,
status: s.enum("error", "warning", "ok"),
})),
overallHealth: s.enum("healthy", "degraded", "critical"),
}),
tools: [validateScriptName],
maxTurns: 6,
addons: repair(),
});

export default packageScriptHealth;

```
37 changes: 37 additions & 0 deletions skills/rig/samples/72-ts-function-signatures.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# 72 - Ts Function Signatures

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

const parseSignatures = defineTool("parseSignatures", {
description: "Extract function signatures from TypeScript source content using regex",
parameters: s.object({ content: s.string }),
handler({ content }) {
const pattern = /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/g;
const functions: { name: string; paramCount: number; isExported: boolean }[] = [];
let match;
while ((match = pattern.exec(content)) !== null) {
const [full, name, params] = match;
const paramCount = params.trim() === "" ? 0 : params.split(",").length;
const isExported = full.trimStart().startsWith("export");
functions.push({ name, paramCount, isExported });
}
return { functions };
},
});

// Agent role: extract TypeScript function signatures from source files and return them keyed by file path.
const tsFunctionSignatures = agent({
model: "small",
instructions: p`Find TypeScript files using ${p.bash("find src -name '*.ts' 2>/dev/null | head -5 || echo 'no files'")} then read each file and use the parseSignatures tool to extract function signatures. Return results keyed by file path.`,
output: s.record(s.array(s.object({

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] File discovery uses p.bash("find src -name '*.ts' ...") — prefer p.glob("src/**/*.ts") (the established rig pattern) over a shell find command for workspace file discovery.

💡 Suggested fix
instructions: p`Find TypeScript files: ${p.glob("src/**/*.ts")}. Read each file and use the parseSignatures tool to extract function signatures. Return results keyed by file path.`,

name: s.string,
paramCount: s.int,
isExported: s.boolean,
}))),
tools: [parseSignatures],
});

export default tsFunctionSignatures;

```
27 changes: 27 additions & 0 deletions skills/rig/samples/73-git-branch-pruner.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# 73 - Git Branch Pruner

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

// Agent role: identify git branches that are candidates for pruning based on merge status and last commit date.
const gitBranchPruner = agent({
model: "small",
instructions: p`Analyze git branches using ${p.bash("git branch --merged HEAD 2>/dev/null || echo 'no branches'")} and ${p.bash("git for-each-ref --format='%(refname:short) %(committerdate:short)' refs/heads/ 2>/dev/null || echo 'no refs'")}. For each branch determine if it should be pruned (merged and not main/master/develop) or kept. Exclude the current branch from prune candidates.`,
output: s.object({
candidates: s.array(s.object({
branch: s.string,
lastCommitDate: s.optional(s.string),
action: s.enum("keep", "prune"),
reason: s.string,
})),
totalBranches: s.int,
pruneCount: s.int,
}),
maxTurns: 6,
addons: repair(),
});

export default gitBranchPruner;

```
50 changes: 50 additions & 0 deletions skills/rig/samples/74-prettier-eslint-compat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# 74 - Prettier Eslint Compat

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

const detectConflicts = defineTool("detectConflicts", {
description: "Detect rule conflicts between Prettier and ESLint configs",
parameters: s.object({ prettierConfig: s.string, eslintConfig: s.string }),
handler({ prettierConfig, eslintConfig }) {

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 detectConflicts handler declares const p (line 10: const p = JSON.parse(...)) which shadows the p import from "rig" at module scope. This creates a confusing name collision in the sample — readers will associate p with rig's prompt-intent builder, not a parsed config object.

💡 Suggested fix

Rename the local variable to avoid the shadow:

const prettier = JSON.parse(prettierConfig || "{}");
const eslint   = JSON.parse(eslintConfig  || "{}");
if (prettier.printWidth && eslint.rules?.["max-len"]) { ... }

const conflicts: { rule: string; prettier: string; eslint: string; fixable: boolean }[] = [];
try {
const p = JSON.parse(prettierConfig || "{}");
const e = JSON.parse(eslintConfig || "{}");
const rules = (e.rules || {});
if (p.printWidth && rules["max-len"]) {
conflicts.push({ rule: "line-length", prettier: `printWidth: ${p.printWidth}`, eslint: `max-len: ${JSON.stringify(rules["max-len"])}`, fixable: true });
}
if (p.singleQuote !== undefined && rules["quotes"]) {
conflicts.push({ rule: "quotes", prettier: `singleQuote: ${p.singleQuote}`, eslint: `quotes: ${JSON.stringify(rules["quotes"])}`, fixable: true });
}
} catch {
// invalid JSON — model will handle
}
return { conflicts };
},
});

// Agent role: check Prettier and ESLint configs for rule conflicts and report compatibility issues.
const prettierEslintCompat = agent({
model: "small",
instructions: p`Read the Prettier config ${p.readOptional(".prettierrc")} and ESLint config ${p.readOptional(".eslintrc.json")} then use the detectConflicts tool to find rule conflicts. Classify each conflict by severity.`,
output: s.object({
conflicts: s.array(s.object({
rule: s.string,
prettier: s.string,
eslint: s.string,
fixable: s.boolean,
severity: s.enum("error", "warning", "info"),
})),
compatible: s.boolean,
}),
tools: [detectConflicts],
maxTurns: 6,
addons: repair(),
});

export default prettierEslintCompat;

```
40 changes: 40 additions & 0 deletions skills/rig/samples/75-workflow-validator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# 75 - Workflow Validator

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

// Agent role: analyze a single GitHub Actions workflow file for structural issues and job count.
const workflowAnalyzer = agent({
name: "workflowAnalyzer",
model: "nano",
input: s.object({ filePath: s.path }),
instructions: p`Analyze the GitHub Actions workflow file at ${p.readInput("filePath")} for issues such as missing permissions, deprecated actions, hardcoded secrets, or missing timeout-minutes. Count the number of jobs defined.`,
output: s.object({
issues: s.array(s.object({
step: s.string,
problem: s.string,
severity: s.enum("error", "warning", "info"),
})),
jobCount: s.int,
}),
});

// Agent role: find all GitHub Actions workflow files and delegate analysis to workflowAnalyzer subagent, then aggregate results.
const workflowValidator = agent({
model: "small",
instructions: p`Find GitHub Actions workflow files using ${p.bash("find .github/workflows -name '*.yml' -o -name '*.yaml' 2>/dev/null | head -10 || echo 'no workflows'")} then delegate each file to the workflowAnalyzer subagent. Aggregate results keyed by filename, adding a pass/warn/fail status based on issue severity.`,
output: s.record(s.object({
issues: s.array(s.object({
step: s.string,
problem: s.string,
severity: s.enum("error", "warning", "info"),
})),
jobCount: s.int,
status: s.enum("pass", "warn", "fail"),
})),
agents: { workflowAnalyzer },
});

export default workflowValidator;

```
26 changes: 26 additions & 0 deletions skills/rig/samples/76-commit-msg-rewriter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# 76 - Commit Msg Rewriter

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

// Agent role: rewrite recent git commit messages into conventional commit format with imperative mood.
const commitMsgRewriter = agent({
model: "small",
instructions: p`Review recent commits from ${p.bash("git log --oneline -20 2>/dev/null || echo 'no commits'")} and rewrite each message in conventional commit format (feat:/fix:/chore:/docs:/test:/refactor:/style: prefix) with imperative mood. Write a markdown summary of all rewrites via ${p.writeOutput("markdown", "commit-rewrites.md")}`,
output: s.object({
rewrites: s.array(s.object({
hash: s.string,
original: s.string,
revised: s.string,
category: s.enum("feat", "fix", "chore", "docs", "test", "refactor", "style"),
})),
markdown: s.string,
}),
maxTurns: 6,
addons: [steering({ message: "Use imperative mood and conventional commit prefixes. Be consistent." }), repair()],
});

export default commitMsgRewriter;

```
32 changes: 32 additions & 0 deletions skills/rig/samples/77-env-key-checker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# 77 - Env Key Checker

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

const parseEnvKeys = defineTool("parseEnvKeys", {
description: "Extract KEY names from dotenv-style file content",
parameters: s.object({ content: s.string }),
handler({ content }) {
const keys = (content.match(/^([A-Z_][A-Z0-9_]*)=/gm) || [])
.map(line => line.replace("=", ""));
return { keys };
},
});

// Agent role: compare .env.example required keys against .env actual keys to find missing and extra entries.
const envKeyChecker = agent({
model: "small",
instructions: p`Read the required env keys from ${p.readOptional(".env.example")} and the present keys from ${p.readOptional(".env")}. Use the parseEnvKeys tool on each file content to extract key names. Compare to find missing keys (in example but not env) and extra keys (in env but not example).`,
output: s.object({
missing: s.array(s.string),
extra: s.array(s.string),
requiredCount: s.int,
presentCount: s.int,
status: s.enum("complete", "partial", "empty"),
}),
tools: [parseEnvKeys],
});

export default envKeyChecker;

```
Loading