Skip to content

Commit c9fc374

Browse files
Add 10 rig samples 341–350 (2026-08-01) (#331)
1 parent 8cdbf65 commit c9fc374

10 files changed

Lines changed: 427 additions & 0 deletions
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# 341 - Release Note Enricher
2+
3+
```rig
4+
import { agent, p, s, defineTool, repair, steering } from "rig";
5+
6+
const lookupTicketMetadata = defineTool("lookupTicketMetadata", {
7+
description: "Extract ticket references (#NNN or PROJ-NNN) from text and return metadata",
8+
parameters: s.object({ text: s.string }),
9+
handler({ text }) {
10+
const githubRefs = [...text.matchAll(/#(\d+)/g)].map((m) => `#${m[1]}`);
11+
const jiraRefs = [...text.matchAll(/\b([A-Z]+-\d+)\b/g)].map((m) => m[1]);
12+
return { githubRefs, jiraRefs, all: [...githubRefs, ...jiraRefs] };
13+
},
14+
});
15+
16+
// Agent role: enrich raw release notes by extracting ticket references, grouping into sections, assigning a risk label, and listing unresolved references.
17+
const releaseNoteEnricher = agent({
18+
model: "small",
19+
input: s.object({ rawNotes: s.string }),
20+
instructions: p`Use the lookupTicketMetadata tool to find all ticket references (#NNN, PROJ-NNN) in the raw release notes from the input. Group the notes into logical sections (e.g., Features, Bug Fixes, Breaking Changes). Assign a riskLabel based on content severity. List any ticket references that appear to be missing or unresolvable.`,
21+
output: s.object({
22+
sections: s.array(s.string),
23+
riskLabel: s.enum("low", "medium", "high", "critical"),
24+
missingReferences: s.array(s.string),
25+
}),
26+
tools: [lookupTicketMetadata],
27+
maxTurns: 5,
28+
addons: [steering(), repair()],
29+
});
30+
31+
export default releaseNoteEnricher;
32+
```
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# 342 - Docs Refactor Coordinator
2+
3+
```rig
4+
import { agent, p, s } from "rig";
5+
6+
// Agent role: extract every API name, function signature, and method reference from the documentation.
7+
const apiExtractor = agent({
8+
name: "apiExtractor",
9+
model: "small",
10+
instructions: p`Read: ${p.read("README.md")}. Extract all API names, function signatures, and method references. Return a flat list of unique identifiers.`,
11+
output: s.array(s.string),
12+
});
13+
14+
// Agent role: rewrite documentation prose to be clearer and more concise.
15+
const proseCleanup = agent({
16+
name: "proseCleanup",
17+
model: "small",
18+
instructions: p`Read: ${p.read("README.md")}. Rewrite the prose sections for clarity and conciseness without altering technical accuracy. Return the improved markdown text.`,
19+
output: s.string,
20+
});
21+
22+
// Agent role: coordinate API extraction and prose cleanup, then write a refactored docs file.
23+
const docsRefactorCoordinator = agent({
24+
model: "small",
25+
instructions: p`Delegate to apiExtractor and proseCleanup subagents. Combine their outputs: extractedApis from apiExtractor and improved prose from proseCleanup. Write the refactored content to ${p.write("docs/refactored.md", "REFACTORED_CONTENT")}. Count the number of substantive prose changes made.`,
26+
output: s.object({
27+
extractedApis: s.array(s.string),
28+
changesApplied: s.int,
29+
outputPath: s.path,
30+
}),
31+
agents: { apiExtractor, proseCleanup },
32+
});
33+
34+
export default docsRefactorCoordinator;
35+
```
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# 343 - Dockerfile Layer Analyzer
2+
3+
```rig
4+
import { agent, p, s, defineTool, repair } from "rig";
5+
6+
const parseLayer = defineTool("parseLayer", {
7+
description: "Classify a Dockerfile instruction line for cache-friendliness and weight",
8+
parameters: s.object({ line: s.string }),
9+
handler({ line }) {
10+
const trimmed = line.trim();
11+
const spaceIdx = trimmed.indexOf(" ");
12+
const instruction = spaceIdx >= 0 ? trimmed.slice(0, spaceIdx).toUpperCase() : trimmed.toUpperCase();
13+
const args = spaceIdx >= 0 ? trimmed.slice(spaceIdx + 1) : "";
14+
const isHeavy = instruction === "RUN" && /apt-get|npm install|pip install|yarn|apk add/.test(args);
15+
const cacheable = (instruction === "COPY" || instruction === "ADD") && /package\.json|requirements\.txt|go\.mod/.test(args);
16+
const tip = isHeavy ? "Combine adjacent RUN commands to reduce layers" as const
17+
: cacheable ? null
18+
: instruction === "COPY" ? "Copy dependency manifests first for better cache" as const
19+
: null;
20+
return { instruction, cacheable, isHeavy, tip };
21+
},
22+
});
23+
24+
// Agent role: analyze Dockerfile layers for cache efficiency and suggest optimizations.
25+
const dockerfileLayerAnalyzer = agent({
26+
model: "small",
27+
instructions: p`Dockerfile content: ${p.readOptional("Dockerfile", "# no Dockerfile found")}
28+
29+
Parse each non-empty, non-comment instruction line. Call parseLayer for each. Count totalLayers. Set optimizable to true if any layer has isHeavy true or a non-null tip. Collect all non-null tips into suggestions.`,
30+
output: s.object({
31+
layers: s.array(s.object({
32+
instruction: s.string,
33+
cacheable: s.boolean,
34+
isHeavy: s.boolean,
35+
tip: s.optional(s.string),
36+
})),
37+
totalLayers: s.int,
38+
optimizable: s.boolean,
39+
suggestions: s.array(s.string),
40+
}),
41+
tools: [parseLayer],
42+
addons: [repair()],
43+
maxTurns: 5,
44+
});
45+
46+
export default dockerfileLayerAnalyzer;
47+
```
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# 344 - Git Worktree Status Reporter
2+
3+
```rig
4+
import { agent, p, s, defineTool, repair } from "rig";
5+
6+
const classifyWorktree = defineTool("classifyWorktree", {
7+
description: "Classify a git worktree entry as clean, dirty, bare, or detached",
8+
parameters: s.object({ path: s.string, branch: s.string, statusOutput: s.string }),
9+
handler({ branch, statusOutput }) {
10+
if (branch === "(bare)") return { status: "bare" as const };
11+
if (branch.startsWith("(HEAD detached")) return { status: "detached" as const };
12+
if (statusOutput.trim().length > 0) return { status: "dirty" as const };
13+
return { status: "clean" as const };
14+
},
15+
});
16+
17+
// Agent role: report the status of all git worktrees in the repository.
18+
const gitWorktreeStatusReporter = agent({
19+
model: "small",
20+
instructions: p`Worktree list: ${p.bash("git worktree list --porcelain")}
21+
22+
Parse each worktree block (separated by blank lines). For each worktree, call classifyWorktree with its path, branch, and an empty statusOutput (use "dirty" heuristic based on porcelain output if available). Return the list of worktrees with their statuses, total count, and whether all are clean.`,
23+
output: s.object({
24+
worktrees: s.array(s.object({
25+
path: s.path,
26+
branch: s.string,
27+
status: s.enum("clean", "dirty", "bare", "detached"),
28+
})),
29+
totalWorktrees: s.int,
30+
allClean: s.boolean,
31+
}),
32+
tools: [classifyWorktree],
33+
addons: [repair()],
34+
maxTurns: 4,
35+
});
36+
37+
export default gitWorktreeStatusReporter;
38+
```
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# 345 - NPM Audit Simplifier
2+
3+
```rig
4+
import { agent, p, s, defineTool, repair } from "rig";
5+
6+
const classifyVuln = defineTool("classifyVuln", {
7+
description: "Classify an npm vulnerability as direct or transitive and produce a recommendation",
8+
parameters: s.object({
9+
name: s.string,
10+
severity: s.string,
11+
isDirect: s.boolean,
12+
}),
13+
handler({ name, severity, isDirect }) {
14+
const sev = severity.toLowerCase();
15+
const scope = isDirect ? "direct" : "transitive";
16+
const recommendation =
17+
sev === "critical" ? `Upgrade ${name} immediately (${scope} dependency)` :
18+
sev === "high" ? `Schedule upgrade for ${name} (${scope} dependency)` :
19+
isDirect ? `Review ${name} — ${sev} severity direct dependency` :
20+
`Monitor ${name} — ${sev} severity transitive dependency`;
21+
return { scope, recommendation };
22+
},
23+
});
24+
25+
// Agent role: simplify npm audit output into an actionable vulnerability summary.
26+
const npmAuditSimplifier = agent({
27+
model: "small",
28+
instructions: p`npm audit output: ${p.bash("npm audit --json 2>/dev/null || echo '{\"vulnerabilities\":{}}'")
29+
}
30+
31+
Parse the audit JSON. For each vulnerability, call classifyVuln with its name, severity, and whether it is a direct dependency. Count criticalCount and highCount. Choose action: urgent if any critical, scheduled if any high, monitor if any moderate/low, none if no vulnerabilities. Write a one-line summary.`,
32+
output: s.object({
33+
vulnerabilities: s.array(s.object({
34+
name: s.string,
35+
severity: s.string,
36+
isDirect: s.boolean,
37+
recommendation: s.string,
38+
})),
39+
criticalCount: s.int,
40+
highCount: s.int,
41+
summary: s.string,
42+
action: s.enum("urgent", "scheduled", "monitor", "none"),
43+
}),
44+
tools: [classifyVuln],
45+
addons: [repair()],
46+
maxTurns: 4,
47+
});
48+
49+
export default npmAuditSimplifier;
50+
```
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# 346 - JS AST Node Counter
2+
3+
```rig
4+
import { agent, p, s, defineTool } from "rig";
5+
import { readFile } from "node:fs/promises";
6+
7+
const countAstNodes = defineTool("countAstNodes", {
8+
description: "Count function/arrow/class/import/export patterns in a TypeScript file using regex heuristics",
9+
parameters: s.object({ filePath: s.path }),
10+
handler: async ({ filePath }: { filePath: string }) => {
11+
let source = "";
12+
try { source = await readFile(filePath, "utf8"); } catch { return { functions: 0, arrows: 0, classes: 0, imports: 0, exports: 0 }; }
13+
const functions = (source.match(/\bfunction\s+\w+/g) || []).length;
14+
const arrows = (source.match(/=>\s*[{(]/g) || []).length;
15+
const classes = (source.match(/\bclass\s+\w+/g) || []).length;
16+
const imports = (source.match(/^import\b/gm) || []).length;
17+
const exports = (source.match(/^export\b/gm) || []).length;
18+
return { functions, arrows, classes, imports, exports };
19+
},
20+
});
21+
22+
// Agent role: count AST-like node patterns in each TypeScript source file and identify the most complex file.
23+
const jsAstNodeCounter = agent({
24+
model: "small",
25+
instructions: p`TypeScript files in this workspace: ${p.glob("src/**/*.ts")}
26+
27+
For each file path listed, call countAstNodes to get pattern counts. Build a record keyed by file path. Sum all functions across files into totalFunctions. Set mostComplexFile to the file with the highest combined function+arrow+class count (omit if no files found).`,
28+
output: s.object({
29+
files: s.record(s.object({
30+
functions: s.int,
31+
arrows: s.int,
32+
classes: s.int,
33+
imports: s.int,
34+
exports: s.int,
35+
})),
36+
totalFunctions: s.int,
37+
mostComplexFile: s.optional(s.path),
38+
}),
39+
tools: [countAstNodes],
40+
maxTurns: 6,
41+
});
42+
43+
export default jsAstNodeCounter;
44+
```
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# 347 - Python Requirements Risk Mapper
2+
3+
```rig
4+
import { agent, p, s, defineTool, steering } from "rig";
5+
6+
const classifyPackageRisk = defineTool("classifyPackageRisk", {
7+
description: "Classify a Python package's risk level based on name and version heuristics",
8+
parameters: s.object({ name: s.string, version: s.string }),
9+
handler({ name, version }) {
10+
const knownLegacy = ["django", "flask", "requests", "urllib3", "cryptography", "pillow", "numpy"];
11+
const versionParts = version.split(".").map(Number);
12+
const major = versionParts[0] ?? 0;
13+
const isOutdated = major === 0 || (knownLegacy.includes(name.toLowerCase()) && major < 2);
14+
const riskLevel: "low" | "medium" | "high" =
15+
isOutdated && knownLegacy.includes(name.toLowerCase()) ? "high" as const
16+
: isOutdated ? "medium" as const
17+
: "low" as const;
18+
return { isOutdated, riskLevel };
19+
},
20+
});
21+
22+
// Agent role: map Python requirements to risk levels and recommend an action.
23+
const pythonRequirementsRiskMapper = agent({
24+
model: "small",
25+
instructions: p`requirements.txt: ${p.readOptional("requirements.txt", "# no requirements.txt found")}
26+
installed packages: ${p.bash("pip list --format=json 2>/dev/null || echo '[]'")}
27+
28+
For each package found in requirements.txt or the installed list, call classifyPackageRisk with its name and version. Build a packages record. Count riskyCount (medium or high risk). Choose recommendedAction: audit if any high-risk, review if any medium-risk, ok otherwise.`,
29+
output: s.object({
30+
packages: s.record(s.object({
31+
version: s.string,
32+
isOutdated: s.boolean,
33+
riskLevel: s.enum("low", "medium", "high"),
34+
})),
35+
totalPackages: s.int,
36+
riskyCount: s.int,
37+
recommendedAction: s.enum("audit", "review", "ok"),
38+
}),
39+
tools: [classifyPackageRisk],
40+
addons: [steering()],
41+
maxTurns: 5,
42+
});
43+
44+
export default pythonRequirementsRiskMapper;
45+
```
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# 348 - Git Merge Complexity Scorer
2+
3+
```rig
4+
import { agent, p, s, defineTool, repair } from "rig";
5+
6+
const scoreMergeComplexity = defineTool("scoreMergeComplexity", {
7+
description: "Classify a git merge commit as simple, moderate, or complex based on files changed",
8+
parameters: s.object({ hash: s.string, message: s.string, filesChanged: s.int }),
9+
handler({ hash, message, filesChanged }) {
10+
const complexity: "simple" | "moderate" | "complex" =
11+
filesChanged <= 3 ? "simple" as const
12+
: filesChanged <= 10 ? "moderate" as const
13+
: "complex" as const;
14+
return { hash, message, filesChanged, complexity };
15+
},
16+
});
17+
18+
// Agent role: score git merge commits by complexity based on the number of files changed.
19+
const gitMergeComplexityScorer = agent({
20+
model: "small",
21+
instructions: p`Recent merge commits: ${p.bash("git log --merges --oneline -20 2>/dev/null || echo ''")}
22+
Merge diff stats: ${p.bash("git log --merges --oneline -20 --format='%H %s' 2>/dev/null | head -20 | while read hash msg; do count=$(git show --stat $hash 2>/dev/null | grep -E 'files? changed' | grep -oE '[0-9]+ files? changed' | grep -oE '^[0-9]+' || echo 0); echo \"$hash|$count|$msg\"; done")}
23+
24+
For each merge commit, call scoreMergeComplexity with its hash, message, and filesChanged count. Count totalMerges and complexMergeCount. Set mostComplexMerge to the hash with the highest filesChanged (omit if no merges).`,
25+
output: s.object({
26+
merges: s.array(s.object({
27+
hash: s.string,
28+
message: s.string,
29+
filesChanged: s.int,
30+
complexity: s.enum("simple", "moderate", "complex"),
31+
})),
32+
totalMerges: s.int,
33+
complexMergeCount: s.int,
34+
mostComplexMerge: s.optional(s.string),
35+
}),
36+
tools: [scoreMergeComplexity],
37+
addons: [repair()],
38+
maxTurns: 5,
39+
});
40+
41+
export default gitMergeComplexityScorer;
42+
```
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# 349 - TS Reexport Chain Tracer
2+
3+
```rig
4+
import { agent, p, s, defineTool, steering } from "rig";
5+
import { readFile } from "node:fs/promises";
6+
7+
const traceReexports = defineTool("traceReexports", {
8+
description: "Scan a TypeScript file for re-export patterns and return referenced files and symbols",
9+
parameters: s.object({ filePath: s.path }),
10+
handler: async ({ filePath }: { filePath: string }) => {
11+
let source = "";
12+
try { source = await readFile(filePath, "utf8"); } catch { return { references: [] }; }
13+
const starMatches = [...source.matchAll(/export\s+\*\s+from\s+['"]([^'"]+)['"]/g)];
14+
const namedMatches = [...source.matchAll(/export\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/g)];
15+
const references = [
16+
...starMatches.map((m) => ({ file: m[1], symbols: ["*"] })),
17+
...namedMatches.map((m) => ({ file: m[2], symbols: m[1].split(",").map((s: string) => s.trim()) })),
18+
];
19+
return { references };
20+
},
21+
});
22+
23+
// Agent role: trace the TypeScript re-export chain starting from an entry file and detect circular re-exports.
24+
const tsReexportChainTracer = agent({
25+
model: "small",
26+
input: s.object({ entryFile: s.path }),
27+
instructions: p`Starting from the entryFile in the input, call traceReexports to discover re-export chains. Follow the chain up to 3 levels deep. Build a list of chain entries (file + symbols). Detect if any file appears more than once in the chain (circularDetected). Report the total chain depth.`,
28+
output: s.object({
29+
chain: s.array(s.object({
30+
file: s.string,
31+
symbols: s.array(s.string),
32+
})),
33+
chainDepth: s.int,
34+
circularDetected: s.boolean,
35+
}),
36+
tools: [traceReexports],
37+
addons: [steering()],
38+
maxTurns: 6,
39+
});
40+
41+
export default tsReexportChainTracer;
42+
```

0 commit comments

Comments
 (0)