diff --git a/skills/rig/samples/451-shell-shebang-glob-validator.md b/skills/rig/samples/451-shell-shebang-glob-validator.md new file mode 100644 index 0000000..2092ec1 --- /dev/null +++ b/skills/rig/samples/451-shell-shebang-glob-validator.md @@ -0,0 +1,40 @@ +# 451 - Shell Shebang Glob Validator + +```rig +import { agent, p, s, defineTool, repair } from "rig"; +import { readFile } from "node:fs/promises"; + +const checkShebangLine = defineTool("checkShebangLine", { + description: "Check if a shell script file has a shebang line and whether it is standard", + parameters: s.object({ filePath: s.path }), + handler: async ({ filePath }: { filePath: string }) => { + const content = await readFile(filePath, "utf8"); + const firstLine = content.split("\n")[0] ?? ""; + const hasShebang = firstLine.startsWith("#!"); + const shebangLine = hasShebang ? firstLine : undefined; + const isStandard = hasShebang && (firstLine === "#!/bin/sh" || firstLine === "#!/bin/bash" || firstLine === "#!/usr/bin/env bash" || firstLine === "#!/usr/bin/env sh"); + return { hasShebang, shebangLine, isStandard }; + }, +}); + +// Agent role: Scan all shell scripts in the workspace and report shebang line status for each. +const shellShebangValidator = agent({ + model: "small", + instructions: p`You are a shell script auditor. The workspace contains these shell scripts: ${p.glob("**/*.sh")}. For each file path listed, call the checkShebangLine tool. Then return the full result object.`, + output: s.object({ + files: s.record(s.object({ + hasShebang: s.boolean, + shebangLine: s.optional(s.string), + isStandard: s.boolean, + })), + missingShebangCount: s.int, + standardShebangCount: s.int, + totalFiles: s.int, + }), + tools: [checkShebangLine], + addons: [repair()], +}); + +export default shellShebangValidator; + +``` diff --git a/skills/rig/samples/452-git-log-graph-summarizer.md b/skills/rig/samples/452-git-log-graph-summarizer.md new file mode 100644 index 0000000..5c44d28 --- /dev/null +++ b/skills/rig/samples/452-git-log-graph-summarizer.md @@ -0,0 +1,36 @@ +# 452 - Git Log Graph Summarizer + +```rig +import { agent, p, s, defineTool, steering, repair } from "rig"; + +const parseGraphLine = defineTool("parseGraphLine", { + description: "Classify a git log graph line as merge, commit, or branch-point", + parameters: s.object({ line: s.string }), + handler: ({ line }: { line: string }): "merge" | "commit" | "branch-point" => { + if (line.includes("Merge")) return "merge" as const; + if (/\*/.test(line) && /[0-9a-f]{7}/.test(line)) return "commit" as const; + return "branch-point" as const; + }, +}); + +// Agent role: Summarize the git log graph by classifying each line and counting merges, commits, and branch-points. +const gitLogGraphSummarizer = agent({ + model: "small", + instructions: p`Analyze the following git log graph output: ${p.bash("git log --oneline --graph -20")}. For each line, call parseGraphLine to classify it. Return the structured summary.`, + output: s.object({ + lines: s.array(s.object({ + type: s.enum("merge", "commit", "branch-point"), + hash: s.optional(s.string), + message: s.optional(s.string), + })), + mergeCount: s.int, + commitCount: s.int, + branchPoints: s.int, + }), + tools: [parseGraphLine], + addons: [steering(), repair()], +}); + +export default gitLogGraphSummarizer; + +``` diff --git a/skills/rig/samples/453-ts-complexity-scorer.md b/skills/rig/samples/453-ts-complexity-scorer.md new file mode 100644 index 0000000..7c447e1 --- /dev/null +++ b/skills/rig/samples/453-ts-complexity-scorer.md @@ -0,0 +1,44 @@ +# 453 - TypeScript Complexity Scorer + +```rig +import { agent, p, s, defineTool, repair } from "rig"; +import { readFile } from "node:fs/promises"; + +const scoreFileComplexity = defineTool("scoreFileComplexity", { + description: "Score the complexity of a TypeScript file by counting nested braces, ternaries, and callbacks", + parameters: s.object({ filePath: s.path }), + handler: async ({ filePath }: { filePath: string }) => { + const content = await readFile(filePath, "utf8"); + const nestedBraces = (content.match(/\{[^{}]*\{/g) ?? []).length; + const ternaries = (content.match(/\?[^:]+:/g) ?? []).length; + const callbacks = (content.match(/=>\s*\{/g) ?? []).length; + const score = nestedBraces + ternaries * 0.5 + callbacks * 0.5; + const complexity: "low" | "medium" | "high" = score < 5 ? "low" : score < 15 ? "medium" : "high"; + const topContributors: string[] = []; + if (nestedBraces > 0) topContributors.push(`nestedBraces:${nestedBraces}`); + if (ternaries > 0) topContributors.push(`ternaries:${ternaries}`); + if (callbacks > 0) topContributors.push(`callbacks:${callbacks}`); + return { score, complexity, topContributors }; + }, +}); + +// Agent role: Score complexity of all TypeScript source files in src/ and return a ranked summary. +const tsComplexityScorer = agent({ + model: "small", + instructions: p`Score the complexity of these TypeScript files: ${p.glob("src/**/*.ts")}. Call scoreFileComplexity for each file path. Then return the full result.`, + output: s.object({ + files: s.record(s.object({ + score: s.number, + complexity: s.enum("low", "medium", "high"), + topContributors: s.array(s.string), + })), + averageScore: s.number, + mostComplexFile: s.optional(s.string), + }), + tools: [scoreFileComplexity], + addons: [repair()], +}); + +export default tsComplexityScorer; + +``` diff --git a/skills/rig/samples/454-parallel-multi-tool-workflow.md b/skills/rig/samples/454-parallel-multi-tool-workflow.md new file mode 100644 index 0000000..c8fdfa5 --- /dev/null +++ b/skills/rig/samples/454-parallel-multi-tool-workflow.md @@ -0,0 +1,41 @@ +# 454 - Parallel Multi Tool Workflow + +```rig +import { agent, workflow, p, s } from "rig"; + +// Agent role: Count files grouped by extension in the workspace. +const fileCountAgent = agent({ + model: "small", + instructions: p`Count files by extension using: ${p.bash("find . -type f -not -path './.git/*' | sed 's/.*\\.//' | sort | uniq -c | sort -rn | head -20")}. Return extCounts mapping each extension to its count.`, + output: s.object({ + extCounts: s.record(s.int), + }), +}); + +// Agent role: Count environment variables grouped by prefix category. +const envHealthAgent = agent({ + model: "small", + instructions: p`Analyze environment variables using: ${p.bash("env | cut -d= -f1 | sed 's/_.*$//' | sort | uniq -c | sort -rn | head -20")}. Return categories mapping each prefix to its count.`, + output: s.object({ + categories: s.record(s.int), + }), +}); + +// Workflow role: Run fileCountAgent and envHealthAgent then synthesize an overall health assessment. +const parallelMultiToolWorkflow = workflow({ + meta: { name: "parallel-multi-tool-workflow", description: "Run file count and env health agents and combine results" }, + body: async ({ call, phase }) => { + phase("Gather"); + const fileSummary = await call(fileCountAgent, "Analyze", { label: "file-count" }); + const envSummary = await call(envHealthAgent, "Analyze", { label: "env-health" }); + phase("Synthesize"); + const totalFiles = Object.values(fileSummary?.extCounts ?? {}).reduce((a: number, b: unknown) => a + (b as number), 0); + const totalEnv = Object.values(envSummary?.categories ?? {}).reduce((a: number, b: unknown) => a + (b as number), 0); + const overallHealth: "healthy" | "degraded" | "unknown" = totalFiles > 0 && totalEnv > 0 ? "healthy" : totalFiles > 0 || totalEnv > 0 ? "degraded" : "unknown"; + return { fileSummary: fileSummary ?? { extCounts: {} }, envSummary: envSummary ?? { categories: {} }, overallHealth }; + }, +}); + +export default parallelMultiToolWorkflow; + +``` diff --git a/skills/rig/samples/455-zlib-compression-analyzer.md b/skills/rig/samples/455-zlib-compression-analyzer.md new file mode 100644 index 0000000..f95cac4 --- /dev/null +++ b/skills/rig/samples/455-zlib-compression-analyzer.md @@ -0,0 +1,48 @@ +# 455 - Zlib Compression Analyzer + +```rig +import { agent, p, s, defineTool, repair } from "rig"; +import { readFile } from "node:fs/promises"; +import { deflateSync } from "node:zlib"; + +const measureCompressionRatio = defineTool("measureCompressionRatio", { + description: "Measure the zlib compression ratio of a file and classify it", + parameters: s.object({ filePath: s.path }), + handler: async ({ filePath }: { filePath: string }) => { + const buf = await readFile(filePath); + const originalSize = buf.length; + if (originalSize === 0) { + return { ratio: 1, compressionClass: "incompressible" as const, originalSize: 0, compressedSize: 0 }; + } + const compressed = deflateSync(buf); + const compressedSize = compressed.length; + const ratio = compressedSize / originalSize; + const compressionClass: "excellent" | "good" | "poor" | "incompressible" = + ratio < 0.3 ? "excellent" : ratio < 0.6 ? "good" : ratio < 0.9 ? "poor" : "incompressible"; + return { ratio, compressionClass, originalSize, compressedSize }; + }, +}); + +// Agent role: Analyze zlib compression ratios for files in targetDir and report compressibility. +const zlibCompressionAnalyzer = agent({ + model: "small", + input: s.object({ targetDir: s.string }), + instructions: p`List files to analyze using: ${p.bash("find . -maxdepth 3 -type f -not -path './.git/*' | head -30")}. Call measureCompressionRatio for each file path. Return the full analysis.`, + output: s.object({ + files: s.record(s.object({ + ratio: s.number, + compressionClass: s.enum("excellent", "good", "poor", "incompressible"), + originalSize: s.int, + compressedSize: s.int, + })), + totalFiles: s.int, + averageRatio: s.number, + mostCompressibleFile: s.optional(s.string), + }), + tools: [measureCompressionRatio], + addons: [repair()], +}); + +export default zlibCompressionAnalyzer; + +``` diff --git a/skills/rig/samples/456-file-crypto-hash-reporter.md b/skills/rig/samples/456-file-crypto-hash-reporter.md new file mode 100644 index 0000000..a941ca9 --- /dev/null +++ b/skills/rig/samples/456-file-crypto-hash-reporter.md @@ -0,0 +1,38 @@ +# 456 - File Crypto Hash Reporter + +```rig +import { agent, p, s, defineTool, repair } from "rig"; +import { readFile } from "node:fs/promises"; +import { createHash } from "node:crypto"; + +const computeFileHash = defineTool("computeFileHash", { + description: "Compute a cryptographic hash of a file", + parameters: s.object({ filePath: s.path, algorithm: s.optional(s.string) }), + handler: async ({ filePath, algorithm = "sha256" }: { filePath: string; algorithm?: string }) => { + const buf = await readFile(filePath); + const hash = createHash(algorithm).update(buf).digest("hex"); + return { hash, sizeBytes: buf.length, algorithm }; + }, +}); + +// Agent role: Compute cryptographic hashes for all files in targetDir. +const fileCryptoHashReporter = agent({ + model: "small", + input: s.object({ targetDir: s.string, algorithm: s.optional(s.string) }), + instructions: p`Find files to hash in the workspace: ${p.glob("**/*")}. For each file, call computeFileHash. Use the algorithm from input if provided (default sha256). Return the result.`, + output: s.object({ + files: s.record(s.object({ + hash: s.string, + sizeBytes: s.int, + algorithm: s.string, + })), + totalFiles: s.int, + algorithm: s.string, + }), + tools: [computeFileHash], + addons: [repair()], +}); + +export default fileCryptoHashReporter; + +``` diff --git a/skills/rig/samples/457-source-line-length-auditor.md b/skills/rig/samples/457-source-line-length-auditor.md new file mode 100644 index 0000000..e63939b --- /dev/null +++ b/skills/rig/samples/457-source-line-length-auditor.md @@ -0,0 +1,49 @@ +# 457 - Source Line Length Auditor + +```rig +import { agent, p, s, defineTool, repair } from "rig"; +import { readFile } from "node:fs/promises"; + +const auditFileLengths = defineTool("auditFileLengths", { + description: "Audit a file for lines exceeding the maximum length limit", + parameters: s.object({ filePath: s.path, maxLen: s.optional(s.number) }), + handler: async ({ filePath, maxLen = 100 }: { filePath: string; maxLen?: number }) => { + const content = await readFile(filePath, "utf8"); + const lines = content.split("\n"); + let longLineCount = 0; + let longestLine = 0; + let worstLineNumber = 0; + lines.forEach((line: string, idx: number) => { + if (line.length > maxLen) { + longLineCount++; + if (line.length > longestLine) { + longestLine = line.length; + worstLineNumber = idx + 1; + } + } + }); + return { longLineCount, longestLine, worstLineNumber }; + }, +}); + +// Agent role: Audit TypeScript source files for lines exceeding maxLen and report violations. +const sourceLineLengthAuditor = agent({ + model: "small", + input: s.object({ dir: s.string, maxLen: s.optional(s.number) }), + instructions: p`Audit these TypeScript files for long lines: ${p.glob("**/*.ts")}. Call auditFileLengths for each file, passing maxLen from input. Return the full audit.`, + output: s.object({ + files: s.record(s.object({ + longLineCount: s.int, + longestLine: s.int, + worstLineNumber: s.int, + })), + totalViolations: s.int, + worstFile: s.string, + }), + tools: [auditFileLengths], + addons: [repair()], +}); + +export default sourceLineLengthAuditor; + +``` diff --git a/skills/rig/samples/458-workspace-symlink-inventory.md b/skills/rig/samples/458-workspace-symlink-inventory.md new file mode 100644 index 0000000..cc55008 --- /dev/null +++ b/skills/rig/samples/458-workspace-symlink-inventory.md @@ -0,0 +1,36 @@ +# 458 - Workspace Symlink Inventory + +```rig +import { agent, p, s, defineTool } from "rig"; +import { readlink, realpath } from "node:fs/promises"; + +const resolveSymlink = defineTool("resolveSymlink", { + description: "Resolve a symlink path and check if it is broken", + parameters: s.object({ symlinkPath: s.path }), + handler: async ({ symlinkPath }: { symlinkPath: string }) => { + const target = await readlink(symlinkPath).catch(() => ""); + if (!target) return { path: symlinkPath, target: "", broken: true }; + const broken = await realpath(symlinkPath).then(() => false).catch(() => true); + return { path: symlinkPath, target, broken }; + }, +}); + +// Agent role: Inventory all symlinks in the workspace and report whether each is broken. +const workspaceSymlinkInventory = agent({ + model: "small", + instructions: p`Find all symlinks in the workspace: ${p.bash("find . -type l 2>/dev/null | head -50")}. Call resolveSymlink for each path. Return the inventory.`, + output: s.object({ + symlinks: s.array(s.object({ + path: s.path, + target: s.string, + broken: s.boolean, + })), + totalSymlinks: s.int, + brokenCount: s.int, + }), + tools: [resolveSymlink], +}); + +export default workspaceSymlinkInventory; + +``` diff --git a/skills/rig/samples/459-xml-attribute-extractor.md b/skills/rig/samples/459-xml-attribute-extractor.md new file mode 100644 index 0000000..223f62d --- /dev/null +++ b/skills/rig/samples/459-xml-attribute-extractor.md @@ -0,0 +1,48 @@ +# 459 - XML Attribute Extractor + +```rig +import { agent, p, s, defineTool } from "rig"; + +const parseXmlAttributes = defineTool("parseXmlAttributes", { + description: "Extract element names and their attribute names from XML content", + parameters: s.object({ xmlContent: s.string }), + handler: ({ xmlContent }: { xmlContent: string }) => { + const openTagPattern = /<([A-Za-z][A-Za-z0-9_:-]*)([^>]*?)(?:\/?>)/g; + const elements: Record = {}; + let tagMatch: RegExpExecArray | null; + while ((tagMatch = openTagPattern.exec(xmlContent)) !== null) { + const tagName = tagMatch[1]; + const attrStr = tagMatch[2] ?? ""; + const attrs: string[] = []; + const ap = /([A-Za-z][A-Za-z0-9_:-]*)=/g; + let attrMatch: RegExpExecArray | null; + while ((attrMatch = ap.exec(attrStr)) !== null) { + attrs.push(attrMatch[1]); + } + if (!elements[tagName]) elements[tagName] = []; + attrs.forEach((a: string) => { + if (!elements[tagName].includes(a)) elements[tagName].push(a); + }); + } + const uniqueElements = Object.keys(elements).length; + const uniqueAttributes = new Set(Object.values(elements).flat()).size; + return { elements, uniqueElements, uniqueAttributes }; + }, +}); + +// Agent role: Extract all XML element names and their attribute names from the given XML file. +const xmlAttributeExtractor = agent({ + model: "small", + input: s.object({ xmlFile: s.string }), + instructions: p`Parse the XML file content: ${p.readInput("xmlFile")}. Call parseXmlAttributes with the full XML content. Return the extracted structure.`, + output: s.object({ + elements: s.record(s.array(s.string)), + uniqueElements: s.int, + uniqueAttributes: s.int, + }), + tools: [parseXmlAttributes], +}); + +export default xmlAttributeExtractor; + +``` diff --git a/skills/rig/samples/460-yaml-anchor-alias-reporter.md b/skills/rig/samples/460-yaml-anchor-alias-reporter.md new file mode 100644 index 0000000..f4de1ed --- /dev/null +++ b/skills/rig/samples/460-yaml-anchor-alias-reporter.md @@ -0,0 +1,40 @@ +# 460 - YAML Anchor Alias Reporter + +```rig +import { agent, p, s, defineTool, steering, repair } from "rig"; +import { readFile } from "node:fs/promises"; + +const scanYamlAnchors = defineTool("scanYamlAnchors", { + description: "Scan a YAML file for anchor definitions (&name) and alias references (*name)", + parameters: s.object({ filePath: s.path }), + handler: async ({ filePath }: { filePath: string }) => { + const content = await readFile(filePath, "utf8"); + const anchorMatches = content.match(/&([A-Za-z0-9_-]+)/g) ?? []; + const aliasMatches = content.match(/\*([A-Za-z0-9_-]+)/g) ?? []; + const anchors = anchorMatches.map((m: string) => m.slice(1)); + const anchorCount = anchors.length; + const aliasCount = aliasMatches.length; + return { anchorCount, aliasCount, anchors }; + }, +}); + +// Agent role: Scan YAML files for anchor and alias usage and report totals. +const yamlAnchorAliasReporter = agent({ + model: "small", + instructions: p`Scan these YAML files for anchors and aliases: ${p.glob("**/*.yml")} and ${p.glob("**/*.yaml")}. Call scanYamlAnchors for each file path. Return the full report.`, + output: s.object({ + files: s.record(s.object({ + anchorCount: s.int, + aliasCount: s.int, + anchors: s.array(s.string), + })), + totalAnchors: s.int, + totalAliases: s.int, + }), + tools: [scanYamlAnchors], + addons: [steering(), repair()], +}); + +export default yamlAnchorAliasReporter; + +```