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
48 changes: 48 additions & 0 deletions skills/rig/samples/110-workspace-config-drift-3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# 110 - Workspace Config Drift 3

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

// Agent role: detect drift in workspace config files against a baseline.
const workspaceConfigDrift = agent({
model: "mini",
maxTurns: 6,
addons: repair(),
input: s.object({
baselineFile: s.path,
}),
instructions: p`Read the baseline config from ${p.readInput("baselineFile")}.
Compare it against the current workspace configs:
- tsconfig.json: ${p.readOptional("tsconfig.json", "{}")}
- .eslintrc.json: ${p.readOptional(".eslintrc.json", "{}")}
- .prettierrc: ${p.readOptional(".prettierrc", "{}")}

Use the parseJson tool to parse each config. For each file, identify fields that differ from
the baseline. Return only the declared output.`,
tools: [
defineTool("parseJson", {
description: "Parse a JSON string and return the keys",
parameters: s.object({ content: s.string }),
handler({ content }) {
try {
return Object.keys(JSON.parse(content));
} catch {
return [];
}
},
}),
],
output: s.object({
results: s.record(
s.object({
driftedFields: s.array(s.string),
status: s.enum("ok", "warning", "error"),
})
),
overallStatus: s.enum("ok", "warning", "error"),
}),
});

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

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

// Agent role: suggest conventional-format rewrites for recent git commit messages.
const conventionalCommitSuggester = agent({
model: "mini",
maxTurns: 6,
addons: [steering({ message: "Ensure each suggestion follows the conventional commits spec: <type>(<scope>?): <description> using imperative mood." }), repair()],
instructions: p`Review the recent git log and suggest conventional commit message rewrites:
${p.bash("git log --oneline -20")}

For each commit, identify the best category (feat/fix/chore/docs/test/refactor/style) and
rewrite the message to follow the conventional commits spec in imperative mood.
Write the full report to output. Return only the declared output.`,
output: s.object({
suggestions: s.array(
s.object({
hash: s.string,
original: s.string,
suggested: s.string,
category: s.enum("feat", "fix", "chore", "docs", "test", "refactor", "style"),
})
),
totalReviewed: s.int,
}),
});

export default conventionalCommitSuggester;
```
39 changes: 39 additions & 0 deletions skills/rig/samples/112-runtime-env-health.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# 112 - Runtime Env Health

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

// Agent role: inspect the runtime environment and report health status.
const runtimeEnvHealth = agent({
model: "mini",
instructions: p`Inspect the runtime environment using the collected info:
Node.js version: ${p.bash("node --version")}
OS info: ${p.bash("uname -a")}
Memory: ${p.bash("free -m 2>/dev/null || vm_stat 2>/dev/null || echo 'unknown'")}

Use the checkThreshold tool to validate Node.js version (minimum v18) and available memory
(minimum 256 MB). Return only the declared output.`,
tools: [
defineTool("checkThreshold", {
description: "Check if a numeric value meets a minimum threshold",
parameters: s.object({
value: s.number,
minimum: s.number,
label: s.string,
}),
handler({ value, minimum, label }) {
return { label, ok: value >= minimum, value, minimum };
},
}),
],
output: s.object({
nodeVersion: s.string,
os: s.string,
memoryMb: s.number,
health: s.enum("ok", "degraded", "critical"),
issues: s.array(s.string),
}),
});

export default runtimeEnvHealth;
```
34 changes: 34 additions & 0 deletions skills/rig/samples/113-git-hotspot-analyzer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# 113 - Git Hotspot Analyzer

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

// Agent role: identify hot-spot files by git churn and top contributors.
const gitHotspotAnalyzer = agent({
model: "mini",
addons: steering({ message: "Focus only on the top N files by change frequency. Skip binary files and node_modules." }),
input: s.object({
topN: s.int,
}),
instructions: p`Analyze git history to find the most frequently changed files.

Changed files from git log:
${p.bash("git log --follow --name-only --format='' -- . | sort | uniq -c | sort -rn | head -20")}

For the top files identified, get contributor info:
${p.bash("git shortlog -sn --all -- . 2>/dev/null | head -10")}

Select the top \${input.topN} files by churn score. For each file compute a churnScore
(number of commits) and list topContributors. Return only the declared output as a record
keyed by file path.`,
output: s.record(

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] \${input.topN} renders as a literal string in the prompt — the caller-supplied topN limit is never injected into the model's instructions, so it is silently ignored.

💡 Suggested fix

Inject the value through the steering message or via an s.int description. Since topN is a number (not a path), p.readInput won't work here — instead, include it explicitly in the steering addon message or in the agent description:

addons: steering({ message: `Focus only on the top ${input.topN} files by change frequency.` }),

Or pass topN through the instructions string by having the harness resolve it, if that pattern is supported by the runtime. Until then the backslash escape means the model always sees the literal text ${input.topN} rather than e.g. 5.

s.object({
churnScore: s.number,
topContributors: s.array(s.string),
})
),
});

export default gitHotspotAnalyzer;
```
39 changes: 39 additions & 0 deletions skills/rig/samples/114-loc-statistics-gatherer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# 114 - Loc Statistics Gatherer

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

// Agent role: gather lines-of-code statistics per file extension.
const locStatisticsGatherer = agent({
model: "mini",
instructions: p`Count lines of code by file extension in this workspace.

Source files found:
${p.bash("find . -not -path '*/node_modules/*' -not -path '*/.git/*' \\( -name '*.ts' -o -name '*.js' -o -name '*.py' -o -name '*.go' -o -name '*.rs' \\) 2>/dev/null | head -200")}

Line counts:
${p.bash("find . -not -path '*/node_modules/*' -not -path '*/.git/*' \\( -name '*.ts' -o -name '*.js' -o -name '*.py' \\) -exec wc -l {} + 2>/dev/null | tail -5")}

Use the classifyComplexity tool to determine overall complexity. Aggregate results by extension.
Return only the declared output.`,
tools: [
defineTool("classifyComplexity", {
description: "Classify total line count into complexity bucket",
parameters: s.object({ totalLines: s.int }),
handler({ totalLines }) {
if (totalLines < 1000) return "small";
if (totalLines < 10000) return "medium";
if (totalLines < 100000) return "large";
return "xlarge";
},
}),
],
output: s.object({
byExtension: s.record(s.object({ lineCount: s.int, fileCount: s.int })),
totalLines: s.int,
complexity: s.enum("small", "medium", "large", "xlarge"),
}),
});

export default locStatisticsGatherer;
```
42 changes: 42 additions & 0 deletions skills/rig/samples/115-import-cycle-detector.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# 115 - Import Cycle Detector

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

// Agent role: detect circular import cycles in the TypeScript project.
const importCycleDetector = agent({
model: "mini",
maxTurns: 6,
addons: repair(),
instructions: p`Detect circular imports in this TypeScript project.

TypeScript config:
${p.bash("cat tsconfig.json 2>/dev/null || echo '{}'")}

Circular imports (via madge):
${p.bash("npx --yes madge --circular --extensions ts . 2>/dev/null || echo 'madge not available or no cycles found'")}

Alternatively, check for potential cycles by scanning imports:
${p.bash("grep -r --include='*.ts' 'from.*\\.' . 2>/dev/null | grep -v node_modules | head -50")}

Identify any circular import chains. For each cycle, classify severity:
- high: cycles in core/shared modules
- medium: cycles in feature modules
- low: cycles in utility or test files

Return only the declared output.`,
output: s.object({
hasCycles: s.boolean,
cycles: s.array(
s.object({
path: s.array(s.string),
severity: s.enum("high", "medium", "low"),
})
),
cycleCount: s.int,
}),
});

export default importCycleDetector;
```
46 changes: 46 additions & 0 deletions skills/rig/samples/116-barrel-file-generator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# 116 - Barrel File Generator

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

// Agent role: generate barrel index.ts files for TypeScript source directories.
const barrelFileGenerator = agent({
model: "mini",
input: s.object({
srcDir: s.path,
}),
instructions: p`Generate barrel index.ts files for TypeScript source directories.

Source files found (excluding index.ts):
${p.bash("find . -name '*.ts' ! -name 'index.ts' ! -name '*.test.ts' ! -name '*.spec.ts' -not -path '*/node_modules/*' 2>/dev/null | head -100")}

Use the classifyExports tool to identify exported symbols in files. Then generate barrel
content for each directory that has exported symbols. Write each barrel file using p.write.

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 instructions reference p.write ("Write each barrel file using p.write") but no p.write call appears in the agent definition. The barrelFilesWritten output field implies actual writes, but nothing will be written — the agent can only report what it would write.

💡 Suggested fix

Either:

  1. Add a p.write intent to actually write barrel files (if the runtime supports it for this use case), or
  2. Rename barrelFilesWritten to barrelFilesToWrite and update the instructions to say "report the barrel content" rather than "write" — making it a dry-run analyser, consistent with what the agent actually does.

As a sample, misleading prose about side effects can confuse users trying to learn from the pattern.


Count the total number of files scanned, barrel files that would be written, export count,
and directories involved. Return only the declared output.`,
tools: [
defineTool("classifyExports", {
description: "Scan a TypeScript file path and detect export patterns",
parameters: s.object({ filePath: s.string }),
async handler({ filePath }) {
const { execSync } = await import("node:child_process");
try {
const out = execSync(`grep -n "^export " "${filePath}" 2>/dev/null || true`, { encoding: "utf-8" });
return { filePath, exportLines: out.trim().split("\n").filter(Boolean).length };
} catch {
return { filePath, exportLines: 0 };
}
},
}),
],
output: s.object({
filesScanned: s.int,
barrelFilesWritten: s.int,
exportCount: s.int,
directories: s.array(s.string),
}),
});

export default barrelFileGenerator;
```
42 changes: 42 additions & 0 deletions skills/rig/samples/117-git-hook-inventory.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# 117 - Git Hook Inventory

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

// Agent role: inventory git hooks and classify each as active, stub, or missing.
const gitHookInventory = agent({
model: "mini",
instructions: p`Inventory the git hooks in this repository.

Available hooks in .git/hooks:
${p.bash("ls .git/hooks/ 2>/dev/null || echo 'no hooks directory'")}

Sample hooks content:
${p.bash("for f in .git/hooks/pre-commit .git/hooks/commit-msg .git/hooks/pre-push; do echo \"=== $f ===\"; cat \"$f\" 2>/dev/null || echo 'missing'; done")}

Use the classifyHook tool to classify each hook. Return a record keyed by hook name
with status, summary, and whether it runs asynchronously (uses & or async patterns).
Return only the declared output.`,
tools: [
defineTool("classifyHook", {
description: "Classify a git hook by its content",
parameters: s.object({ name: s.string, content: s.string }),
handler({ content }) {
if (!content || content === "missing") return { status: "missing", isAsync: false };
const isSample = content.includes("sample") || content.trim() === "#!/bin/sh";
const isAsync = content.includes(" &") || content.includes("async");
return { status: isSample ? "stub" : "active", isAsync };
},
}),

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 classifyHook handler declares name as a required parameter but never uses it — the returned object also omits summary, which is a required field in the output schema. The LLM must invent a summary value since the tool never provides one.

💡 Suggested fix

Either include summary in the handler's return value or remove it from the output schema. For example:

handler({ name, content }) {
  if (!content || content === "missing") return { status: "missing", isAsync: false, summary: "not installed" };
  const isSample = content.includes("sample") || content.trim() === "#!/bin/sh";
  const isAsync = content.includes(" &") || content.includes("async");
  return { status: isSample ? "stub" : "active", isAsync, summary: isSample ? "sample hook" : name };
}

],
output: s.record(
s.object({
summary: s.string,
status: s.enum("active", "stub", "missing"),
isAsync: s.boolean,
})
),
});

export default gitHookInventory;
```
44 changes: 44 additions & 0 deletions skills/rig/samples/118-pr-review-checklist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# 118 - Pr Review Checklist

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

// Agent role: generate a PR review checklist from the recent git diff.
const prReviewChecklist = agent({
model: "mini",
maxTurns: 6,
addons: repair(),
instructions: p`Generate a PR review checklist based on the changes in this branch.

Changed files:
${p.bash("git diff --name-only HEAD~1 2>/dev/null || git diff --name-only HEAD 2>/dev/null || echo 'no diff available'")}

Diff summary:
${p.bash("git diff --stat HEAD~1 2>/dev/null || git diff --stat HEAD 2>/dev/null || echo 'no diff stats'")}

Key changes:
${p.bash("git diff HEAD~1 -- . 2>/dev/null | head -200 || echo 'no diff content'")}

For each concern in the changes, produce a checklist item with:
- A clear action item description
- Category (test/docs/security/performance/style)
- Priority (high/medium/low)

Also list the changed files and indicate if the PR is ready for review.
Return only the declared output.`,
output: s.object({
checklist: s.array(
s.object({
item: s.string,
category: s.enum("test", "docs", "security", "performance", "style"),
priority: s.enum("high", "medium", "low"),
})
),
changedFiles: s.array(s.string),
ready: s.boolean,
}),
});

export default prReviewChecklist;
```
Loading