Skip to content
Draft
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
46 changes: 46 additions & 0 deletions skills/rig/samples/361-stale-branch-detector-v3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# 361 - Stale Branch Detector V3

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

const classifyBranchAge = defineTool("classifyBranchAge", {
description: "Classify a branch as fresh, stale, or dead based on its Unix commit timestamp.",
parameters: s.object({ name: s.string, unixTimestamp: s.int }),
handler: ({ unixTimestamp }: { name: string; unixTimestamp: number }) => {
const ageDays = (Date.now() / 1000 - unixTimestamp) / 86400;
if (ageDays < 30) return "fresh" as const;
if (ageDays < 90) return "stale" as const;
return "dead" as const;
},
});

// Agent role: detect stale and dead local git branches and recommend candidates for deletion.
const staleBranchDetector = agent({
model: "small",
instructions: p`Detect stale and dead local git branches.

Branch list (format: name|unix-timestamp):
${p.bash("git for-each-ref --format='%(refname:short)|%(committerdate:unix)' refs/heads 2>/dev/null || echo ''")}

Steps:
1. Parse each line as name|unixTimestamp.
2. For each branch call classifyBranchAge with the parsed values.
3. Build the branches array with name, lastCommit (ISO string from timestamp), and ageClass.
4. Count staleCount (ageClass="stale") and deadCount (ageClass="dead").
5. Set recommendedForDeletion to names where ageClass is "dead".`,
output: s.object({
branches: s.array(s.object({
name: s.string,
lastCommit: s.string,
ageClass: s.enum("fresh", "stale", "dead"),
})),
staleCount: s.number,
deadCount: s.number,
recommendedForDeletion: s.array(s.string),
}),
tools: [classifyBranchAge],
maxTurns: 6,
});

export default staleBranchDetector;
```
50 changes: 50 additions & 0 deletions skills/rig/samples/362-license-header-checker-v3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# 362 - License Header Checker V3

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

const checkLicenseHeader = defineTool("checkLicenseHeader", {
description: "Check whether a file's first lines match the expected license header.",
parameters: s.object({ filePath: s.path, expectedHeader: s.string }),
handler: async ({ filePath, expectedHeader }: { filePath: string; expectedHeader: string }) => {
try {
const content = await readFile(filePath, "utf8");
const hasHeader = content.startsWith(expectedHeader);
const status = hasHeader ? ("ok" as const) : content.trimStart().startsWith("//") || content.trimStart().startsWith("/*") ? ("wrong" as const) : ("missing" as const);
return { hasHeader, status };
} catch {
return { hasHeader: false, status: "missing" as const };
}
},
});

// Agent role: check that all TypeScript files contain the expected license header.
const licenseHeaderChecker = agent({
model: "small",
input: s.object({ expectedHeader: s.string }),
instructions: p`Check all TypeScript files for the expected license header.

TypeScript files found:
${p.bash("find . -name '*.ts' -not -path '*/node_modules/*' -not -path '*/.git/*' 2>/dev/null | head -100 || echo ''")}

Steps:
1. For each file path, call checkLicenseHeader with the file path and input.expectedHeader.
2. Build the files record keyed by file path with hasHeader and status.
3. Count missingCount (status != "ok").
4. Set allCompliant = (missingCount === 0).`,
output: s.object({
files: s.record(s.object({
hasHeader: s.boolean,
status: s.enum("ok", "missing", "wrong"),
})),
missingCount: s.number,
allCompliant: s.boolean,
}),
tools: [checkLicenseHeader],
maxTurns: 8,
addons: [],
});

export default licenseHeaderChecker;
```
59 changes: 59 additions & 0 deletions skills/rig/samples/363-yaml-config-diff-v3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# 363 - YAML Config Diff V3

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

const extractTopLevelKeys = defineTool("extractTopLevelKeys", {
description: "Extract top-level keys from YAML content using regex.",
parameters: s.object({ yamlContent: s.string }),
handler: ({ yamlContent }: { yamlContent: string }) => {
const matches = yamlContent.match(/^(\w[\w-]*):/gm) ?? [];
const keys = matches.map((m: string) => m.replace(/:$/, ""));
return { keys };
},
});

const diffKeys = defineTool("diffKeys", {
description: "Compute added, removed, and common keys between two arrays.",
parameters: s.object({ baseKeys: s.array(s.string), targetKeys: s.array(s.string) }),
handler: ({ baseKeys, targetKeys }: { baseKeys: string[]; targetKeys: string[] }) => {
const baseSet = new Set(baseKeys);
const targetSet = new Set(targetKeys);
const added = targetKeys.filter((k: string) => !baseSet.has(k));
const removed = baseKeys.filter((k: string) => !targetSet.has(k));
const common = baseKeys.filter((k: string) => targetSet.has(k));
return { added, removed, common };
},
});

// Agent role: compare top-level keys of two YAML config files and report differences.
const yamlConfigDiff = agent({
model: "small",
input: s.object({ baseFile: s.string, targetFile: s.string }),
instructions: p`Compare two YAML config files and report key differences.

Base file (input.baseFile):
${p.readInput("baseFile")}

Target file (input.targetFile):
${p.readInput("targetFile")}

Steps:
1. Call extractTopLevelKeys on each file's content.
2. Call diffKeys with both key arrays.
3. Set totalChanges = added.length + removed.length.
4. Set hasBreakingChanges = removed.length > 0.`,
output: s.object({
addedKeys: s.array(s.string),
removedKeys: s.array(s.string),
commonKeys: s.array(s.string),
totalChanges: s.number,
hasBreakingChanges: s.boolean,
}),
tools: [extractTopLevelKeys, diffKeys],
maxTurns: 4,
addons: [],
});

export default yamlConfigDiff;
```
54 changes: 54 additions & 0 deletions skills/rig/samples/364-monorepo-workspace-lister-v3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# 364 - Monorepo Workspace Lister V3

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

const extractPackageInfo = defineTool("extractPackageInfo", {
description: "Extract name, version, hasPrivate, and dependencyCount from a package.json file.",
parameters: s.object({ filePath: s.path }),
handler: async ({ filePath }: { filePath: string }) => {
try {
const raw = await readFile(filePath, "utf8");
const pkg = JSON.parse(raw);
const depCount = Object.keys({ ...pkg.dependencies, ...pkg.devDependencies, ...pkg.peerDependencies }).length;
return {
name: pkg.name ?? "unknown",
version: pkg.version ?? "0.0.0",
hasPrivate: pkg.private === true,
dependencyCount: depCount,
};
} catch {
return { name: "unknown", version: "0.0.0", hasPrivate: false, dependencyCount: 0 };
}
},
});

// Agent role: list all workspace packages in a monorepo with their metadata.
const monorepoWorkspaceLister = agent({
model: "small",
instructions: p`List all workspace packages in this monorepo.

Nested package.json files (excluding node_modules):
${p.bash("find . -name 'package.json' -mindepth 2 -maxdepth 4 -not -path '*/node_modules/*' 2>/dev/null || echo ''")}

Steps:
1. For each file path, call extractPackageInfo.
2. Build the packages array with name, version, path (the filePath), hasPrivate, and dependencyCount.
3. Set totalPackages = packages.length.`,
output: s.object({
packages: s.array(s.object({
name: s.string,
version: s.string,
path: s.path,
hasPrivate: s.boolean,
dependencyCount: s.number,
})),
totalPackages: s.number,
}),
tools: [extractPackageInfo],
maxTurns: 6,
});

export default monorepoWorkspaceLister;
```
57 changes: 57 additions & 0 deletions skills/rig/samples/365-gh-actions-timing-analyzer-v2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# 365 - GH Actions Timing Analyzer V2

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

const analyzeWorkflow = defineTool("analyzeWorkflow", {
description: "Analyze a GitHub Actions workflow YAML file for job/step counts and optimization opportunities.",
parameters: s.object({ filePath: s.path }),
handler: async ({ filePath }: { filePath: string }) => {
try {
const content = await readFile(filePath, "utf8");
const jobMatches = content.match(/^\s{0,2}[\w-]+:\s*$/gm) ?? [];
const jobCount = Math.max(1, jobMatches.length - 3);
const stepMatches = content.match(/^\s+- (name|uses|run):/gm) ?? [];
const stepCount = stepMatches.length;
const hasCacheStep = /uses:\s*actions\/cache/.test(content);
const hasMatrix = /matrix:/.test(content);
const optimizable = !hasCacheStep || (!hasMatrix && stepCount > 8);
return { jobCount, stepCount, hasCacheStep, hasMatrix, optimizable };
} catch {
return { jobCount: 0, stepCount: 0, hasCacheStep: false, hasMatrix: false, optimizable: false };
}
},
});

// Agent role: analyze GitHub Actions workflows for job/step counts and optimization opportunities.
const ghActionsTimingAnalyzer = agent({
model: "small",
instructions: p`Analyze all GitHub Actions workflow files for optimization opportunities.

Workflow files:
${p.bash("find .github/workflows -name '*.yml' -o -name '*.yaml' 2>/dev/null | sort || echo ''")}

Steps:
1. For each workflow file path, call analyzeWorkflow.
2. Build the workflows array with file, jobCount, stepCount, hasCacheStep, hasMatrix, optimizable.
3. Count totalWorkflows and optimizableCount (optimizable = true).`,
output: s.object({
workflows: s.array(s.object({
file: s.string,
jobCount: s.number,
stepCount: s.number,
hasCacheStep: s.boolean,
hasMatrix: s.boolean,
optimizable: s.boolean,
})),
totalWorkflows: s.number,
optimizableCount: s.number,
}),
tools: [analyzeWorkflow],
maxTurns: 6,
addons: [],
});

export default ghActionsTimingAnalyzer;
```
57 changes: 57 additions & 0 deletions skills/rig/samples/366-binary-file-detector-v2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# 366 - Binary File Detector V2

```rig
import { agent, p, s, defineTool } from "rig";
import { open } from "node:fs/promises";
import { stat } from "node:fs/promises";

const detectBinaryFile = defineTool("detectBinaryFile", {
description: "Detect if a file is binary by sampling the first 8KB for null bytes.",
parameters: s.object({ filePath: s.path }),
handler: async ({ filePath }: { filePath: string }) => {
try {
const stats = await stat(filePath);
const sizeBytes = stats.size;
const fh = await open(filePath, "r");
try {
const buf = Buffer.alloc(Math.min(8192, sizeBytes));
await fh.read(buf, 0, buf.length, 0);
const hasNull = buf.includes(0);
return { type: (hasNull ? "binary" : "text") as "binary" | "text", sizeBytes };
} finally {
await fh.close();
}
} catch {
return { type: "unknown" as const, sizeBytes: 0 };
}
},
});

// Agent role: detect binary files among git-tracked files by sampling file contents.
const binaryFileDetector = agent({
model: "small",
instructions: p`Detect binary files among git-tracked files.

Git-tracked files:
${p.bash("git ls-files 2>/dev/null | head -200 || echo ''")}

Steps:
1. For each file path, call detectBinaryFile.
2. Build files as a record keyed by file path with type and sizeBytes.
3. Count binaryCount (type="binary") and textCount (type="text").
4. Set hasBinaryFiles = binaryCount > 0.`,
output: s.object({
files: s.record(s.object({
type: s.enum("text", "binary", "unknown"),
sizeBytes: s.number,
})),
binaryCount: s.number,
textCount: s.number,
hasBinaryFiles: s.boolean,
}),
tools: [detectBinaryFile],
maxTurns: 8,
});

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

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

const verifyLockEntry = defineTool("verifyLockEntry", {
description: "Verify a lockfile package entry has valid resolved URL and non-empty integrity hash.",
parameters: s.object({ name: s.string, resolved: s.optional(s.string), integrity: s.optional(s.string) }),
handler: ({ resolved, integrity }: { name: string; resolved?: string; integrity?: string }) => {
const hasResolved = typeof resolved === "string" && resolved.startsWith("https://");
const hasIntegrity = typeof integrity === "string" && integrity.length > 0;
return { valid: hasResolved && hasIntegrity, hasIntegrity, hasResolved };
},
});

// Agent role: verify package-lock.json entries have valid resolved URLs and integrity hashes.
const lockfileIntegrityChecker = agent({
model: "small",
instructions: p`Verify all package entries in package-lock.json have valid resolved and integrity fields.

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

Steps:
1. Parse the lockfile JSON and iterate over packages (lockfileVersion 2/3: packages object, v1: dependencies object).
2. For each package name and entry, call verifyLockEntry with name, resolved, and integrity.
3. Build packages record keyed by name with valid, hasIntegrity, hasResolved.
4. Count mismatchCount (valid=false) and set isClean = mismatchCount === 0.
5. Set totalChecked = number of 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],
maxTurns: 6,
addons: [repair()],
});

export default lockfileIntegrityChecker;
```
Loading