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
25 changes: 25 additions & 0 deletions skills/rig/samples/78-build-log-analyzer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# 78 - Build Log Analyzer

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

// Agent role: run the build and analyze the output for errors, warnings, and success status.
const buildLogAnalyzer = agent({
model: "small",
instructions: p`Run the build command and analyze its output: ${p.bash("npm run build 2>&1 || true")}. Extract all errors and warnings with their severity and file location if available. Determine whether the build succeeded overall.`,
output: s.object({
errors: s.array(s.object({
message: s.string,
file: s.optional(s.string),
severity: s.enum("error", "warning", "info"),
})),
buildSucceeded: s.boolean,
summary: s.string,
}),
maxTurns: 5,
addons: repair(),
});

export default buildLogAnalyzer;
```
31 changes: 31 additions & 0 deletions skills/rig/samples/79-ts-type-alias-mapper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# 79 - Ts Type Alias Mapper

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

const categorizeAlias = defineTool("categorizeAlias", {
description: "Categorize a TypeScript type alias definition by its kind using regex",
parameters: s.object({ definition: s.string }),
handler({ definition }) {
const trimmed = definition.trim();
if (/=\s*\w+\s*\|/.test(trimmed)) return { kind: "union" };
if (/=\s*\w+\s*&/.test(trimmed)) return { kind: "intersection" };
if (/=\s*\{[^}]*\[[^\]]+\]/.test(trimmed)) return { kind: "mapped" };
if (/=\s*(string|number|boolean|null|undefined|never|any|unknown)\s*$/.test(trimmed)) return { kind: "primitive" };
return { kind: "other" };
},
});

// Agent role: scan TypeScript files for type alias declarations and categorize each one.
const tsTypeAliasMapper = agent({
model: "small",
instructions: p`Find all TypeScript files: ${p.bash("find . -name '*.ts' -not -path '*/node_modules/*' -not -path '*/.git/*'")} and scan their contents: ${p.bash("grep -rn 'type [A-Z]' --include='*.ts' --exclude-dir=node_modules --exclude-dir=.git . 2>/dev/null || true")}. Use the categorizeAlias tool to classify each type alias. For each alias, also record whether it is exported (starts with 'export type').`,
output: s.record(s.object({
kind: s.enum("primitive", "union", "intersection", "mapped", "other"),
exported: s.boolean,
})),
tools: [categorizeAlias],
});

export default tsTypeAliasMapper;
```
18 changes: 18 additions & 0 deletions skills/rig/samples/80-git-contributor-mapper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# 80 - Git Contributor Mapper

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

// Agent role: map git contributors to their commit counts, primary work areas, and role classification.
const gitContributorMapper = agent({
model: "small",
instructions: p`Analyze git contributors using: ${p.bash("git shortlog -sn --no-merges")} and ${p.bash("git log --no-merges --name-only --pretty=format:'%an' | head -500")}. For each contributor, count their commits, identify which directories they primarily touch, and classify their role as core (many commits across many files), peripheral (few commits or limited scope), or single-file.`,
output: s.record(s.object({
commitCount: s.number,
primaryAreas: s.array(s.string),
role: s.enum("core", "peripheral", "single-file"),
})),
});

export default gitContributorMapper;
```
27 changes: 27 additions & 0 deletions skills/rig/samples/81-markdown-doc-summarizer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# 81 - Markdown Doc Summarizer

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

// Agent role: summarize each top-level section of the README, then compile into a full report.
const sectionSummarizer = agent({
name: "sectionSummarizer",
model: "nano",
instructions: p`Summarize the section of documentation provided in the input.`,
input: s.object({ heading: s.string, content: s.string }),
output: s.object({ heading: s.string, summary: s.string }),
});

// Agent role: read the project README, delegate per-section summarization, and write the final report.
const markdownDocSummarizer = agent({
model: "small",
instructions: p`Read the README: ${p.readOptional("README.md", "No README found.")}. Identify each top-level heading (##) and its content. Delegate summarization of each section to the sectionSummarizer agent. Compile all summaries into a report and write it to summaries/README-summary.md via ${p.writeOutput("reportPath", "summaries/README-summary.md")}.`,
output: s.object({
sections: s.array(s.object({ heading: s.string, summary: s.string })),
reportPath: s.string,
}),
agents: { sectionSummarizer },
});

export default markdownDocSummarizer;
```
34 changes: 34 additions & 0 deletions skills/rig/samples/82-workspace-config-drift.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# 82 - Workspace Config Drift

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

const parseJson = defineTool("parseJson", {
description: "Parse a JSON string and return it, or report a parse error",
parameters: s.object({ content: s.string, filename: s.string }),
handler({ content, filename }) {
try {
const parsed = JSON.parse(content);
return { ok: true, parsed };
} catch (e) {
return { ok: false, error: String(e), filename };
}
},
});

// Agent role: detect drift in workspace config files by reading them and comparing against known defaults.
const workspaceConfigDrift = agent({
model: "small",
instructions: p`Read project config files: ${p.readOptional("tsconfig.json", "{}")} (tsconfig.json), ${p.readOptional(".eslintrc.json", "{}")} (.eslintrc.json), ${p.readOptional(".prettierrc", "{}")} (.prettierrc). Use the parseJson tool to parse each file. For each config, identify fields that deviate from sensible defaults and report them as drifted. Assign status ok if no drift, warning for minor issues, error for significant mismatches.`,
output: s.record(s.object({
driftedFields: s.array(s.string),
status: s.enum("ok", "warning", "error"),
})),
tools: [parseJson],
maxTurns: 4,
addons: repair(),
});

export default workspaceConfigDrift;
```
22 changes: 22 additions & 0 deletions skills/rig/samples/83-commit-format-suggester.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# 83 - Commit Format Suggester

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

// Agent role: review recent git commits and suggest conventional-format rewrites for each one.
const commitFormatSuggester = agent({
model: "small",
instructions: p`Review recent git commits: ${p.bash("git log --oneline -20 --no-merges")}. For each commit, check whether its message follows conventional commit format (type: description). Suggest a rewritten message in conventional format. Classify each commit as one of: feat, fix, chore, docs, test, refactor, style. Write the full report to commit-report.md via ${p.writeOutput("reportWritten", "commit-report.md")}.`,
output: 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] p.writeOutput("reportWritten", ...) declares a write intent but the output schema is s.array(s.object(...)) — there is no reportWritten field in the schema to capture the written path. The harness needs a corresponding string field in the output schema.

💡 Fix

Wrap in s.object and add the reportWritten field, matching the pattern from samples 76 and 81:

output: s.object({
  commits: s.array(s.object({
    hash: s.string,
    original: s.string,
    suggested: s.string,
    category: s.enum("feat", "fix", "chore", "docs", "test", "refactor", "style"),
  })),
  reportWritten: s.string,
}),

hash: s.string,
original: s.string,
suggested: s.string,
category: s.enum("feat", "fix", "chore", "docs", "test", "refactor", "style"),
})),
maxTurns: 5,
addons: [steering(), repair()],

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] steering() is called with no message, which means no guidance text is injected on the final retry. The reference docs state: "Use steering({ message: '...' }); a positional string is invalid." An empty steering() is technically valid but wastes the final-turn hint.

💡 Suggestion

Add a targeted steering message, as in sample 76:

addons: [steering({ message: "Follow conventional commit format strictly: type(scope): imperative description." }), repair()],

});

export default commitFormatSuggester;
```
35 changes: 35 additions & 0 deletions skills/rig/samples/84-json-schema-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# 84 - Json Schema Migration

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

// Agent role: analyze two JSON schema files to identify structural changes and produce a migration plan.
const diffAnalyzer = agent({
name: "diffAnalyzer",
model: "nano",
instructions: p`Analyze the structural differences between the two JSON schemas provided in the input and list each change type (add, remove, modify, rename) with the JSON path and whether it is a breaking change.`,
input: s.object({ oldSchema: s.string, newSchema: s.string }),
output: s.array(s.object({
changeType: s.enum("add", "remove", "modify", "rename"),
path: s.string,
description: s.string,
breakingChange: s.boolean,
})),
});

// Agent role: read two JSON schema files and coordinate a migration plan using the diffAnalyzer subagent.
const jsonSchemaMigration = agent({
model: "small",
input: s.object({ oldSchemaPath: s.path, newSchemaPath: s.path }),
instructions: p`Read the old schema at ${p.readInput("oldSchemaPath")} and new schema at ${p.readInput("newSchemaPath")}. Pass both to the diffAnalyzer subagent to identify all changes and whether each is a breaking change.`,
output: s.array(s.object({
changeType: s.enum("add", "remove", "modify", "rename"),
path: s.string,
description: s.string,
breakingChange: s.boolean,
})),
agents: { diffAnalyzer },
});

export default jsonSchemaMigration;
```
45 changes: 45 additions & 0 deletions skills/rig/samples/85-dockerfile-security-audit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# 85 - Dockerfile Security Audit

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

const checkSecurityPattern = defineTool("checkSecurityPattern", {
description: "Check a Dockerfile line for known security anti-patterns",
parameters: s.object({ line: s.string, lineNumber: s.number }),

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 USER root check regex uses ||' pattern — /^USER\s+root\s*$/i.test(line.trim()) || /^RUN.&&.&&.*--no-check/.test(line) — the second part (--no-check) is bundled into the same 'running as root' finding. This mixes two unrelated anti-patterns into one rule, making the message "Running as root user"misleading when theRUN --no-check` branch triggers.

💡 Fix

Split into separate findings:

if (/^USER\s+root\s*$/i.test(line.trim())) {
  findings.push({ severity: "critical", message: "Running as root user", rule: "no-root-user" });
}
if (/^RUN.*--no-check/.test(line)) {
  findings.push({ severity: "high", message: "Package install skips integrity check", rule: "no-skip-check" });
}

handler({ line, lineNumber }) {
const findings: Array<{ severity: string; message: string; rule: string }> = [];
if (/^USER\s+root\s*$/i.test(line.trim()) || /^RUN.*&&.*&&.*--no-check/.test(line)) {
findings.push({ severity: "critical", message: "Running as root user", rule: "no-root-user" });
}
if (/ADD\s+/.test(line) && !/ADD\s+https?:\/\//.test(line)) {
findings.push({ severity: "medium", message: "Prefer COPY over ADD for local files", rule: "prefer-copy" });
}
if (/FROM\s+\S+:latest/.test(line)) {
findings.push({ severity: "high", message: "Avoid :latest tag for reproducibility", rule: "no-latest-tag" });
}
if (/ENV\s+\w*(KEY|SECRET|PASSWORD|TOKEN)\w*\s*=/.test(line)) {
findings.push({ severity: "critical", message: "Secret embedded in ENV instruction", rule: "no-secrets-in-env" });
}
return { lineNumber, findings };
},
});

// Agent role: audit a Dockerfile for security issues by scanning each instruction for known anti-patterns.
const dockerfileSecurityAudit = agent({
model: "small",
input: s.object({ dockerfilePath: s.path }),
instructions: p`Read the Dockerfile at ${p.readInput("dockerfilePath")}. Use the checkSecurityPattern tool on each instruction line to detect security issues. Compile all findings with their severity, line number, rule, and message.`,
output: s.object({
findings: s.array(s.object({
severity: s.enum("critical", "high", "medium", "low"),
lineNumber: s.number,
message: s.string,
rule: s.string,
})),
passes: s.boolean,
}),
tools: [checkSecurityPattern],
});

export default dockerfileSecurityAudit;
```
21 changes: 21 additions & 0 deletions skills/rig/samples/86-npm-audit-simplifier.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# 86 - Npm Audit Simplifier

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

// Agent role: run npm audit, parse the JSON output, and produce a simplified vulnerability report.
const npmAuditSimplifier = agent({
model: "small",
instructions: p`Run ${p.bash("npm audit --json 2>/dev/null || echo '{}'")} to get the vulnerability report. Parse the JSON and group vulnerabilities by severity level (critical, high, moderate, low, info). Count the total number of vulnerabilities. Provide a one-sentence recommendation for remediation.`,
output: s.object({
vulnerabilitiesByLevel: s.record(s.array(s.string)),
totalCount: s.number,
recommendation: s.string,
}),
maxTurns: 5,
addons: repair(),
});

export default npmAuditSimplifier;
```
19 changes: 19 additions & 0 deletions skills/rig/samples/87-git-stash-inventory.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# 87 - Git Stash Inventory

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

// Agent role: inventory all git stashes with their descriptions, changed files, and staleness classification.
const gitStashInventory = agent({
model: "small",
instructions: p`List all git stashes: ${p.bash("git stash list 2>/dev/null || echo 'No stashes found'")}. For each stash entry shown, show its changed files: ${p.bash("git stash show --name-only 2>/dev/null || true")}. For each stash, classify its staleness as: fresh (< 1 week), aging (1-4 weeks), stale (1-3 months), ancient (> 3 months) based on the date shown in the stash list.`,

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] git stash show --name-only without a stash reference always shows stash@{0} — only the top stash's files are fetched, not each individual stash entry. For an inventory of N stashes, this produces wrong data for all but the first.

💡 Fix

The agent needs to iterate over stash refs. Use a command that iterates, or note in the instructions that the agent should call git stash show --name-only stash@{N} per entry. A bash one-liner like this collects all at once:

git stash list --format='%gd' | xargs -I{} sh -c 'echo "---{}"; git stash show --name-only {} 2>/dev/null'

Or simplify to a single p.bash that already includes the stash ref loop.

output: s.array(s.object({
stashRef: s.string,
description: s.string,
changedFiles: s.array(s.string),
staleness: s.enum("fresh", "aging", "stale", "ancient"),
})),
});

export default gitStashInventory;
```
Loading