[rig-tasks] Add 10 rig samples — 2026-07-29 - #272
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 /codebase-design and /grill-with-docs — requesting changes on three correctness issues and one API-pattern ambiguity.
📋 Key Themes & Highlights
Issues Found
- Logic bug (309):
selectMidpointdeclarescurrentBad/currentGoodparameters but ignores them — the binary search never actually narrows. Readers copying this pattern will get a tool that always bisects the full list. - Wrong default case (306):
classifyDiffEntryreturns"renamed"when additions and deletions are both 0. That is an unchanged file in numstat, not a rename. - Shadowed import (308): lambda parameter
sshadows thesschema builder from"rig"— confusing in teaching material. - Pattern ambiguity (307):
p.writein instructions andtemplateGenerated: s.boolean/templatePath: s.pathin output try to do the same thing two ways simultaneously. Pick one idiom.
Positive Highlights
- ✅ All 10 samples pass typecheck on first attempt — strong signal of well-structured generation.
- ✅ Good variety of real-world use cases covering async tools,
s.record,s.optional,p.readOptional,p.glob, andp.write. - ✅ Consistent use of
steering()andrepair()addons where appropriate. - ✅ Progressive complexity from simple regex tools (300–303) to async node:fs tools (305, 307, 308).
🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 51.3 AIC · ⌖ 6.52 AIC · ⊞ 6.3K
Comment /matt to run again
| const classes: Array<{ className: string; parent?: string; interfaces: string[]; isAbstract: boolean }> = []; | ||
| let m: RegExpExecArray | null; | ||
| while ((m = pattern.exec(content)) !== null) { | ||
| const interfaces = m[4] ? m[4].split(",").map((s: string) => s.trim()).filter(Boolean) : []; |
There was a problem hiding this comment.
[/grill-with-docs] The lambda parameter s shadows the top-level s schema builder imported from "rig". Inside this callback s refers to the string element, not the schema helper — confusing in a sample meant to teach rig patterns.
💡 Suggested rename
Rename the lambda parameter to avoid the collision:
const interfaces = m[4] ? m[4].split(",").map((iface: string) => iface.trim()).filter(Boolean) : [];Samples are the primary teaching material for rig; any name that collides with a core import creates a false mental model for readers.
| parameters: s.object({ commits: s.array(s.string), currentBad: s.string, currentGood: s.string }), | ||
| handler({ commits }) { | ||
| if (commits.length === 0) return { midpoint: null, remaining: 0 }; | ||
| const mid = Math.floor(commits.length / 2); |
There was a problem hiding this comment.
[/codebase-design] selectMidpoint ignores currentBad and currentGood — both parameters are declared but never used. The tool's binary-search purpose is undermined: it always bisects the full commits array rather than the narrowed [currentGood..currentBad] slice.
💡 Suggested fix
Either remove the unused parameters from the schema, or implement the narrowing:
handler({ commits, currentBad, currentGood }) {
const badIdx = commits.indexOf(currentBad);
const goodIdx = commits.indexOf(currentGood);
const slice = badIdx !== -1 && goodIdx !== -1
? commits.slice(Math.min(badIdx, goodIdx), Math.max(badIdx, goodIdx) + 1)
: commits;
const mid = Math.floor(slice.length / 2);
return { midpoint: slice[mid] ?? null, remaining: slice.length };
},As a sample demonstrating bisect logic, readers will copy this pattern — unused params that carry semantic meaning are a correctness trap.
| templatePath: s.path, | ||
| envKeys: s.array(s.string), | ||
| undocumentedKeys: s.array(s.string), | ||
| templateGenerated: s.boolean, |
There was a problem hiding this comment.
[/grill-with-docs] The p.write call in the instructions prompt is a placeholder that tells the LLM what to write — but the output schema already declares templateGenerated: s.boolean and templatePath: s.path. The sample conflates two different approaches: p.write as a prompt intent vs. returning a path for the caller to act on. Only one pattern should be modeled.
💡 Consider
If the intent is to show p.write as a prompt intent that instructs the model to write the file, the output schema fields templateGenerated and templatePath are redundant and should be removed. If the intent is to return structured data for the caller to write the file, remove the p.write from the instructions and keep the output schema. Mixing both patterns in one sample will confuse readers about when to use each approach.
| if (additions > 0 && deletions === 0) return "added" as const; | ||
| if (additions === 0 && deletions > 0) return "deleted" as const; | ||
| if (additions > 0 && deletions > 0) return "modified" as const; | ||
| return "renamed" as const; |
There was a problem hiding this comment.
[/codebase-design] classifyDiffEntry returns "renamed" as the fallback when both additions === 0 and deletions === 0 — but that is actually an unchanged file in git diff --numstat output. A renamed file in numstat typically shows as 0 0 old => new or {old => new}. The sample teaches an incorrect classification heuristic.
💡 Suggested fix
handler({ additions, deletions }) {
if (additions > 0 && deletions === 0) return "added" as const;
if (additions === 0 && deletions > 0) return "deleted" as const;
if (additions > 0 && deletions > 0) return "modified" as const;
return "unchanged" as const; // 0/0 means no net line changes, not a rename
},Rename detection would require inspecting the file path string (e.g. {old => new} pattern). This is worth a brief comment so readers don't copy the wrong semantic.
| parameters: s.object({ content: s.string }), | ||
| handler({ content }) { | ||
| const routes: Array<{ path: string; method: string }> = []; | ||
| const pattern = /router\.(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`]+)['"`]/gi; |
There was a problem hiding this comment.
[/grill-with-docs] extractRoutes only matches Express-style router.METHOD() calls and returns them directly as the tool's return value, but the instructions pass the raw grep output (not the parsed code) to the tool. The tool receives the entire concatenated grep lines as content, which includes line numbers and file prefixes (file.ts:42:router.get(...)). The regex router\. will work on this string, but the file-prefixed format isn't called out — a reader adapting this sample may get silent mismatches.
💡 Suggestion
Either document in a comment that grep -h strips the file prefix (which the bash command already uses), or adjust the regex to anchor more carefully. A small clarifying comment near the p.bash line would close the gap for readers:
// -h suppresses filename prefix so each line starts with the code directly
${p.bash("grep -rhn 'router\.' ...")}
Summary
Added 10 new rig sample files to
skills/rig/samples/.Typecheck failures
None — all 10 programs passed typecheck on first attempt.
Tasks run