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
51 changes: 51 additions & 0 deletions skills/rig/samples/250-git-commit-annotator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# 250 - Git Commit Annotator

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

// Agent role: summarize a single git commit line into a category and impact rating
const commitSummarizer = agent({
name: "commitSummarizer",
model: "small",
instructions: p`Analyze the git commit message provided in the input and classify it.

Commit info: ${p.inputField("commitLine")}

Classify into category (feat, fix, chore, docs, refactor, test, perf) and rate impact (low, medium, high).`,
input: s.object({ commitLine: s.string }),
output: s.object({
category: s.enum("feat", "fix", "chore", "docs", "refactor", "test", "perf"),
impact: s.enum("low", "medium", "high"),
}),
});

// Agent role: annotate recent git commits with category and impact via subagent delegation, writing a markdown report
const gitCommitAnnotator = agent({
name: "gitCommitAnnotator",
model: "small",
agents: { commitSummarizer },
instructions: p`Annotate recent git commits using the commitSummarizer subagent.

Recent commits (hash + message):
${p.bash("git log --oneline -20")}

For each commit line, delegate to commitSummarizer with the full commit line as commitLine.
Collect the category and impact for every commit.
Build a markdown report table with columns: hash, message, category, impact.
Write the report to commit-annotations.md.
${p.writeOutput("report", "commit-annotations.md")}`,
output: s.object({
annotations: s.array(
s.object({
hash: s.string,
message: s.string,
category: s.enum("feat", "fix", "chore", "docs", "refactor", "test", "perf"),
impact: s.enum("low", "medium", "high"),
})
),
report: s.string,
}),
});

export default gitCommitAnnotator;
```
51 changes: 51 additions & 0 deletions skills/rig/samples/251-npm-script-dep-mapper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# 251 - Npm Script Dep Mapper

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

const parseScriptDeps = defineTool("parseScriptDeps", {
description: "Extract npm run references and detect pre/post hooks for a script",
parameters: s.object({
scriptName: s.string,
scriptBody: s.string,
allScriptNames: s.array(s.string),
}),
handler: ({ scriptName, scriptBody, allScriptNames }) => {
const deps: string[] = [];
const runRefs = scriptBody.match(/npm run ([\w:.-]+)/g) ?? [];
runRefs.forEach((ref: string) => deps.push(ref.replace("npm run ", "")));
const hasPreHook = allScriptNames.includes("pre" + scriptName);
const hasPostHook = allScriptNames.includes("post" + scriptName);
return JSON.stringify({ deps, hasPreHook, hasPostHook });
},
});

// Agent role: map npm script dependencies and classify each script by category
const npmScriptDepMapper = agent({
name: "npmScriptDepMapper",
model: "small",
maxTurns: 2,
addons: repair(),
tools: [parseScriptDeps],
instructions: p`Analyze npm scripts from package.json and map their dependencies.

package.json: ${p.read("package.json")}

For each script, call parseScriptDeps with the scriptName, scriptBody, and list of all script names.
Classify each script's category: build, test, lint, dev, deploy, util, hook, or other.
A script is a hook category if its name starts with pre or post.
Set hasPreHook and hasPostHook based on tool output.
Return a record keyed by script name.`,
output: s.record(
s.object({
command: s.string,
dependsOn: s.array(s.string),
category: s.enum("build", "test", "lint", "dev", "deploy", "util", "hook", "other"),
hasPreHook: s.boolean,
hasPostHook: s.boolean,
})
),
});

export default npmScriptDepMapper;
```
60 changes: 60 additions & 0 deletions skills/rig/samples/252-markdown-link-checker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# 252 - Markdown Link Checker

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

const checkUrl = defineTool("checkUrl", {
description: "Check the HTTP status of a URL using curl",
parameters: s.object({ url: s.string }),
handler: async ({ url }) => {
const { execSync } = await import("node:child_process");
try {
const code = execSync(
`curl --head --silent --max-time 5 -o /dev/null -w "%{http_code}" "${url}" 2>/dev/null`,

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] Shell injection risk: url is interpolated directly into a shell string. A URL with embedded $(...) or backticks would execute arbitrary code when curl is invoked via execSync.

💡 Suggested fix

Shell-escape the URL or pass it as an argument after --:

const safeUrl = url.replace(/"/g, '');
const code = execSync(
  `curl --head --silent --max-time 5 -o /dev/null -w "%{http_code}" -- "${safeUrl}"`,
  { encoding: "utf8", timeout: 8000 }
).trim();

Or avoid the shell entirely with node:https / fetch.

{ encoding: "utf8", timeout: 8000 }
).trim();
return code;
} catch {
return "timeout";
}
},
});

// Agent role: locate markdown files, extract HTTP links, and check each link's HTTP status
const markdownLinkChecker = agent({
name: "markdownLinkChecker",
model: "small",
addons: repair(),
tools: [checkUrl],
instructions: p`Check HTTP links found across all markdown files in this workspace.

Markdown files found:
${p.bash("find . -name '*.md' -not -path '*/node_modules/*' | head -20")}

Unique URLs extracted:
${p.bash("grep -roh 'https\\?://[^)\"\\s]*' . --include='*.md' 2>/dev/null | sort -u | head -50")}

For each URL, call checkUrl and classify the result:
- ok: HTTP 200-299
- redirect: HTTP 301, 302, 307, 308
- broken: HTTP 400-599
- timeout: curl timed out or failed
- skipped: malformed or non-http URL

Set allValid to true only if brokenCount is 0.`,
output: s.object({
links: s.array(
s.object({
url: s.url,
status: s.enum("ok", "broken", "redirect", "timeout", "skipped"),
httpCode: s.optional(s.int),
foundInFile: s.optional(s.path),
})
),
brokenCount: s.int,
allValid: s.boolean,
}),
});

export default markdownLinkChecker;
```
39 changes: 39 additions & 0 deletions skills/rig/samples/253-package-version-drift.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# 253 - Package Version Drift

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

// Agent role: compare installed npm package versions against the latest published versions and classify drift
const pkgVersionDrift = agent({
name: "pkgVersionDrift",
model: "small",
instructions: p`Report npm package version drift for this project.

Package manifest: ${p.read("package.json")}

Outdated packages (JSON): ${p.bash("npm outdated --json 2>/dev/null || echo '{}'")}

For each dependency, classify driftLevel:
- ok: current matches latest
- patch: only patch version behind
- minor: minor version behind
- major: major version behind

Write a markdown drift summary report.
${p.writeOutput("report", "version-drift-report.md")}`,
output: s.object({
packages: s.array(
s.object({
name: s.string,
current: s.string,
latest: s.string,
driftLevel: s.enum("ok", "patch", "minor", "major"),
})
),
report: s.string,
reportWritten: s.boolean,

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] reportWritten: s.boolean is declared in the output schema but there is no mechanism for the agent to know whether p.writeOutput succeeded — it's a prompt instruction, not a tool call with a return value. The field will be whatever the LLM guesses (likely true always), making it a false signal.

💡 Suggestion

Remove reportWritten from the output schema, or if confirming file writes matters, use a defineTool that does the write in-process and returns a real success/error value.

}),
});

export default pkgVersionDrift;
```
37 changes: 37 additions & 0 deletions skills/rig/samples/254-git-author-stats.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# 254 - Git Author Stats

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

// Agent role: aggregate git commit statistics per author including commit counts and date ranges
const gitAuthorStats = agent({
name: "gitAuthorStats",
model: "small",
instructions: p`Aggregate git commit statistics per author.

Author commit counts: ${p.bash("git shortlog -sne --all 2>/dev/null | head -40")}

Commit log with author emails and dates: ${p.bash("git log --format='%ae|%ad' --date=short 2>/dev/null | head -200")}

For each unique author email, compute:
- commitCount: total number of commits
- firstCommit: earliest commit date (YYYY-MM-DD)
- lastCommit: most recent commit date (YYYY-MM-DD)

Identify topAuthor with the most commits.
Compute totalCommits across all authors.`,
output: s.object({
authors: s.record(
s.object({
commitCount: s.int,
firstCommit: s.string,
lastCommit: s.string,
})
),
topAuthor: s.string,
totalCommits: s.int,
}),
});

export default gitAuthorStats;
```
48 changes: 48 additions & 0 deletions skills/rig/samples/255-ts-path-alias-validator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# 255 - Ts Path Alias Validator

```rig
import { agent, p, s, defineTool } from "rig";
import { existsSync } from "node:fs";

const checkAlias = defineTool("checkAlias", {
description: "Check whether a TypeScript path alias target exists on the filesystem",
parameters: s.object({ alias: s.string, target: s.string }),
handler: ({ alias, target }) => {
const resolved = target.replace(/\/\*$/, "");
const exists = existsSync(resolved);
return JSON.stringify({ alias, target, exists });
},
});

// Agent role: validate TypeScript path alias targets from tsconfig.json against the filesystem
const tsPathAliasValidator = agent({
name: "tsPathAliasValidator",
model: "small",
tools: [checkAlias],
instructions: p`Validate TypeScript path aliases defined in tsconfig.json.

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

Import alias usage in source: ${p.bash("grep -r --include='*.ts' -h 'from [\"\\x27]' . 2>/dev/null | grep -v node_modules | grep -v '\\./' | grep -v '\\.\\./' | head -40")}

For each entry in compilerOptions.paths, call checkAlias with the alias pattern and its first target path.
Classify each alias:
- valid: target exists on disk and alias is used in imports
- broken: target path does not exist
- unused: target exists but alias not found in any imports

Report totalAliases and brokenCount.`,
output: s.object({
aliases: s.record(
s.object({
target: s.string,
status: s.enum("valid", "broken", "unused"),
})
),
totalAliases: s.int,
brokenCount: s.int,
}),
});

export default tsPathAliasValidator;
```
56 changes: 56 additions & 0 deletions skills/rig/samples/256-gh-actions-timing-analyzer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# 256 - Gh Actions Timing Analyzer

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

const parseWorkflowSteps = defineTool("parseWorkflowSteps", {
description: "Parse a GitHub Actions workflow YAML to extract job names, step counts, and detect optimization patterns",
parameters: s.object({ content: s.string, filename: s.string }),
handler: ({ content, filename }) => {
const jobMatches = content.match(/^\s{2}[\w-]+:/gm) ?? [];

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] The job-count regex /^\s{2}[\w-]+:/gm will match any top-level YAML key indented exactly two spaces — including on:, env:, defaults:, concurrency:, and permissions:, not only jobs.<id>. This overstates jobs and triggers the wrong suggestions.

💡 Suggested fix

Anchor the match to the jobs: block. A simple heuristic is to count keys two levels deep under a jobs: line:

const jobsSection = content.match(/^jobs:\n([\s\S]*?)(?=^\S|$)/m)?.[1] ?? '';
const jobMatches = jobsSection.match(/^  [\w-]+:/gm) ?? [];

Or just document the known over-counting so sample readers aren't misled.

const stepMatches = content.match(/^\s+-\s+(?:name:|uses:|run:)/gm) ?? [];
const hasCache = /uses:\s+actions\/cache/.test(content) || /cache:/.test(content);
const hasMatrix = /matrix:/.test(content);
const hasInstall = /npm (ci|install)|yarn install|pnpm install/.test(content);
const missingCache = hasInstall && !hasCache;
const suggestions: string[] = [];
if (missingCache) suggestions.push("Add actions/cache after install step to speed up builds");
if (hasMatrix && jobMatches.length === 1) suggestions.push("Matrix found but only one job — consider parallel jobs for different test suites");
if (stepMatches.length > 15) suggestions.push("Workflow has many steps — consider splitting into reusable workflows");
return JSON.stringify({ filename, jobs: jobMatches.length, steps: stepMatches.length, hasCache, hasMatrix, suggestions });
},
});

// Agent role: analyze GitHub Actions workflow files for timing and performance optimization opportunities
const ghActionsTimingAnalyzer = agent({
name: "ghActionsTimingAnalyzer",
model: "small",
addons: repair(),
tools: [parseWorkflowSteps],
instructions: p`Analyze GitHub Actions workflow files for performance optimization opportunities.

Workflow files: ${p.glob(".github/workflows/*.yml")}

Workflow contents: ${p.bash("find .github/workflows -name '*.yml' 2>/dev/null | head -10 | xargs cat 2>/dev/null || echo 'No workflows found'")}

For each workflow file, call parseWorkflowSteps with the file content and filename.
Collect results and determine if any workflows are optimizable (have suggestions).
Set optimizable to true if any workflow has suggestions.`,
output: s.object({
workflows: s.array(
s.object({
file: s.path,
jobs: s.int,
steps: s.int,
hasCache: s.boolean,
hasMatrix: s.boolean,
suggestions: s.array(s.string),
})
),
totalWorkflows: s.int,
optimizable: s.boolean,
}),
});

export default ghActionsTimingAnalyzer;
```
Loading