[rig-tasks] Add 10 rig samples — 2026-08-10 - #395
Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅ |
There was a problem hiding this comment.
Skills-Based Review 🧠
Applied /grill-with-docs — requesting changes on 3 correctness issues where samples encode misleading or incorrect rig API usage patterns.
📋 Key Themes & Highlights
Issues (correctness — readers will copy these patterns)
p.writeOutputvsp.writeInput(391):outputFileis a caller-supplied input field but the sample writes to a hardcoded static path, silently ignoring it. Should usep.writeInput.- Hardcoded
p.bashin 395: ReadsHEAD:README.mdat definition time regardless of the agent's ownfilePath/revisioninput fields — a self-contradicting sample. require()in ESM handler (395): Only instance across all 10 samples; breaks the consistentimportconvention.p.glob("**/*")ignorestargetDirinput (398): Files are enumerated across the entire workspace, not scoped to the input directory.- Optional
algorithmmismatch (398): Declared optional in agent input, required in tool parameters — forces the LLM to guess a value silently.
Positive Highlights
- ✅ Clean use of
repair()vssteering()addons chosen appropriately by task type - ✅
p.readOptionalused correctly in 393 for the potentially-absent SSH config - ✅
p.readInput(field)used correctly in 396 for caller-supplied file paths - ✅
node:prefix consistently used on Node.js builtins (except therequirein 395) - ✅ 399 correctly falls back to sequential
call()after theparalleltype-inference issue
🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 55.2 AIC · ⌖ 4.73 AIC · ⊞ 6.3K
Comment /matt to run again
| List .ts files (excluding index.ts): ${p.bash("find . -name '*.ts' -not -name 'index.ts' -not -name '*.d.ts' 2>/dev/null | head -30")} | ||
| For each relevant file in sourceDir, call extractExportedSymbols. | ||
| Then call p.write equivalent: use the write intent below to persist the barrel. | ||
| ${p.writeOutput("barrelContent", "barrel-out.ts")} |
There was a problem hiding this comment.
[/grill-with-docs] p.writeOutput hardcodes the destination path (barrel-out.ts), silently ignoring the caller-supplied outputFile input field. Readers who copy this pattern will lose dynamic path support.
💡 Fix: use p.writeInput for caller-supplied paths
p.writeOutput(field, path) requires a static path. Since outputFile comes from the agent's input, use:
${p.writeInput("outputFile", "barrelContent")}See references/prompt-intents.md — p.writeOutput vs p.writeInput is the exact distinction documented there.
| description: "Run git log to get commit hash and message for a revision", | ||
| parameters: s.object({ revision: s.string }), | ||
| handler({ revision }) { | ||
| const { execSync } = require("node:child_process"); |
There was a problem hiding this comment.
[/grill-with-docs] require() is used inside an ES module handler. All other samples use top-level import — this is the sole exception and it will confuse readers about the expected module style.
💡 Fix: top-level import
Move the import to the top of the file alongside the other imports:
import { execSync } from "node:child_process";Then remove the inline require. This is consistent with the node: prefix convention used throughout the samples.
| model: "small", | ||
| input: s.object({ filePath: s.string, revision: s.string }), | ||
| instructions: p`Fetch the file content and commit metadata for the given revision. | ||
| File at revision: ${p.bash("git show HEAD:README.md 2>/dev/null | head -5 || echo '(use input revision and filePath)'")} |
There was a problem hiding this comment.
[/grill-with-docs] The p.bash intent reads HEAD:README.md unconditionally at definition time, not the actual filePath/revision from input. This leaks repository-specific content into the sample's prompt and won't generalize — readers see a misleading pattern for reading input-based git revisions.
💡 Suggested replacement
The correct intent for fetching a file at a caller-supplied revision is either a p.bash using the runtime input fields, or better, a defineTool that runs git show. The current hint comment (use input revision and filePath) acknowledges this but leaves the wrong intent in place. Consider:
instructions: p`Fetch file content and commit metadata.
Call extractRevisionMetadata with the revision from input, then call getFileAtRevision.
Return the declared output.`,And add a getFileAtRevision tool alongside extractRevisionMetadata using execSync (or execa) with git show ${revision}:${filePath}.
| model: "small", | ||
| input: s.object({ targetDir: s.string, algorithm: s.optional(s.string) }), | ||
| instructions: p`Compute file hashes for all files in targetDir. | ||
| Files found: ${p.glob("**/*")} |
There was a problem hiding this comment.
[/grill-with-docs] p.glob("**/*") lists all files in the entire workspace, not the caller-supplied targetDir. This ignores the agent's own input contract and is a common misuse pattern readers will repeat.
💡 Fix: scope the glob to targetDir
Since targetDir is a runtime input value, you cannot embed it directly in a p.glob call (which is static). The right approach is to use p.bash with the directory from input, or enumerate files inside computeFileHash's caller loop using a listDir tool:
instructions: p`Compute file hashes for all files in targetDir from input.
List files: ${p.bash("find . -type f | head -50")}
For each file in targetDir call computeFileHash.`,Alternatively, add a listFilesInDir tool that receives targetDir from the LLM and uses readdir with full paths.
| tools: [ | ||
| defineTool("computeFileHash", { | ||
| description: "Compute the cryptographic hash and size of a file", | ||
| parameters: s.object({ filePath: s.path, algorithm: s.string }), |
There was a problem hiding this comment.
[/grill-with-docs] The algorithm parameter is declared s.optional(s.string) in the agent input but required (s.string, no optional) in the tool's parameters. If the caller omits algorithm, the LLM must invent a value — a silent default-guessing trap.
💡 Fix: resolve algorithm before passing to tool
Either default it in the agent instructions (use 'sha256' if no algorithm supplied) and document that in the instructions, or make the tool parameter optional and default inside the handler:
parameters: s.object({ filePath: s.path, algorithm: s.optional(s.string) }),
async handler({ filePath, algorithm = "sha256" }) { ... }| const listed = await call(scriptsLister, "list scripts"); | ||
| const health = await call(scriptsHealthChecker, "check health"); | ||
| const scripts = listed?.scripts ?? []; | ||
| const categorized = await call(scriptsCategorizer, { scripts }); |
There was a problem hiding this comment.
[/grill-with-docs] health result is used with optional chaining (health?.dependencyHealth) but listed is also accessed with optional chaining (listed?.scripts ?? []). Both subagents have fully-typed, required output schemas, so ? is unnecessary and misleads readers into thinking nullable results are normal here.
💡 Minor but patterns matter in samples
If call() returns null on failure it signals an error path that isn't handled — in that case explicit error handling is better than silent fallback. Samples should reflect correct usage:
const listed = await call(scriptsLister, "list scripts");
const scripts = listed.scripts; // typed, no fallback needed if error is surfacedIf resilient fallback is desired, add a comment explaining why.
Summary
Added 10 new rig sample files to
skills/rig/samples/.Typecheck failures
Two tasks required fixes before passing:
Task 6 (396-json-schema-validator): Initial code used
Record<string, unknown>for parsed JSON schema, triggeringTS4111: Property 'required' comes from an index signatureerrors. Fixed by declaring a typed interface for the schema shape.Task 9 (399-pkg-scripts-trio-workflow): Initial code used
parallel([() => call(agentA, ...), () => call(agentB, ...)])with heterogeneous agent output types.parallel<Result>infers a single homogeneous type, so TypeScript could not reconcile the differing outputs. Fixed by using sequentialcall()statements instead.Tasks run