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
53 changes: 53 additions & 0 deletions skills/rig/samples/411-package-json-completeness-scorer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# 411 - Package Json Completeness Scorer

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

const scoreField = defineTool("scoreField", {
description: "Score a package.json field for completeness and return its category.",
parameters: s.object({ fieldName: s.string, value: s.unknown }),
handler: ({ fieldName, value }: { fieldName: string; value: unknown }) => {
const required = ["name", "version"];
const recommended = ["description", "main", "scripts", "license", "repository", "keywords"];
const category = required.includes(fieldName)
? ("required" as const)
: recommended.includes(fieldName)
? ("recommended" as const)
: ("optional" as const);
const present = value !== undefined && value !== null && value !== "";
const score = present ? 1 : 0;
return { present, category, score };
},
});

// Agent role: score the completeness of package.json against required and recommended fields.
const packageJsonCompletenessScorer = agent({
model: "small",
instructions: p`Score the completeness of package.json.

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

Required fields: name, version.
Recommended fields: description, main, scripts, license, repository, keywords.
Optional fields: author, bugs, homepage, engines, files, types.

For each field from all three categories, call scoreField with fieldName and value (or undefined if absent).
Build fields record. totalScore = sum of scores. maxScore = total fields. completenessPercent = (totalScore / maxScore) * 100. missingRequired = required fields where present is false.`,
output: s.object({
fields: s.record(s.object({
present: s.boolean,
category: s.enum("required", "recommended", "optional"),
score: s.number,
})),
totalScore: s.number,
maxScore: s.number,
completenessPercent: s.number,
missingRequired: s.array(s.string),
}),
tools: [scoreField],
addons: [repair()],
});

export default packageJsonCompletenessScorer;
```
54 changes: 54 additions & 0 deletions skills/rig/samples/412-parallel-branch-analysis-workflow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# 412 - Parallel Branch Analysis Workflow

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

const branchMetric = s.object({
totalBranches: 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.

[/codebase-design] This sample is near-duplicate of the existing 360-parallel-branch-analysis-workflow.md, which covers the same domain (parallel branch + commit analysis, same health enum). The key difference — using Promise.all instead of parallel() — is noted in the PR description as a typecheck workaround, but that makes this sample teach a weaker pattern.

💡 Options
  1. Differentiate the domain: rename and repurpose this sample to something distinct (e.g. tag analysis, contributor frequency).
  2. Fix and replace 360: if the parallel() helper now supports heterogeneous types, update 360 instead of adding a workaround sample.
  3. Delete this sample: if 360 already covers the pattern, remove this one to keep the sample set non-redundant.

Samples are learning material — duplicates with subtly worse patterns are actively harmful.

staleCount: s.number,
activeBranches: s.number,
});

const commitMetric = s.object({
totalCommits: s.number,
activeDays: s.number,
averagePerDay: s.number,
});

// Agent role: measure branch count and identify stale branches.
const branchHealthAgent = agent({
model: "small",
output: branchMetric,
instructions: p`Count total, stale (>30 days old), and active branches.
${p.bash("git branch -a 2>/dev/null || echo ''")}
${p.bash("git for-each-ref --format='%(refname:short) %(committerdate:relative)' refs/heads/ 2>/dev/null || echo ''")}`,
});

// Agent role: measure commit frequency over the last 30 days.
const commitFrequencyAgent = agent({
model: "small",
output: commitMetric,
instructions: p`Count total commits in last 30 days and estimate active days and average per day.
${p.bash("git log --oneline --since='30 days ago' 2>/dev/null || echo ''")}`,
});

// Workflow role: run branch health and commit frequency agents in parallel, then classify overall health.
const parallelBranchAnalysis = workflow({
meta: { name: "parallelBranchAnalysis", description: "Parallel branch analysis", phases: ["Analyze", "Rate"] },
body: async ({ call, phase }) => {
phase("Analyze");
const [branchHealth, commitFrequency] = await Promise.all([
call(branchHealthAgent, "analyze"),
call(commitFrequencyAgent, "analyze"),
]);
phase("Rate");
const overallHealth = await call.json(
`Branch health: ${JSON.stringify(branchHealth)}. Commit frequency: ${JSON.stringify(commitFrequency)}. Classify overall health as "healthy", "needs-attention", or "critical".`,
s.enum("healthy", "needs-attention", "critical"),
);
return { branchHealth, commitFrequency, overallHealth };
},
});

export default parallelBranchAnalysis;
```
46 changes: 46 additions & 0 deletions skills/rig/samples/413-lockfile-integrity-checker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# 413 - Lockfile Integrity Checker

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

const verifyLockEntry = defineTool("verifyLockEntry", {
description: "Verify a package entry in package-lock.json has required integrity and resolved fields.",
parameters: s.object({ packageName: s.string, entryJson: s.string }),
handler({ entryJson }: { packageName: string; entryJson: string }) {
try {
const entry = JSON.parse(entryJson) as Record<string, unknown>;
const hasIntegrity = typeof entry["integrity"] === "string" && (entry["integrity"] as string).length > 0;
const hasResolved = typeof entry["resolved"] === "string" && (entry["resolved"] as string).length > 0;
const valid = hasIntegrity && hasResolved;
return { valid, hasIntegrity, hasResolved };
} catch {
return { valid: false, hasIntegrity: false, hasResolved: false };
}
},
});

// Agent role: check package-lock.json entries for integrity and resolved fields.
const lockfileIntegrityChecker = agent({
model: "small",
instructions: p`Check integrity of package-lock.json entries.

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

For each package in the "packages" or "dependencies" section, call verifyLockEntry with the package name and its JSON entry (as a string). Build the packages record. mismatchCount = number of invalid entries. isClean = mismatchCount === 0. totalChecked = total packages checked.`,
output: s.object({
packages: s.record(s.object({
valid: s.boolean,
hasIntegrity: s.boolean,
hasResolved: s.boolean,
})),
mismatchCount: s.number,
isClean: s.boolean,
totalChecked: s.number,
}),
tools: [verifyLockEntry],
addons: [repair()],
});

export default lockfileIntegrityChecker;
```
47 changes: 47 additions & 0 deletions skills/rig/samples/414-graphql-schema-type-extractor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# 414 - Graphql Schema Type Extractor

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

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] Duplicate import from "rig"steering should be merged into the first import on line 4.

💡 Suggested fix
import { agent, defineTool, p, s, steering } from "rig";

Two separate import statements from the same module is inconsistent with every other sample in this repo. Merge them.


const extractGraphqlTypes = defineTool("extractGraphqlTypes", {
description: "Extract type declarations from a GraphQL schema file.",
parameters: s.object({ filePath: s.path }),
async handler({ filePath }: { filePath: string }) {
const { readFile } = await import("node:fs/promises");
const content = await readFile(filePath, "utf8");
const pattern = /^(type|input|enum|interface|union)\s+(\w+)[^{]*\{([^}]*)\}/gm;
const results: Record<string, { kind: string; fieldCount: number; fields: string[]; sourceFile: string }> = {};
let match: RegExpExecArray | null;
while ((match = pattern.exec(content)) !== null) {
const kind = match[1];
const name = match[2];
const body = match[3];
const fields = body.split("\n").map((l: string) => l.trim()).filter((l: string) => l.length > 0 && !l.startsWith("#"));
results[name] = { kind, fieldCount: fields.length, fields, sourceFile: filePath };
}

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 regex /^(type|...)\s+\w+[^{]*\{([^}]*)\}/gm only matches type bodies that fit on one line — any multi-line GraphQL type body silently produces no match. Real schemas almost always span multiple lines, so this tool will return empty results for typical inputs.

💡 Suggested fix

Remove the m flag and use a dotall approach, or use a character-class that crosses newlines:

// Replace [^}]* with [\s\S]*? to span lines
const pattern = /^(type|input|enum|interface|union)\s+(\w+)[^{]*\{([\s\S]*?)\}/gm;

This matches the closing } of multi-line type bodies. Note: nested braces (e.g. inline input types) will still confuse the regex — that's acceptable for a sample, but worth a comment.

return results;
},
});

// Agent role: discover and extract all GraphQL type declarations across .graphql files.
const graphqlSchemaTypeExtractor = agent({
model: "small",
instructions: p`Extract GraphQL type declarations from .graphql files.

Files found:
${p.bash("find . -name '*.graphql' -not -path '*/node_modules/*' 2>/dev/null || echo '(none)'")}

For each .graphql file found, call extractGraphqlTypes with the file path. Merge all results into a single record keyed by type name.`,
output: s.record(s.object({
kind: s.enum("type", "input", "enum", "interface", "union"),
fieldCount: s.number,
fields: s.array(s.string),
sourceFile: s.string,
})),
tools: [extractGraphqlTypes],
addons: [steering()],
});

export default graphqlSchemaTypeExtractor;
```
51 changes: 51 additions & 0 deletions skills/rig/samples/415-os-path-structure-analyzer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# 415 - Os Path Structure Analyzer

```rig
import { agent, defineTool, p, repair, s } from "rig";
import { basename } from "node:path";

const classifyDirectory = defineTool("classifyDirectory", {
description: "Classify a directory path into a category based on its basename.",
parameters: s.object({ dirPath: s.path, rootDir: s.string }),
handler({ dirPath, rootDir }: { dirPath: string; rootDir: string }) {
const name = basename(dirPath);
const depth = dirPath.replace(rootDir, "").split("/").filter(Boolean).length;
const srcNames = new Set(["src", "lib", "source"]);
const testNames = new Set(["test", "tests", "__tests__", "spec", "specs"]);
const configNames = new Set(["config", "configs", "conf", ".config"]);
const buildNames = new Set(["dist", "build", "out", "output", ".next", ".cache"]);
const vendorNames = new Set(["node_modules", "vendor", "third_party"]);
const category = srcNames.has(name) ? "src"
: testNames.has(name) ? "test"
: configNames.has(name) ? "config"
: buildNames.has(name) ? "build"
: vendorNames.has(name) ? "vendor"
: "other";
return { depth, category };
},
});

// Agent role: analyze the directory structure of a given root path and classify each directory.
const osPathStructureAnalyzer = agent({
model: "small",
input: s.object({ rootDir: s.string }),
instructions: p`Analyze the directory structure of the given rootDir.

Directories found:
${p.bash("find . -maxdepth 3 -type d -not -path '*/node_modules/*' 2>/dev/null || echo '(none)'")}

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] p.bash("find . ...") ignores the agent's rootDir input — the discovered directories will always be relative to the process cwd, not the caller-supplied root. This breaks the contract the input schema advertises.

💡 Suggested fix

Use p.readInput isn't applicable for shell args, so inject the value via a p.bash template:

instructions: p`Analyze the directory structure of the given rootDir.

Directories found:
${p.bash("find ${input.rootDir} -maxdepth 3 -type d -not -path '*/node_modules/*' 2>/dev/null || echo '(none)'")}`,

Or use a defineTool that receives rootDir as a parameter and calls fs.readdir recursively so the path is validated.


For each directory, call classifyDirectory with its path and the rootDir. Build the directories record. Compute maxDepth and categoryCounts.`,
output: s.object({
directories: s.record(s.object({
depth: s.number,
category: s.string,
})),
maxDepth: s.number,
categoryCounts: s.record(s.number),
}),
tools: [classifyDirectory],
addons: [repair()],
});

export default osPathStructureAnalyzer;
```
56 changes: 56 additions & 0 deletions skills/rig/samples/416-ci-log-error-classifier.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# 416 - Ci Log Error Classifier

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

const classifyLogLine = defineTool("classifyLogLine", {
description: "Classify a CI log line into an error class and severity.",
parameters: s.object({ line: s.string }),
handler({ line }: { line: string }) {
const lower = line.toLowerCase();
const errorClass =
lower.includes("error") && (lower.includes("compil") || lower.includes("syntax") || lower.includes("tsc"))
? ("compile" as const)
: lower.includes("test") && (lower.includes("fail") || lower.includes("error"))
? ("test" as const)
: lower.includes("eslint") || lower.includes("lint")
? ("lint" as const)
: lower.includes("econnrefused") || lower.includes("enotfound") || lower.includes("network")
? ("network" as const)
: lower.includes("permission") || lower.includes("eacces")
? ("permission" as const)
: ("unknown" as const);
const severity =
lower.includes("error") ? ("error" as const)
: lower.includes("warn") ? ("warning" as const)
: ("info" as const);
return { errorClass, severity };
},
});

// Agent role: classify each line of a CI log file by error class and severity.
const ciLogErrorClassifier = agent({
model: "small",
input: s.object({ logFile: s.string }),
instructions: p`Classify lines in the CI log file at the path provided in logFile.

Log content:
${p.readInput("logFile")}

For each non-empty line, call classifyLogLine. Build the lines array. Compute errorCount (lines with severity "error"), warningCount (lines with severity "warning"). dominantError = the most frequent errorClass among error-severity lines (omit if none).`,
output: s.object({
lines: s.array(s.object({
line: s.string,
errorClass: s.enum("compile", "test", "lint", "network", "permission", "unknown"),
severity: s.enum("error", "warning", "info"),
})),
errorCount: s.number,
warningCount: s.number,
dominantError: s.optional(s.string),
}),
tools: [classifyLogLine],
addons: [repair()],
});

export default ciLogErrorClassifier;
```
52 changes: 52 additions & 0 deletions skills/rig/samples/417-ini-config-parser.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# 417 - Ini Config Parser

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

const parseIniSection = defineTool("parseIniSection", {
description: "Parse an INI file content and return sections with key-value pairs.",
parameters: s.object({ content: s.string }),
handler({ content }: { content: string }) {
const sections: Record<string, Record<string, string>> = {};
let currentSection = "__default__";
sections[currentSection] = {};
for (const rawLine of content.split("\n")) {
const line = rawLine.trim();
if (!line || line.startsWith(";") || line.startsWith("#")) continue;
const sectionMatch = /^\[(.+)\]$/.exec(line);
if (sectionMatch) {
currentSection = sectionMatch[1];
sections[currentSection] = {};
continue;
}
const kvMatch = /^([^=]+)=(.*)$/.exec(line);
if (kvMatch) {
sections[currentSection][kvMatch[1].trim()] = kvMatch[2].trim();
}
}
return sections;
},
});

// Agent role: parse an INI config file and report its sections and key-value pairs.
const iniConfigParser = agent({
model: "small",
input: s.object({ configFile: s.string }),
instructions: p`Parse the INI config file at the path provided in configFile.

Content:
${p.readInput("configFile")}

Call parseIniSection with the full file content. Build the sections record. totalSections = number of sections (excluding __default__ if empty). totalKeys = total key-value pairs across all sections. hasDefaultSection = true if there are keys outside any named section.`,
output: s.object({
sections: s.record(s.record(s.string)),
totalKeys: s.number,
totalSections: s.number,
hasDefaultSection: s.boolean,
}),
tools: [parseIniSection],
addons: [repair()],
});

export default iniConfigParser;
```
Loading
Loading