[rig-tasks] Add 10 rig samples — 2026-08-11 - #404
Conversation
Samples cover: git-file-at-revision, json-schema-structure-validator, markdown-frontmatter-extractor, git-diff-word-frequency, ts-async-function-finder, npm-dep-depth-analyzer, http-access-log-stats, ts-spread-usage-counter, git-hook-file-scanner, merge-strategy-selector. All 10/10 typecheck passed. 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 — requesting changes on correctness and security issues in the tool handlers.
📋 Key Themes & Highlights
Key Findings
- Shell injection (401):
revisionandfilePathare interpolated unsanitised intoexecSyncstrings — usespawnSyncwith an argv array instead (two call sites). - maxDepth off-by-one (406): The depth tracking update happens before recursion, so
maxDepthis always one less than the true value. - Per-line tool calls (407): Calling
parseLogLineonce per log line will exceedmaxTurnson any real log file. The tool should accept the full log content. undefinedvs absent (402): Early-return paths setschemaTitle: undefined, which serialises inconsistently — usenullor omit the key.- Regex correctness (403, 405, 408): Multiple regex patterns have edge-case bugs (frontmatter delimiter anchoring, async method coverage, nested object spread counting).
- Single-commit edge case (410):
HEAD~1reference fails on repos with one commit.
Positive Highlights
- ✅ Good variety of patterns across the 10 samples (tools, prompt intents, workflow, steering/repair addons)
- ✅ Consistent use of
s.path,s.int,s.enumover bares.stringwhere appropriate - ✅ Sample 410 correctly avoids the invalid
workflow({ agents })pattern and uses sequentialcall()invocations
🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 56.1 AIC · ⌖ 4.74 AIC · ⊞ 6.3K
Comment /matt to run again
| parameters: s.object({ revision: s.string, filePath: s.path }), | ||
| handler: async ({ revision, filePath }: { revision: string; filePath: string }) => { | ||
| const { execSync } = await import("node:child_process"); | ||
| const fileContent = execSync(`git show ${revision}:${filePath} 2>/dev/null || echo ""`, { encoding: "utf8" }); |
There was a problem hiding this comment.
[/codebase-design] Shell injection risk: revision and filePath are interpolated directly into execSync command strings without sanitisation — a caller-supplied revision like $(rm -rf .) would execute arbitrary commands.
💡 Suggested fix
Use spawnSync with an explicit argv array to avoid shell interpolation:
import { spawnSync } from "node:child_process";
const result = spawnSync("git", ["show", `${revision}:${filePath}`], { encoding: "utf8" });
const fileContent = result.stdout ?? "";This is the correct pattern whenever tool parameters flow into shell commands.
| fileContent, | ||
| commitHash: spaceIdx > -1 ? logLine.slice(0, spaceIdx) : logLine, | ||
| commitMessage: spaceIdx > -1 ? logLine.slice(spaceIdx + 1) : "", | ||
| linesCount: fileContent.split("\n").length, |
There was a problem hiding this comment.
[/codebase-design] The same shell injection concern applies to git log — revision and filePath are still interpolated unquoted. Consistently use spawnSync for both calls, as suggested on line 11.
| addons: [repair()], | ||
| }); | ||
|
|
||
| export default jsonSchemaStructureValidator; |
There was a problem hiding this comment.
[/codebase-design] schemaTitle: undefined in early-return paths conflicts with the output schema s.optional(s.string) which expects the field to be absent (or null), not undefined. Return null or omit the key to match the schema contract — some JSON serialisers will silently drop undefined, others may fail validation.
💡 Example
return { valid: false, errors: ["Invalid JSON in schema file"], checkedFields: 0, schemaTitle: null };Or change the output schema to s.optional(s.string) accepting null explicitly, if supported.
| tags = tagsMatch[1].split(/[\n,]/).map((t: string) => t.replace(/^\s*-\s*/, "").trim()).filter(Boolean); | ||
| } | ||
| return { | ||
| hasFrontmatter: true, |
There was a problem hiding this comment.
[/codebase-design] The frontmatter regex /^--- ? ([\s\S]*?) ? ---/ doesn't require the closing --- to be at the start of a line (^), so it could match a --- in the middle of a line. Add the m flag and anchor the closing delimiter:
const match = content.match(/^---
?
([\s\S]*?)
?
^---/m);Otherwise a document with --- foo --- in its body could produce a false frontmatter match.
| }); | ||
|
|
||
| // Agent role: Parse an HTTP access log file and return status count statistics. | ||
| const httpAccessLogStats = agent({ |
There was a problem hiding this comment.
[/codebase-design] The agent is asked to call parseLogLine once per log line, which could mean thousands of tool calls for a real access log. This will exceed maxTurns (default 10) and produce incomplete results without any guidance.
💡 Suggested fix
Either:
- Have the tool accept the full log content and loop internally (preferred — fewer turns, no token overhead per line), or
- Set
maxTurnsto a sensible bound and document the expected log size limit.
| const depthClass: "direct" | "transitive-shallow" | "transitive-deep" = | ||
| depth === 1 ? "direct" : depth === 2 ? "transitive-shallow" : "transitive-deep"; | ||
| if (!packages[name]) { | ||
| packages[name] = { depth, depthClass }; |
There was a problem hiding this comment.
[/codebase-design] maxDepth is updated with if (depth > maxDepth) maxDepth = depth before recursing, so it always lags one level behind. Move the update after the recursive call, or simply Math.max at each level:
walk(child, depth + 1);
if (depth > maxDepth) maxDepth = depth;As written, maxDepth will always equal one less than the true maximum depth.
| if (name) matches.push(name); | ||
| } | ||
| return { functions: matches }; | ||
| }, |
There was a problem hiding this comment.
[/codebase-design] The regex /const\s+(\w+)\s*=\s*async\s*(?:\([^)]*\)|[^=]+)\s*=>/g is brittle for multiline arrow functions and will silently miss async methods (async methodName()) in classes. Since this is a sample, the limitation should at minimum be documented in a comment so readers don't copy it expecting complete coverage.
| totalFiles: s.int, | ||
| dominantArea: s.enum("src", "test", "config", "docs", "mixed"), | ||
| }), | ||
| }); |
There was a problem hiding this comment.
[/codebase-design] branchDiffAgent uses git diff --stat HEAD~1..HEAD which will fail on a repository with a single commit (no HEAD~1). The || git diff --stat HEAD fallback still won't produce stats since HEAD with no parent means an empty diff. Consider git diff --stat $(git rev-parse --verify HEAD~1 2>/dev/null || git hash-object -t tree /dev/null) HEAD or document this limitation.
| }, | ||
| }); | ||
|
|
||
| // Agent role: Count TypeScript object spread and array spread patterns across source files. |
There was a problem hiding this comment.
[/codebase-design] The object-spread regex /\{[^}]*\.\.\./g counts characters between { and ... but [^}]* is greedy and stops at the first }, so spread patterns inside nested objects (e.g. { a: { ...x }, b: 1 }) will match on the outer { and the wrong .... Consider a simpler /\.\.\.[a-zA-Z_$]/g count as a more robust proxy for spread occurrences in the file, noting the inherent approximation.
Summary
Added 10 new rig sample files to
skills/rig/samples/.Typecheck failures
None — all 10 tasks passed typecheck. Task 10 required one fix: the initial
workflow({ agents: {...} })pattern is not valid in the current rig API. Fixed by closing over agents as variables and using sequentialcall()invocations with type casts instead ofparallel([])(which loses heterogeneous return types).Tasks run