From 3e0268fd2776436ff770802a9cfdbaf3b8bf5c87 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 07:46:01 +0000 Subject: [PATCH] Add 10 rig samples 501-510 (2026-09-01) Samples cover: Dockerfile ENV parsing, npm script prefix analysis, INI config parsing, git reflog inspection, source line length auditing, npm script dependency workflow, HTML form field extraction, dotenv template validation, TypeScript namespace usage reporting, and multi-stage build artifact pipeline. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../samples/501-dockerfile-env-inspector.md | 43 +++++++++ .../samples/502-npm-script-prefix-analyzer.md | 39 ++++++++ skills/rig/samples/503-ini-config-parser.md | 49 ++++++++++ .../rig/samples/504-git-reflog-inspector.md | 43 +++++++++ .../samples/505-source-line-length-auditor.md | 51 ++++++++++ .../samples/506-npm-script-dep-workflow.md | 60 ++++++++++++ .../samples/507-html-form-field-extractor.md | 92 +++++++++++++++++++ .../samples/508-dotenv-template-validator.md | 47 ++++++++++ .../509-ts-namespace-usage-reporter.md | 56 +++++++++++ .../510-multi-stage-artifact-pipeline.md | 62 +++++++++++++ 10 files changed, 542 insertions(+) create mode 100644 skills/rig/samples/501-dockerfile-env-inspector.md create mode 100644 skills/rig/samples/502-npm-script-prefix-analyzer.md create mode 100644 skills/rig/samples/503-ini-config-parser.md create mode 100644 skills/rig/samples/504-git-reflog-inspector.md create mode 100644 skills/rig/samples/505-source-line-length-auditor.md create mode 100644 skills/rig/samples/506-npm-script-dep-workflow.md create mode 100644 skills/rig/samples/507-html-form-field-extractor.md create mode 100644 skills/rig/samples/508-dotenv-template-validator.md create mode 100644 skills/rig/samples/509-ts-namespace-usage-reporter.md create mode 100644 skills/rig/samples/510-multi-stage-artifact-pipeline.md diff --git a/skills/rig/samples/501-dockerfile-env-inspector.md b/skills/rig/samples/501-dockerfile-env-inspector.md new file mode 100644 index 0000000..ad38ad8 --- /dev/null +++ b/skills/rig/samples/501-dockerfile-env-inspector.md @@ -0,0 +1,43 @@ +# 501 - Dockerfile ENV Inspector + +```rig +import { agent, p, s, defineTool, repair } from "rig"; +import { readFile } from "node:fs/promises"; + +const extractDockerfileEnv = defineTool("extractDockerfileEnv", { + description: "Extract ENV instructions from a Dockerfile", + parameters: s.object({ filePath: s.string }), + handler: async ({ filePath }: { filePath: string }) => { + const content = await readFile(filePath, "utf8"); + const envVars: Record = {}; + const keyValueRe = /^ENV\s+(\w+)=(\S+)/gm; + const keySpaceRe = /^ENV\s+(\w+)\s+(.+)$/gm; + let m: RegExpExecArray | null; + while ((m = keyValueRe.exec(content)) !== null) { + envVars[m[1]] = m[2]; + } + while ((m = keySpaceRe.exec(content)) !== null) { + if (!(m[1] in envVars)) envVars[m[1]] = m[2].trim(); + } + return envVars; + }, +}); + +// Agent role: Parse ENV instructions from all Dockerfiles in the workspace and classify each variable. +const dockerfileEnvInspector = agent({ + model: "small", + instructions: p`You are given the output of: ${p.bash("find . -name Dockerfile -o -name 'Dockerfile.*' 2>/dev/null | head -20")}. +For each Dockerfile path found, call extractDockerfileEnv to extract its ENV variables. +Classify each variable as build-time (used during build, e.g. VERSION, BUILD_DATE) or runtime (passed to app at runtime). +Return the declared output.`, + output: s.object({ + files: s.record(s.record(s.string)), + totalVars: s.int, + totalFiles: s.int, + }), + tools: [extractDockerfileEnv], + addons: [repair()], +}); + +export default dockerfileEnvInspector; +``` diff --git a/skills/rig/samples/502-npm-script-prefix-analyzer.md b/skills/rig/samples/502-npm-script-prefix-analyzer.md new file mode 100644 index 0000000..4051c34 --- /dev/null +++ b/skills/rig/samples/502-npm-script-prefix-analyzer.md @@ -0,0 +1,39 @@ +# 502 - NPM Script Prefix Analyzer + +```rig +import { agent, p, s, defineTool, steering, repair } from "rig"; + +const classifyScriptPrefix = defineTool("classifyScriptPrefix", { + description: "Classify the command prefix of an npm script", + parameters: s.object({ command: s.string }), + handler: ({ command }: { command: string }) => { + const first = command.trim().split(/\s+/)[0] ?? ""; + if (first === "node") return "node" as const; + if (first === "ts-node" || first === "ts-node-esm") return "ts-node" as const; + if (first === "npx") return "npx" as const; + if (first === "sh" || first === "bash" || first === "zsh" || first.startsWith("./")) return "shell" as const; + return "other" as const; + }, +}); + +// Agent role: Analyze npm script command prefixes in package.json and summarize by prefix class. +const npmScriptPrefixAnalyzer = agent({ + model: "small", + instructions: p`Analyze the npm scripts in: ${p.read("package.json")}. +For each script, call classifyScriptPrefix with the script command. +Return the declared output with per-script details and a summary of prefix counts.`, + output: s.object({ + scripts: s.record(s.object({ + command: s.string, + prefix: s.string, + prefixClass: s.enum("node", "ts-node", "npx", "shell", "other"), + })), + totalScripts: s.int, + prefixCounts: s.record(s.int), + }), + tools: [classifyScriptPrefix], + addons: [steering(), repair()], +}); + +export default npmScriptPrefixAnalyzer; +``` diff --git a/skills/rig/samples/503-ini-config-parser.md b/skills/rig/samples/503-ini-config-parser.md new file mode 100644 index 0000000..beced3b --- /dev/null +++ b/skills/rig/samples/503-ini-config-parser.md @@ -0,0 +1,49 @@ +# 503 - INI Config Parser + +```rig +import { agent, p, s, defineTool, repair } from "rig"; + +const parseIniSection = defineTool("parseIniSection", { + description: "Parse an INI config file into sections and key-value pairs", + parameters: s.object({ content: s.string }), + handler: ({ content }: { content: string }) => { + const sections: Record> = {}; + let current = "__default__"; + sections[current] = {}; + for (const raw of content.split(/\r?\n/)) { + const line = raw.trim(); + if (!line || line.startsWith(";") || line.startsWith("#")) continue; + const sectionMatch = line.match(/^\[(.+)\]$/); + if (sectionMatch) { + current = sectionMatch[1]; + sections[current] = {}; + continue; + } + const kvMatch = line.match(/^([^=]+)=(.*)$/); + if (kvMatch) { + sections[current][kvMatch[1].trim()] = kvMatch[2].trim(); + } + } + return sections; + }, +}); + +// Agent role: Parse a caller-supplied INI config file and return structured sections and key counts. +const iniConfigParser = agent({ + model: "small", + input: s.object({ configFile: s.string }), + instructions: p`Parse the INI config file at the path provided in input.configFile: ${p.readInput("configFile")}. +Call parseIniSection with the file contents. +Return the declared output.`, + 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; +``` diff --git a/skills/rig/samples/504-git-reflog-inspector.md b/skills/rig/samples/504-git-reflog-inspector.md new file mode 100644 index 0000000..4b6cece --- /dev/null +++ b/skills/rig/samples/504-git-reflog-inspector.md @@ -0,0 +1,43 @@ +# 504 - Git Reflog Inspector + +```rig +import { agent, p, s, defineTool, steering } from "rig"; + +const classifyReflogEntry = defineTool("classifyReflogEntry", { + description: "Classify a single git reflog entry line", + parameters: s.object({ line: s.string }), + handler: ({ line }: { line: string }) => { + const hashMatch = line.match(/^([0-9a-f]+)/); + const hash = hashMatch ? hashMatch[1] : ""; + const lower = line.toLowerCase(); + let action: "commit" | "merge" | "rebase" | "reset" | "checkout" | "other" = "other"; + if (lower.includes("merge")) action = "merge"; + else if (lower.includes("rebase")) action = "rebase"; + else if (lower.includes("reset")) action = "reset"; + else if (lower.includes("checkout")) action = "checkout"; + else if (lower.includes("commit")) action = "commit"; + return { hash, action }; + }, +}); + +// Agent role: Inspect the last 50 git reflog entries, classify each action, and return counts. +const gitReflogInspector = agent({ + model: "small", + instructions: p`Inspect git reflog: ${p.bash("git reflog --oneline -50 2>/dev/null || echo 'no reflog'")}. +For each line, call classifyReflogEntry to get the hash and action. +Return the declared output.`, + output: s.object({ + entries: s.array(s.object({ + hash: s.string, + action: s.enum("commit", "merge", "rebase", "reset", "checkout", "other"), + message: s.string, + })), + actionCounts: s.record(s.number), + totalEntries: s.number, + }), + tools: [classifyReflogEntry], + addons: [steering()], +}); + +export default gitReflogInspector; +``` diff --git a/skills/rig/samples/505-source-line-length-auditor.md b/skills/rig/samples/505-source-line-length-auditor.md new file mode 100644 index 0000000..de5f44e --- /dev/null +++ b/skills/rig/samples/505-source-line-length-auditor.md @@ -0,0 +1,51 @@ +# 505 - Source Line Length Auditor + +```rig +import { agent, p, s, defineTool, repair } from "rig"; +import { readFile } from "node:fs/promises"; + +const auditLineLengths = defineTool("auditLineLengths", { + description: "Audit line lengths in a source file", + parameters: s.object({ filePath: s.string }), + handler: async ({ filePath }: { filePath: string }) => { + const content = await readFile(filePath, "utf8"); + const lines = content.split(/\r?\n/); + let total = 0; + let maxLength = 0; + let longLineCount = 0; + let veryLongLineCount = 0; + for (const line of lines) { + const len = line.length; + total += len; + if (len > maxLength) maxLength = len; + if (len > 80) longLineCount++; + if (len > 120) veryLongLineCount++; + } + const avgLength = lines.length > 0 ? total / lines.length : 0; + return { avgLength, maxLength, longLineCount, veryLongLineCount }; + }, +}); + +// Agent role: Audit line lengths across all TypeScript source files and identify the most verbose file. +const sourceLineLengthAuditor = agent({ + model: "small", + instructions: p`Audit TypeScript source files found at: ${p.bash("find src -name '*.ts' 2>/dev/null | head -50 || echo 'no src dir'")}. +For each file path, call auditLineLengths to get per-file statistics. +Return the declared output.`, + output: s.object({ + files: s.record(s.object({ + avgLength: s.number, + maxLength: s.number, + longLineCount: s.number, + veryLongLineCount: s.number, + })), + totalFiles: s.number, + globalMaxLength: s.number, + mostVerboseFile: s.optional(s.string), + }), + tools: [auditLineLengths], + addons: [repair()], +}); + +export default sourceLineLengthAuditor; +``` diff --git a/skills/rig/samples/506-npm-script-dep-workflow.md b/skills/rig/samples/506-npm-script-dep-workflow.md new file mode 100644 index 0000000..edc0af1 --- /dev/null +++ b/skills/rig/samples/506-npm-script-dep-workflow.md @@ -0,0 +1,60 @@ +# 506 - NPM Script Dep Workflow + +```rig +import { agent, workflow, p, s } from "rig"; + +// Agent role: Read package.json and build a graph of npm script dependencies via pre/post hooks. +const scriptGraphBuilder = agent({ + name: "scriptGraphBuilder", + model: "small", + instructions: p`Read package.json: ${p.read("package.json")}. +Extract all npm scripts. For each script, identify its pre/post hook dependencies (e.g., "prebuild" runs before "build"). +Return the declared output.`, + output: s.object({ + scripts: s.record(s.object({ + command: s.string, + deps: s.array(s.string), + })), + totalScripts: s.int, + }), +}); + +// Agent role: Detect cycles in a script dependency graph using DFS. +const cycleDetector = agent({ + name: "cycleDetector", + model: "small", + input: s.object({ + scripts: s.record(s.object({ + command: s.string, + deps: s.array(s.string), + })), + totalScripts: s.int, + }), + instructions: `Analyze the provided script dependency graph for cycles using DFS. +Return whether cycles exist, list any cycles found, and the total node count.`, + output: s.object({ + hasCycles: s.boolean, + cycles: s.array(s.array(s.string)), + totalNodes: s.int, + }), +}); + +// Workflow role: Build npm script dependency graph then detect cycles. +const npmScriptDepWorkflow = workflow({ + meta: { name: "npm-script-dep-workflow", description: "Analyze npm script pre/post dependency graph and detect cycles" }, + body: async ({ call }) => { + const graph = await call(scriptGraphBuilder, "build script graph from package.json"); + const scripts = graph?.scripts ?? {}; + const totalScripts = graph?.totalScripts ?? 0; + const cycles = await call(cycleDetector, { scripts, totalScripts }); + const hasCycles = cycles?.hasCycles ?? false; + const cycleList = cycles?.cycles ?? []; + let recommendation: "safe" | "has-cycles" | "empty" = "safe"; + if (totalScripts === 0) recommendation = "empty"; + else if (hasCycles) recommendation = "has-cycles"; + return { scriptCount: totalScripts, hasCycles, cycles: cycleList, recommendation }; + }, +}); + +export default npmScriptDepWorkflow; +``` diff --git a/skills/rig/samples/507-html-form-field-extractor.md b/skills/rig/samples/507-html-form-field-extractor.md new file mode 100644 index 0000000..e7bfabf --- /dev/null +++ b/skills/rig/samples/507-html-form-field-extractor.md @@ -0,0 +1,92 @@ +# 507 - HTML Form Field Extractor + +```rig +import { agent, p, s, defineTool, repair } from "rig"; + +const extractFormFields = defineTool("extractFormFields", { + description: "Extract HTML form fields from HTML content", + parameters: s.object({ content: s.string }), + handler: ({ content }: { content: string }) => { + type FieldType = "text" | "email" | "password" | "checkbox" | "radio" | "select" | "textarea" | "other"; + const fields: Array<{ name?: string; type: FieldType; required: boolean; id?: string }> = []; + const inputRe = /]*)>/gi; + const selectRe = /]*)>/gi; + const textareaRe = /]*)>/gi; + const getAttr = (attrs: string, attr: string): string | undefined => { + const m = attrs.match(new RegExp(`${attr}\\s*=\\s*["']?([^"'\\s>]*)`, "i")); + return m ? m[1] : undefined; + }; + const classifyType = (t?: string): FieldType => { + if (!t) return "text"; + const lower = t.toLowerCase(); + if (lower === "email") return "email"; + if (lower === "password") return "password"; + if (lower === "checkbox") return "checkbox"; + if (lower === "radio") return "radio"; + if (lower === "text") return "text"; + return "other"; + }; + let m: RegExpExecArray | null; + while ((m = inputRe.exec(content)) !== null) { + const attrs = m[1]; + const entry: { name?: string; type: FieldType; required: boolean; id?: string } = { + type: classifyType(getAttr(attrs, "type")), + required: /\brequired\b/i.test(attrs), + }; + const name = getAttr(attrs, "name"); + if (name !== undefined) entry.name = name; + const id = getAttr(attrs, "id"); + if (id !== undefined) entry.id = id; + fields.push(entry); + } + while ((m = selectRe.exec(content)) !== null) { + const attrs = m[1]; + const entry: { name?: string; type: FieldType; required: boolean; id?: string } = { + type: "select" as const, + required: /\brequired\b/i.test(attrs), + }; + const name = getAttr(attrs, "name"); + if (name !== undefined) entry.name = name; + const id = getAttr(attrs, "id"); + if (id !== undefined) entry.id = id; + fields.push(entry); + } + while ((m = textareaRe.exec(content)) !== null) { + const attrs = m[1]; + const entry: { name?: string; type: FieldType; required: boolean; id?: string } = { + type: "textarea" as const, + required: /\brequired\b/i.test(attrs), + }; + const name = getAttr(attrs, "name"); + if (name !== undefined) entry.name = name; + const id = getAttr(attrs, "id"); + if (id !== undefined) entry.id = id; + fields.push(entry); + } + return fields; + }, +}); + +// Agent role: Extract and classify HTML form fields from a caller-supplied HTML file. +const htmlFormFieldExtractor = agent({ + model: "small", + input: s.object({ htmlFile: s.string }), + instructions: p`Extract form fields from the HTML file at input.htmlFile: ${p.readInput("htmlFile")}. +Call extractFormFields with the file content. +Return the declared output.`, + output: s.object({ + fields: s.array(s.object({ + name: s.optional(s.string), + type: s.enum("text", "email", "password", "checkbox", "radio", "select", "textarea", "other"), + required: s.boolean, + id: s.optional(s.string), + })), + totalFields: s.int, + fieldTypeCounts: s.record(s.int), + }), + tools: [extractFormFields], + addons: [repair()], +}); + +export default htmlFormFieldExtractor; +``` diff --git a/skills/rig/samples/508-dotenv-template-validator.md b/skills/rig/samples/508-dotenv-template-validator.md new file mode 100644 index 0000000..df4acc9 --- /dev/null +++ b/skills/rig/samples/508-dotenv-template-validator.md @@ -0,0 +1,47 @@ +# 508 - Dotenv Template Validator + +```rig +import { agent, p, s, defineTool, steering } from "rig"; + +const compareEnvKeys = defineTool("compareEnvKeys", { + description: "Compare env keys between a template and actual .env file", + parameters: s.object({ template: s.string, actual: s.string }), + handler: ({ template, actual }: { template: string; actual: string }) => { + const parseKeys = (content: string) => + content.split(/\r?\n/) + .map((l: string) => l.trim()) + .filter((l: string) => l && !l.startsWith("#")) + .map((l: string) => l.split("=")[0].trim()) + .filter(Boolean); + const templateKeys = new Set(parseKeys(template)); + const actualKeys = new Set(parseKeys(actual)); + const missingInEnv = [...templateKeys].filter((k: string) => !actualKeys.has(k)); + const extraInEnv = [...actualKeys].filter((k: string) => !templateKeys.has(k)); + const completenessScore = templateKeys.size > 0 + ? Math.round(((templateKeys.size - missingInEnv.length) / templateKeys.size) * 100) + : 100; + return { missingInEnv, extraInEnv, totalTemplate: templateKeys.size, totalActual: actualKeys.size, completenessScore }; + }, +}); + +// Agent role: Compare .env.example template keys against actual .env and report completeness. +const dotenvTemplateValidator = agent({ + model: "small", + instructions: p`Compare env template ${p.read(".env.example")} with actual env ${p.readOptional(".env", "")}. +Call compareEnvKeys with both file contents. +Determine the status: complete (no missing, no extra), missing-keys, extra-keys, or mismatch (both). +Return the declared output.`, + output: s.object({ + missingInEnv: s.array(s.string), + extraInEnv: s.array(s.string), + totalTemplate: s.int, + totalActual: s.int, + completenessScore: s.number, + status: s.enum("complete", "missing-keys", "extra-keys", "mismatch"), + }), + tools: [compareEnvKeys], + addons: [steering()], +}); + +export default dotenvTemplateValidator; +``` diff --git a/skills/rig/samples/509-ts-namespace-usage-reporter.md b/skills/rig/samples/509-ts-namespace-usage-reporter.md new file mode 100644 index 0000000..b8c4716 --- /dev/null +++ b/skills/rig/samples/509-ts-namespace-usage-reporter.md @@ -0,0 +1,56 @@ +# 509 - TS Namespace Usage Reporter + +```rig +import { agent, p, s, defineTool, repair } from "rig"; +import { readFile } from "node:fs/promises"; + +const scanNamespaces = defineTool("scanNamespaces", { + description: "Scan a TypeScript file for namespace declarations and usages", + parameters: s.object({ filePath: s.string }), + handler: async ({ filePath }: { filePath: string }) => { + const content = await readFile(filePath, "utf8"); + const results: Record = {}; + const declRe = /\bnamespace\s+(\w+)/g; + const augRe = /declare\s+(?:module|namespace)\s+['"]?([\w.]+)/g; + const accessRe = /\b([A-Z]\w*)(?:\.\w+){1,}/g; + let m: RegExpExecArray | null; + while ((m = declRe.exec(content)) !== null) { + const ns = m[1]; + if (!results[ns]) results[ns] = { declarationCount: 0, accessCount: 0, augmentationCount: 0 }; + results[ns].declarationCount++; + } + while ((m = augRe.exec(content)) !== null) { + const ns = m[1].split(".")[0]; + if (!results[ns]) results[ns] = { declarationCount: 0, accessCount: 0, augmentationCount: 0 }; + results[ns].augmentationCount++; + } + while ((m = accessRe.exec(content)) !== null) { + const ns = m[1]; + if (results[ns]) results[ns].accessCount++; + } + return results; + }, +}); + +// Agent role: Report TypeScript namespace declarations and access patterns across all source files. +const tsNamespaceUsageReporter = agent({ + model: "small", + instructions: p`Scan TypeScript files: ${p.glob("src/**/*.ts")}. +For each file path, call scanNamespaces to detect namespace usages. +Aggregate results across all files and identify the most used namespace. +Return the declared output.`, + output: s.object({ + namespaces: s.record(s.object({ + declarationCount: s.int, + accessCount: s.int, + augmentationCount: s.int, + })), + totalFiles: s.int, + mostUsedNamespace: s.optional(s.string), + }), + tools: [scanNamespaces], + addons: [repair()], +}); + +export default tsNamespaceUsageReporter; +``` diff --git a/skills/rig/samples/510-multi-stage-artifact-pipeline.md b/skills/rig/samples/510-multi-stage-artifact-pipeline.md new file mode 100644 index 0000000..232ae06 --- /dev/null +++ b/skills/rig/samples/510-multi-stage-artifact-pipeline.md @@ -0,0 +1,62 @@ +# 510 - Multi Stage Artifact Pipeline + +```rig +import { agent, workflow, p, s } from "rig"; + +// Agent role: Discover build artifacts in the dist directory. +const artifactDiscoverer = agent({ + name: "artifactDiscoverer", + model: "small", + instructions: p`List build artifacts: ${p.bash("find dist -type f \\( -name '*.js' -o -name '*.d.ts' -o -name '*.js.map' \\) 2>/dev/null | head -100 || echo 'no dist'")}. +For each file, report its path, approximate size (use 0 if unknown), and file extension type. +Return the declared output.`, + output: s.object({ + artifacts: s.array(s.object({ + path: s.path, + sizeBytes: s.int, + type: s.string, + })), + totalFound: s.int, + }), +}); + +// Agent role: Classify build artifacts by kind (esm, cjs, declaration, sourcemap, other). +const artifactClassifier = agent({ + name: "artifactClassifier", + model: "small", + input: s.object({ + artifacts: s.array(s.object({ + path: s.path, + sizeBytes: s.int, + type: s.string, + })), + totalFound: s.int, + }), + instructions: `Classify each artifact: .d.ts → declaration, .js.map → sourcemap, .mjs or esm/**/*.js → esm, .cjs or cjs/**/*.js → cjs, else other. +Return the classified list and a byType count.`, + output: s.object({ + classified: s.array(s.object({ + path: s.path, + kind: s.enum("esm", "cjs", "declaration", "sourcemap", "other"), + })), + byType: s.record(s.int), + }), +}); + +// Workflow role: Discover, classify, and report on dist build artifacts in three sequential stages. +const multiStageArtifactPipeline = workflow({ + meta: { name: "multi-stage-artifact-pipeline", description: "Three-stage pipeline to discover, classify, and report dist build artifacts" }, + body: async ({ call }) => { + const discovered = await call(artifactDiscoverer, "discover dist artifacts"); + const artifacts = discovered?.artifacts ?? []; + const totalFound = discovered?.totalFound ?? 0; + const classified = await call(artifactClassifier, { artifacts, totalFound }); + const byType = classified?.byType ?? {}; + const totalArtifacts = classified?.classified?.length ?? 0; + const summary = `Found ${totalArtifacts} artifacts: ${Object.entries(byType).map(([k, v]) => `${v} ${k}`).join(", ")}`; + return { reportPath: "dist/artifact-report.json", totalArtifacts, byType, summary }; + }, +}); + +export default multiStageArtifactPipeline; +```