Skip to content

Commit ee98512

Browse files
Add 10 rig samples (160-169) — 2026-07-26 (#169)
1 parent 95e954c commit ee98512

10 files changed

Lines changed: 462 additions & 0 deletions
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# 160 - Dead Code Detector
2+
3+
```rig
4+
import { agent, defineTool, p, s } from "rig";
5+
import { repair } from "rig/addons";
6+
7+
// Agent role: detect dead TypeScript exports by scanning for exported symbols and estimating usage counts.
8+
const deadCodeDetector = agent({
9+
model: "small",
10+
instructions: p`Detect dead code in this TypeScript project.
11+
12+
Files in project:
13+
${p.bash("find . -name '*.ts' -not -path '*/node_modules/*' -not -path '*/.git/*' | head -40")}
14+
15+
Exported symbols found:
16+
${p.bash("grep -rn '^export ' --include='*.ts' . 2>/dev/null | grep -v node_modules | head -60")}
17+
18+
Use the estimateUsage tool for each exported symbol name to count how many times it appears across the codebase. Classify each as: "used" (>1 reference), "possibly-dead" (exactly 1, the declaration itself), or "dead" (0 references outside declaration). Return only the declared output.`,
19+
tools: [
20+
defineTool("estimateUsage", {
21+
description: "Count non-declaration usages of an exported symbol across the codebase",
22+
parameters: s.object({ symbol: s.string }),
23+
async handler({ symbol }) {
24+
const { execSync } = await import("node:child_process");
25+
try {
26+
const out = execSync(
27+
`grep -rn "\\b${symbol}\\b" --include="*.ts" . 2>/dev/null | grep -v "^export " | grep -v node_modules | wc -l`,
28+
{ encoding: "utf-8" }
29+
);
30+
return { symbol, usageCount: parseInt(out.trim(), 10) };
31+
} catch {
32+
return { symbol, usageCount: 0 };
33+
}
34+
},
35+
}),
36+
],
37+
addons: [repair()],
38+
output: s.object({
39+
symbols: s.record(s.object({
40+
usageCount: s.int,
41+
status: s.enum("used", "possibly-dead", "dead"),
42+
})),
43+
totalExported: s.int,
44+
deadCount: s.int,
45+
}),
46+
});
47+
48+
export default deadCodeDetector;
49+
```
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# 161 - OpenAPI Spec Validator
2+
3+
```rig
4+
import { agent, defineTool, p, s } from "rig";
5+
6+
// Agent role: validate an OpenAPI spec file for structural correctness and report issues.
7+
const openapiSpecValidator = agent({
8+
model: "small",
9+
instructions: p`Validate the OpenAPI specification in this workspace.
10+
11+
openapi.json content:
12+
${p.readOptional("openapi.json")}
13+
14+
openapi.yaml content:
15+
${p.readOptional("openapi.yaml")}
16+
17+
Use the checkStructure tool to validate the spec content. Check for: required fields (openapi version, info.title, info.version, paths), valid HTTP methods, proper response codes, and schema references. Return only the declared output.`,
18+
tools: [
19+
defineTool("checkStructure", {
20+
description: "Check structural validity of OpenAPI spec content",
21+
parameters: s.object({ content: s.string }),
22+
handler({ content }) {
23+
const issues: Array<{ type: "error" | "warning" | "info"; message: string; path?: string }> = [];
24+
try {
25+
const spec = JSON.parse(content);
26+
if (!spec.openapi) issues.push({ type: "error", message: "Missing required field 'openapi'", path: "openapi" });
27+
if (!spec.info) issues.push({ type: "error", message: "Missing required field 'info'", path: "info" });
28+
else {
29+
if (!spec.info.title) issues.push({ type: "error", message: "Missing info.title", path: "info.title" });
30+
if (!spec.info.version) issues.push({ type: "error", message: "Missing info.version", path: "info.version" });
31+
}
32+
if (!spec.paths) issues.push({ type: "error", message: "Missing required field 'paths'", path: "paths" });
33+
} catch {
34+
issues.push({ type: "warning", message: "Content is not valid JSON — may be YAML or empty" });
35+
}
36+
return { issues };
37+
},
38+
}),
39+
],
40+
output: s.object({
41+
valid: s.boolean,
42+
issues: s.array(s.object({
43+
type: s.enum("error", "warning", "info"),
44+
message: s.string,
45+
path: s.optional(s.string),
46+
})),
47+
issueCount: s.int,
48+
}),
49+
});
50+
51+
export default openapiSpecValidator;
52+
```
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# 162 - API Endpoint Extractor
2+
3+
```rig
4+
import { agent, p, s } from "rig";
5+
6+
// Agent role: classify purpose of each API endpoint found in route files.
7+
const endpointClassifier = agent({
8+
name: "endpointClassifier",
9+
model: "nano",
10+
input: s.object({ route: s.string }),
11+
instructions: p`Classify this API route: return purpose and description.`,
12+
output: s.object({
13+
purpose: s.enum("read", "write", "delete", "auth", "health", "other"),
14+
description: s.string,
15+
}),
16+
});
17+
18+
// Agent role: extract API endpoints from route/controller files and classify each by purpose.
19+
const apiEndpointExtractor = agent({
20+
model: "small",
21+
agents: { endpointClassifier },
22+
instructions: p`Extract and classify all API endpoints in this workspace.
23+
24+
Route patterns found:
25+
${p.bash("grep -rn '\\.(get|post|put|delete|patch)(' --include='*.ts' --include='*.js' . 2>/dev/null | grep -v node_modules | head -40")}
26+
27+
For each unique route pattern found, delegate to the endpointClassifier subagent to determine its purpose. Return all discovered endpoints with method, path, and purpose. Return only the declared output.`,
28+
output: s.object({
29+
endpoints: s.array(s.object({
30+
method: s.enum("GET", "POST", "PUT", "DELETE", "PATCH"),
31+
path: s.string,
32+
purpose: s.enum("read", "write", "delete", "auth", "health", "other"),
33+
})),
34+
totalCount: s.int,
35+
hasCrud: s.boolean,
36+
}),
37+
});
38+
39+
export default apiEndpointExtractor;
40+
```
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# 163 - Source File Annotator
2+
3+
```rig
4+
import { agent, p, s } from "rig";
5+
6+
// Agent role: generate JSDoc annotations for TypeScript source files and write annotated output.
7+
const jsDocAnnotator = agent({
8+
name: "jsDocAnnotator",
9+
model: "nano",
10+
input: s.object({ filePath: s.path, content: s.string }),
11+
instructions: p`Add JSDoc comments to every exported function, class, and interface in the TypeScript source. Return only the declared output.`,
12+
output: s.object({
13+
annotatedContent: s.string,
14+
annotationsAdded: s.int,
15+
}),
16+
});
17+
18+
// Agent role: annotate TypeScript source files with JSDoc comments using a subagent and write results.
19+
const sourceFileAnnotator = agent({
20+
model: "small",
21+
agents: { jsDocAnnotator },
22+
instructions: p`Annotate TypeScript source files with JSDoc comments.
23+
24+
TypeScript files in workspace:
25+
${p.bash("find . -name '*.ts' -not -path '*/node_modules/*' -not -name '*.test.ts' -not -name '*.d.ts' | head -20")}
26+
27+
For each file, read its content, delegate to the jsDocAnnotator subagent, then write the annotated version back using p.write. Track which files were processed successfully and which were skipped.
28+
29+
${p.write("annotated-summary.md", "# Annotation Summary\n<!-- will be filled by agent -->")}
30+
31+
Return only the declared output.`,
32+
output: s.object({
33+
filesProcessed: s.int,
34+
filesSkipped: s.int,
35+
totalAnnotations: s.int,
36+
processedPaths: s.array(s.path),
37+
}),
38+
});
39+
40+
export default sourceFileAnnotator;
41+
```
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# 164 - Stale Dependency Detector
2+
3+
```rig
4+
import { agent, defineTool, p, s } from "rig";
5+
import { repair } from "rig/addons";
6+
7+
// Agent role: detect stale npm dependencies by comparing installed versions with latest published versions.
8+
const staleDependencyDetector = agent({
9+
model: "small",
10+
instructions: p`Identify stale npm dependencies that need updating.
11+
12+
package.json contents:
13+
${p.read("package.json")}
14+
15+
npm outdated results:
16+
${p.bash("npm outdated --json 2>/dev/null || echo '{}'")}
17+
18+
Use the classifyDrift tool for each outdated package to classify the version drift level. Determine the overall risk for the project. Return only the declared output.`,
19+
tools: [
20+
defineTool("classifyDrift", {
21+
description: "Classify version drift between current and latest version",
22+
parameters: s.object({ current: s.string, latest: s.string }),
23+
handler({ current, latest }) {
24+
const parse = (v: string) => v.replace(/^[^0-9]*/, "").split(".").map(Number);
25+
const [cMaj, cMin] = parse(current);
26+
const [lMaj, lMin] = parse(latest);
27+
if (lMaj > cMaj) return { driftLevel: "major" as const };
28+
if (lMin > cMin) return { driftLevel: "minor" as const };
29+
return { driftLevel: "patch" as const };
30+
},
31+
}),
32+
],
33+
addons: [repair()],
34+
output: s.object({
35+
packages: s.array(s.object({
36+
name: s.string,
37+
current: s.string,
38+
latest: s.string,
39+
driftLevel: s.enum("major", "minor", "patch", "ok"),
40+
})),
41+
overallRisk: s.enum("safe", "moderate", "critical"),
42+
staleCount: s.int,
43+
}),
44+
});
45+
46+
export default staleDependencyDetector;
47+
```
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# 165 - Git Tag Timeline
2+
3+
```rig
4+
import { agent, p, s } from "rig";
5+
6+
// Agent role: summarize commits and authors between two git tags.
7+
const timelineSummarizer = agent({
8+
name: "timelineSummarizer",
9+
model: "nano",
10+
input: s.object({
11+
tag: s.string,
12+
prevTag: s.string,
13+
commits: s.string,
14+
}),
15+
instructions: p`Summarize the git log between two tags and return structured timeline entry.`,
16+
output: s.object({
17+
tag: s.string,
18+
date: s.string,
19+
commitCount: s.int,
20+
topAuthors: s.array(s.string),
21+
}),
22+
});
23+
24+
// Agent role: build a release timeline from git tags showing commit counts and top authors per tag.
25+
const gitTagTimeline = agent({
26+
model: "small",
27+
agents: { timelineSummarizer },
28+
instructions: p`Build a git tag release timeline.
29+
30+
Git tags (sorted by date):
31+
${p.bash("git tag --sort=version:refname 2>/dev/null | tail -20 || echo 'No tags found'")}
32+
33+
Recent commit log:
34+
${p.bash("git log --oneline --format='%h %an %ad %s' --date=short -40 2>/dev/null || echo 'No commits'")}
35+
36+
For each tag pair, delegate to the timelineSummarizer subagent with the relevant commit log section. Also check for unreleased commits after the latest tag. Return only the declared output.`,
37+
output: s.object({
38+
timeline: s.array(s.object({
39+
tag: s.string,
40+
date: s.string,
41+
commitCount: s.int,
42+
topAuthors: s.array(s.string),
43+
})),
44+
hasUnreleasedCommits: s.boolean,
45+
totalTags: s.int,
46+
}),
47+
});
48+
49+
export default gitTagTimeline;
50+
```
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# 166 - Env Variable Type Inferrer
2+
3+
```rig
4+
import { agent, defineTool, p, s } from "rig";
5+
6+
// Agent role: infer the type and purpose of each variable defined in a .env.example file.
7+
const envVariableTypeInferrer = agent({
8+
model: "small",
9+
instructions: p`Infer the type and purpose of each environment variable in the project.
10+
11+
.env.example contents:
12+
${p.readOptional(".env.example")}
13+
14+
.env.sample contents:
15+
${p.readOptional(".env.sample")}
16+
17+
For each KEY=value pair found, use the inferType tool to classify the value type. Produce a description of each variable's likely purpose based on its name. Determine whether all variables appear to be documented (have a non-empty value or comment). Return only the declared output.`,
18+
tools: [
19+
defineTool("inferType", {
20+
description: "Infer the type of an environment variable value",
21+
parameters: s.object({ key: s.string, value: s.string }),
22+
handler({ value }) {
23+
const v = value.trim();
24+
if (/^https?:\/\//i.test(v)) return { type: "url" as const };
25+
if (/^\/|^\.\.?\//.test(v)) return { type: "path" as const };
26+
if (/^(true|false)$/i.test(v)) return { type: "boolean" as const };
27+
if (/^\d+(\.\d+)?$/.test(v)) return { type: "number" as const };
28+
if (v === "") return { type: "unknown" as const };
29+
return { type: "string" as const };
30+
},
31+
}),
32+
],
33+
output: s.object({
34+
variables: s.record(s.object({
35+
type: s.enum("string", "number", "boolean", "url", "path", "unknown"),
36+
description: s.string,
37+
})),
38+
totalCount: s.int,
39+
allDocumented: s.boolean,
40+
}),
41+
});
42+
43+
export default envVariableTypeInferrer;
44+
```
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# 167 - Makefile Target Extractor
2+
3+
```rig
4+
import { agent, defineTool, p, s } from "rig";
5+
import { repair } from "rig/addons";
6+
7+
// Agent role: extract and classify Makefile targets into phony vs real file targets with descriptions.
8+
const makefileTargetExtractor = agent({
9+
model: "small",
10+
instructions: p`Extract and classify all targets from the Makefile in this workspace.
11+
12+
Makefile contents:
13+
${p.readOptional("Makefile")}
14+
15+
Use the parseTargets tool to extract target names, then classify each. Phony targets are declared with .PHONY or have no corresponding file. Check if each target has an adjacent comment (##) for help text. Return only the declared output.`,
16+
tools: [
17+
defineTool("parseTargets", {
18+
description: "Parse Makefile content and extract target names with their type",
19+
parameters: s.object({ content: s.string }),
20+
handler({ content }) {
21+
const phonyTargets = new Set<string>();
22+
const phonyMatch = content.match(/^\.PHONY\s*:(.*)/gm) || [];
23+
for (const line of phonyMatch) {
24+
line.replace(/^\.PHONY\s*:/, "").trim().split(/\s+/).forEach(t => phonyTargets.add(t));
25+
}
26+
const targetLines = content.match(/^([a-zA-Z0-9_-]+)\s*:/gm) || [];
27+
const targets = targetLines
28+
.map(l => l.replace(/:.*/, "").trim())
29+
.filter(t => t && t !== ".PHONY");
30+
return { targets, phonyTargets: [...phonyTargets] };
31+
},
32+
}),
33+
],
34+
addons: [repair()],
35+
output: s.object({
36+
targets: s.array(s.object({
37+
name: s.string,
38+
isPhony: s.boolean,
39+
hasHelp: s.boolean,
40+
description: s.optional(s.string),
41+
})),
42+
totalCount: s.int,
43+
phonyCount: s.int,
44+
}),
45+
});
46+
47+
export default makefileTargetExtractor;
48+
```

0 commit comments

Comments
 (0)