diff --git a/skills/rig/samples/461-pkg-scripts-trio-workflow.md b/skills/rig/samples/461-pkg-scripts-trio-workflow.md new file mode 100644 index 0000000..a40b49c --- /dev/null +++ b/skills/rig/samples/461-pkg-scripts-trio-workflow.md @@ -0,0 +1,64 @@ +# 461 - Package Scripts Trio Workflow + +```rig +import { agent, p, s, workflow } from "rig"; + +// Agent role: list all npm scripts from package.json. +const scriptsLister = agent({ + name: "scriptsLister", + model: "small", + instructions: p`List all npm scripts from this project. +${p.read("package.json")} +Return all script names from the "scripts" field as an array.`, + output: s.object({ scripts: s.array(s.string) }), +}); + +// Agent role: categorize npm scripts by purpose. +const scriptsCategorizer = agent({ + name: "scriptsCategorizer", + model: "small", + input: s.object({ scripts: s.array(s.string) }), + instructions: `Classify each script name into build, test, lint, release, utility, or other. +Return a record mapping category to the list of script names in it, plus the dominantCategory.`, + output: s.object({ + categories: s.record(s.array(s.string)), + dominantCategory: s.enum("build", "test", "lint", "release", "utility", "other"), + }), +}); + +// Agent role: check installed dependency health via npm ls. +const scriptsHealthChecker = agent({ + name: "scriptsHealthChecker", + model: "small", + instructions: p`Check installed npm dependency health. +${p.bash("npm ls --depth=0 2>&1 | tail -30")} +List any packages that appear missing or broken, and classify overall health.`, + output: s.object({ + missingDeps: s.array(s.string), + dependencyHealth: s.enum("ok", "warnings", "errors"), + }), +}); + +// Workflow role: run three package.json analysis agents and produce an overall health verdict. +export default workflow({ + meta: { name: "pkg-scripts-trio", description: "Three-agent package.json scripts and dependency analysis." }, + body: async ({ call, phase }) => { + phase("Collect"); + const [listed, health] = await Promise.all([ + call(scriptsLister, "list scripts"), + call(scriptsHealthChecker, "check dependency health"), + ]); + const scripts = listed?.scripts ?? []; + phase("Categorize"); + const categorized = await call(scriptsCategorizer, { scripts }); + phase("Summarize"); + return call.json( + `scripts=${JSON.stringify(scripts)} categories=${JSON.stringify(categorized?.categories ?? {})} missingDeps=${JSON.stringify(health?.missingDeps ?? [])} dependencyHealth=${health?.dependencyHealth ?? "ok"}. Determine overallHealth: healthy if dependencyHealth=ok and scripts non-empty, needs-attention if warnings or empty scripts, critical if errors or missing deps.`, + s.object({ + overallHealth: s.enum("healthy", "needs-attention", "critical"), + summary: s.string, + }), + ); + }, +}); +``` diff --git a/skills/rig/samples/462-ts-const-enum-extractor.md b/skills/rig/samples/462-ts-const-enum-extractor.md new file mode 100644 index 0000000..c6d4e39 --- /dev/null +++ b/skills/rig/samples/462-ts-const-enum-extractor.md @@ -0,0 +1,55 @@ +# 462 - TypeScript Const Enum Extractor + +```rig +import { agent, defineTool, p, s, steering } from "rig"; + + +const extractConstEnums = defineTool("extractConstEnums", { + description: "Read a TypeScript file and extract all const enum declarations with their members.", + parameters: s.object({ filePath: s.path("TypeScript file path") }), + async handler({ filePath }) { + const { readFile } = await import("node:fs/promises"); + const src = await readFile(filePath, "utf8"); + const enumRe = /const\s+enum\s+(\w+)\s*\{([^}]*)\}/g; + const result: Record = {}; + let m: RegExpExecArray | null; + while ((m = enumRe.exec(src)) !== null) { + const enumName = m[1]; + const body = m[2]; + const members = body + .split(",") + .map((line: string) => line.trim()) + .filter((line: string) => line.length > 0) + .map((line: string) => { + const [name, value] = line.split("=").map((s: string) => s.trim()); + return { name, value: value ?? null }; + }); + result[enumName] = members; + } + return JSON.stringify(result); + }, +}); + +// Agent role: find and extract all TypeScript const enum declarations across the workspace. +const tsConstEnumExtractor = agent({ + name: "tsConstEnumExtractor", + model: "small", + instructions: p`Scan all TypeScript source files for const enum declarations. +Files: ${p.glob("src/**/*.ts")} +Use extractConstEnums on each file. Aggregate results: enums record (members array, memberCount, sourceFile), totalEnums, totalMembers, largestEnum (name with most members, if any).`, + output: s.object({ + enums: s.record(s.object({ + members: s.array(s.object({ name: s.string, value: s.optional(s.string) })), + memberCount: s.int, + sourceFile: s.path, + })), + totalEnums: s.int, + totalMembers: s.int, + largestEnum: s.optional(s.string), + }), + tools: [extractConstEnums], + addons: [steering()], +}); + +export default tsConstEnumExtractor; +``` diff --git a/skills/rig/samples/463-http-access-log-stats.md b/skills/rig/samples/463-http-access-log-stats.md new file mode 100644 index 0000000..e05462f --- /dev/null +++ b/skills/rig/samples/463-http-access-log-stats.md @@ -0,0 +1,48 @@ +# 463 - HTTP Access Log Stats + +```rig +import { agent, defineTool, p, repair, s } from "rig"; + + +const parseLogLine = defineTool("parseLogLine", { + description: "Parse a single HTTP access log line and classify its HTTP status.", + parameters: s.object({ line: s.string("Raw access log line") }), + handler({ line }) { + const parts = line.split(" "); + const statusStr = parts.find((p: string) => /^\d{3}$/.test(p)) ?? "0"; + const status = parseInt(statusStr, 10); + let statusClass: "2xx" | "3xx" | "4xx" | "5xx" | "other"; + if (status >= 200 && status < 300) statusClass = "2xx"; + else if (status >= 300 && status < 400) statusClass = "3xx"; + else if (status >= 400 && status < 500) statusClass = "4xx"; + else if (status >= 500 && status < 600) statusClass = "5xx"; + else statusClass = "other"; + const pathMatch = line.match(/"(?:GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+([^\s"]+)/); + const path = pathMatch ? pathMatch[1] : "/"; + return JSON.stringify({ status, statusClass, path }); + }, +}); + +// Agent role: parse an HTTP access log file and compute request statistics. +const httpAccessLogStats = agent({ + name: "httpAccessLogStats", + model: "small", + input: s.object({ logFile: s.path("Path to HTTP access log file") }), + instructions: p`Read the HTTP access log at the specified path using ${p.readInput("logFile")}. +Use parseLogLine on each non-empty line to classify its status. Aggregate: +- statusCounts: count per status class (2xx, 3xx, 4xx, 5xx, other) +- topPaths: top 5 most frequent request paths +- totalRequests: total line count +- errorRate: (4xx + 5xx) / total as a fraction 0-1`, + output: s.object({ + statusCounts: s.record(s.int), + topPaths: s.array(s.string), + totalRequests: s.int, + errorRate: s.number, + }), + tools: [parseLogLine], + addons: [repair()], +}); + +export default httpAccessLogStats; +``` diff --git a/skills/rig/samples/464-ts-spread-usage-counter.md b/skills/rig/samples/464-ts-spread-usage-counter.md new file mode 100644 index 0000000..7023059 --- /dev/null +++ b/skills/rig/samples/464-ts-spread-usage-counter.md @@ -0,0 +1,41 @@ +# 464 - TypeScript Spread Usage Counter + +```rig +import { agent, defineTool, p, s, steering } from "rig"; + + +const countSpreadPatterns = defineTool("countSpreadPatterns", { + description: "Count object spread and array spread usages in a TypeScript file.", + parameters: s.object({ filePath: s.path("TypeScript file path") }), + async handler({ filePath }) { + const { readFile } = await import("node:fs/promises"); + const src = await readFile(filePath, "utf8"); + const objectSpreads = (src.match(/\.\.\.[a-zA-Z_$][a-zA-Z0-9_$]*(?=\s*[,}])/g) ?? []).length; + const arraySpreads = (src.match(/\.\.\.[a-zA-Z_$][a-zA-Z0-9_$]*(?=\s*[,\]])/g) ?? []).length; + return JSON.stringify({ objectSpreads, arraySpreads }); + }, +}); + +// Agent role: count object spread and array spread patterns across all TypeScript source files. +const tsSpreadUsageCounter = agent({ + name: "tsSpreadUsageCounter", + model: "small", + instructions: p`Scan all TypeScript source files for spread operator usage. +Files: ${p.glob("src/**/*.ts")} +Use countSpreadPatterns on each file. Aggregate into: +- files: record mapping filePath to { objectSpreadCount, arraySpreadCount } +- totalObjectSpreads: sum of all object spreads +- totalArraySpreads: sum of all array spreads +- mostSpreadFile: path of the file with the highest combined spread count (null if none)`, + output: s.object({ + files: s.record(s.object({ objectSpreadCount: s.int, arraySpreadCount: s.int })), + totalObjectSpreads: s.int, + totalArraySpreads: s.int, + mostSpreadFile: s.optional(s.path), + }), + tools: [countSpreadPatterns], + addons: [steering()], +}); + +export default tsSpreadUsageCounter; +``` diff --git a/skills/rig/samples/465-git-hook-file-scanner.md b/skills/rig/samples/465-git-hook-file-scanner.md new file mode 100644 index 0000000..4ea80e4 --- /dev/null +++ b/skills/rig/samples/465-git-hook-file-scanner.md @@ -0,0 +1,50 @@ +# 465 - Git Hook File Scanner + +```rig +import { agent, defineTool, p, repair, s } from "rig"; + + +const analyzeHookFile = defineTool("analyzeHookFile", { + description: "Read a git hook file and detect its shebang, executability, and type.", + parameters: s.object({ hookPath: s.path("Full path to the hook file") }), + async handler({ hookPath }) { + const { readFile, stat } = await import("node:fs/promises"); + try { + const [content, info] = await Promise.all([readFile(hookPath, "utf8"), stat(hookPath)]); + const shebang = content.split("\n")[0] ?? ""; + const executable = !!(info.mode & 0o111); + const name = hookPath.split("/").pop() ?? hookPath; + const knownTypes = ["pre-commit", "commit-msg", "post-commit", "pre-push", "pre-receive"]; + const hookType = knownTypes.includes(name) ? name : "other"; + const lineCount = content.split("\n").length; + return JSON.stringify({ shebang, executable, hookType, lineCount }); + } catch { + return JSON.stringify({ error: "could not read hook" }); + } + }, +}); + +// Agent role: scan .git/hooks for installed hook scripts and report their properties. +const gitHookFileScanner = agent({ + name: "gitHookFileScanner", + model: "small", + instructions: p`List all files in the .git/hooks directory. +${p.bash("ls -1 .git/hooks/ 2>/dev/null || echo 'no hooks directory'")} +For each non-sample file, use analyzeHookFile passing the full path (.git/hooks/). +Return hooks as a record keyed by hook name, activeCount (executable hooks), and totalHooks.`, + output: s.object({ + hooks: s.record(s.object({ + shebang: s.string, + executable: s.boolean, + hookType: s.enum("pre-commit", "commit-msg", "post-commit", "pre-push", "pre-receive", "other"), + lineCount: s.int, + })), + activeCount: s.int, + totalHooks: s.int, + }), + tools: [analyzeHookFile], + addons: [repair()], +}); + +export default gitHookFileScanner; +``` diff --git a/skills/rig/samples/466-merge-strategy-selector.md b/skills/rig/samples/466-merge-strategy-selector.md new file mode 100644 index 0000000..58e4d5f --- /dev/null +++ b/skills/rig/samples/466-merge-strategy-selector.md @@ -0,0 +1,53 @@ +# 466 - Merge Strategy Selector + +```rig +import { agent, p, s, workflow } from "rig"; + +// Agent role: analyze git diff --stat to determine which area of the codebase changed most. +const branchDiffAgent = agent({ + name: "branchDiffAgent", + model: "small", + instructions: p`Analyze the git diff statistics for the current branch vs main. +${p.bash("git diff --stat origin/main...HEAD 2>/dev/null || git diff --stat HEAD~1...HEAD 2>/dev/null || echo 'no diff available'")} +Determine which area of the codebase was changed most (src/test/config/docs/mixed).`, + output: s.object({ + changedFiles: s.int, + insertions: s.int, + deletions: s.int, + dominantArea: s.enum("src", "test", "config", "docs", "mixed"), + }), +}); + +// Agent role: assess merge conflict risk from git status. +const conflictRiskAgent = agent({ + name: "conflictRiskAgent", + model: "small", + instructions: p`Check git working tree status for conflicts. +${p.bash("git status --short 2>/dev/null | head -30")} +Count conflict markers (lines starting with UU, AA, DD) and report conflict risk.`, + output: s.object({ + conflictCount: s.int, + hasConflicts: s.boolean, + }), +}); + +// Workflow role: analyze branch diff and conflict risk, then recommend a merge strategy. +export default workflow({ + meta: { name: "merge-strategy-selector", description: "Select optimal merge strategy based on diff and conflict analysis." }, + body: async ({ call, phase }) => { + phase("Analyze"); + const [diffResult, conflictResult] = await Promise.all([ + call(branchDiffAgent, "analyze diff"), + call(conflictRiskAgent, "check conflicts"), + ]); + phase("Recommend"); + return call.json( + `dominantArea=${diffResult?.dominantArea} changedFiles=${diffResult?.changedFiles} hasConflicts=${conflictResult?.hasConflicts} conflictCount=${conflictResult?.conflictCount}. Choose mergeRecommendation: fast-forward (few files, no conflicts, src-only), squash (many small commits, clean), merge (mixed areas), rebase (linear history preferred, no conflicts).`, + s.object({ + mergeRecommendation: s.enum("fast-forward", "squash", "merge", "rebase"), + rationale: s.string, + }), + ); + }, +}); +``` diff --git a/skills/rig/samples/467-ini-config-parser.md b/skills/rig/samples/467-ini-config-parser.md new file mode 100644 index 0000000..a2b4f67 --- /dev/null +++ b/skills/rig/samples/467-ini-config-parser.md @@ -0,0 +1,48 @@ +# 467 - INI Config Parser + +```rig +import { agent, defineTool, p, repair, s } from "rig"; + + +const parseIniSection = defineTool("parseIniSection", { + description: "Parse key-value pairs from an INI config file section.", + parameters: s.object({ content: s.string("Full INI file content"), section: s.string("Section name to parse") }), + handler({ content, section }) { + const lines = content.split("\n"); + const sectionRe = new RegExp(`^\\[${section}\\]`); + const result: Record = {}; + let inSection = false; + for (const line of lines) { + if (sectionRe.test(line.trim())) { inSection = true; continue; } + if (/^\[/.test(line.trim())) { if (inSection) break; continue; } + if (!inSection) continue; + const eqIdx = line.indexOf("="); + if (eqIdx < 0) continue; + const key = line.slice(0, eqIdx).trim(); + const val = line.slice(eqIdx + 1).trim(); + if (key) result[key] = val; + } + return JSON.stringify(result); + }, +}); + +// Agent role: parse an INI configuration file and extract all sections and their key-value pairs. +const iniConfigParser = agent({ + name: "iniConfigParser", + model: "small", + input: s.object({ configFile: s.path("Path to the INI config file") }), + instructions: p`Read the INI configuration file at the specified path. +${p.readInput("configFile")} +Use parseIniSection for each section header you find (lines matching [SectionName]). +Return sections as a record, totalKeys count, and sectionCount.`, + output: s.object({ + sections: s.record(s.record(s.string)), + sectionCount: s.int, + totalKeys: s.int, + }), + tools: [parseIniSection], + addons: [repair()], +}); + +export default iniConfigParser; +``` diff --git a/skills/rig/samples/468-file-crypto-hash-reporter.md b/skills/rig/samples/468-file-crypto-hash-reporter.md new file mode 100644 index 0000000..bfc8e6f --- /dev/null +++ b/skills/rig/samples/468-file-crypto-hash-reporter.md @@ -0,0 +1,41 @@ +# 468 - File Crypto Hash Reporter + +```rig +import { agent, defineTool, p, s, steering } from "rig"; + + +const hashFile = defineTool("hashFile", { + description: "Compute SHA-256 hash of a file using node:crypto.", + parameters: s.object({ filePath: s.path("File to hash") }), + async handler({ filePath }) { + const { readFile } = await import("node:fs/promises"); + const { createHash } = await import("node:crypto"); + try { + const buf = await readFile(filePath); + const hash = createHash("sha256").update(buf).digest("hex"); + const sizeBytes = buf.length; + return JSON.stringify({ hash, sizeBytes }); + } catch { + return JSON.stringify({ error: "could not read file" }); + } + }, +}); + +// Agent role: compute SHA-256 hashes and sizes for key project files and report integrity status. +const fileCryptoHashReporter = agent({ + name: "fileCryptoHashReporter", + model: "small", + instructions: p`Find key project files to hash for integrity checking. +${p.bash("find . -maxdepth 2 -name 'package.json' -o -name 'package-lock.json' -o -name 'tsconfig.json' -o -name '.npmrc' 2>/dev/null | grep -v node_modules | head -10")} +Use hashFile on each file path. Return a hashes record keyed by filePath with hash and sizeBytes, totalFiles, and largestFile path.`, + output: s.object({ + hashes: s.record(s.object({ hash: s.string, sizeBytes: s.int })), + totalFiles: s.int, + largestFile: s.optional(s.path), + }), + tools: [hashFile], + addons: [steering()], +}); + +export default fileCryptoHashReporter; +``` diff --git a/skills/rig/samples/469-git-reflog-classifier.md b/skills/rig/samples/469-git-reflog-classifier.md new file mode 100644 index 0000000..474061d --- /dev/null +++ b/skills/rig/samples/469-git-reflog-classifier.md @@ -0,0 +1,53 @@ +# 469 - Git Reflog Classifier + +```rig +import { agent, p, s, workflow } from "rig"; + +// Agent role: fetch recent git reflog entries and extract operation types. +const reflogFetcher = agent({ + name: "reflogFetcher", + model: "small", + instructions: p`Retrieve recent git reflog entries. +${p.bash("git reflog --format='%H|%gs|%ar' -50 2>/dev/null || echo 'no reflog'")} +Parse each line into hash, action description, and relativeTime. Return entries array.`, + output: s.object({ + entries: s.array(s.object({ + hash: s.string, + action: s.string, + relativeTime: s.string, + })), + }), +}); + +// Agent role: classify reflog entries by operation type and produce a summary. +const reflogClassifier = agent({ + name: "reflogClassifier", + model: "small", + input: s.object({ + entries: s.array(s.object({ hash: s.string, action: s.string, relativeTime: s.string })), + }), + instructions: `Classify each reflog entry's action into: commit, merge, rebase, checkout, reset, cherry-pick, or other. +Count occurrences per type. Return classified entries array, typeCounts record, and mostFrequentOp.`, + output: s.object({ + classified: s.array(s.object({ + hash: s.string, + action: s.string, + opType: s.enum("commit", "merge", "rebase", "checkout", "reset", "cherry-pick", "other"), + })), + typeCounts: s.record(s.int), + mostFrequentOp: s.string, + }), +}); + +// Workflow role: fetch and classify git reflog entries to understand recent repository activity. +export default workflow({ + meta: { name: "git-reflog-classifier", description: "Fetch and classify git reflog entries by operation type." }, + body: async ({ call, phase }) => { + phase("Fetch"); + const fetched = await call(reflogFetcher, "fetch reflog"); + if (!fetched) return null; + phase("Classify"); + return call(reflogClassifier, { entries: fetched.entries }); + }, +}); +``` diff --git a/skills/rig/samples/470-package-json-field-auditor.md b/skills/rig/samples/470-package-json-field-auditor.md new file mode 100644 index 0000000..7c74ad4 --- /dev/null +++ b/skills/rig/samples/470-package-json-field-auditor.md @@ -0,0 +1,47 @@ +# 470 - Package JSON Field Auditor + +```rig +import { agent, defineTool, p, repair, s } from "rig"; + + +const checkFieldPresence = defineTool("checkFieldPresence", { + description: "Check which standard package.json fields are present and non-empty.", + parameters: s.object({ content: s.string("Raw package.json content") }), + handler({ content }) { + let pkg: Record; + try { pkg = JSON.parse(content); } catch { return JSON.stringify({ error: "invalid json" }); } + const standard = ["name", "version", "description", "main", "types", "scripts", "dependencies", "devDependencies", "peerDependencies", "license", "author", "repository", "keywords", "files", "engines"]; + const present: string[] = []; + const missing: string[] = []; + for (const f of standard) { + const v = pkg[f]; + if (v !== undefined && v !== null && v !== "" && !(Array.isArray(v) && v.length === 0) && !(typeof v === "object" && !Array.isArray(v) && Object.keys(v as object).length === 0)) { + present.push(f); + } else { + missing.push(f); + } + } + const score = Math.round((present.length / standard.length) * 100); + return JSON.stringify({ present, missing, score }); + }, +}); + +// Agent role: audit package.json for presence of standard fields and compute a completeness score. +const packageJsonFieldAuditor = agent({ + name: "packageJsonFieldAuditor", + model: "small", + instructions: p`Audit the project's package.json for standard field completeness. +${p.read("package.json")} +Use checkFieldPresence with the full file content. Return present/missing fields, completenessScore (0–100), and a recommendation.`, + output: s.object({ + presentFields: s.array(s.string), + missingFields: s.array(s.string), + completenessScore: s.int, + recommendation: s.string, + }), + tools: [checkFieldPresence], + addons: [repair()], +}); + +export default packageJsonFieldAuditor; +```