Skip to content

[rig-tasks] Add 10 rig samples — 2026-08-14 - #421

Merged
pelikhan merged 1 commit into
mainfrom
rig-tasks/2026-08-14-84de4a875a6a1c35
Aug 15, 2026
Merged

[rig-tasks] Add 10 rig samples — 2026-08-14#421
pelikhan merged 1 commit into
mainfrom
rig-tasks/2026-08-14-84de4a875a6a1c35

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Summary

Added 10 new rig sample files to skills/rig/samples/.

# File Description Typecheck
1 411-ts-decorator-usage-scanner.md TS Decorator Usage Scanner pass
2 412-git-tag-semver-validator.md Git Tag Semver Validator pass
3 413-package-json-completeness-scorer.md Package JSON Completeness Scorer pass
4 414-parallel-branch-analysis.md Parallel Branch Analysis Workflow pass
5 415-lockfile-integrity-checker.md Lockfile Integrity Checker pass
6 416-graphql-schema-type-extractor.md GraphQL Schema Type Extractor pass
7 417-git-stale-branch-reporter.md Git Stale Branch Reporter pass
8 418-ts-abstract-class-finder.md TS Abstract Class Finder pass
9 419-dockerfile-env-inspector.md Dockerfile ENV Inspector pass
10 420-npm-script-prefix-analyzer.md NPM Script Prefix Analyzer pass

Typecheck failures

No final typecheck failures. Two tasks required fixes before passing:

  • Task 4 (parallel-branch-analysis): WorkflowMeta requires description (not just name); also parallel() requires homogeneous result type across thunks — solved by giving both subagents the same unified output schema.
  • Task 5 (lockfile-integrity-checker): Record<string, unknown> requires bracket notation for property access due to noPropertyAccessFromIndexSignature — fixed entry.integrityentry["integrity"].

Tasks run

  • (reused) TypeScript decorator usage scanner — p.glob + async defineTool + repair()
  • (reused) Git tag semver validator — p.bash + defineTool + steering() + as const literals
  • (reused) Package.json completeness scorer — p.read + defineTool + repair()
  • (reused) Parallel branch analysis workflow — workflow() + parallel() + call.json()
  • (reused) Lockfile integrity checker — p.read + defineTool + repair()
  • (reused) GraphQL schema type extractor — p.bash + async defineTool + s.record output + steering()
  • (new) Git stale branch age reporter — p.bash + defineTool with node:child_process + repair()
  • (new) TypeScript abstract class finder — p.glob + async defineTool + steering()
  • (new) Dockerfile ENV inspector — p.bash find + async defineTool + nested s.record + repair()
  • (new) NPM script prefix analyzer — p.read + defineTool + steering() + as const literals

Generated by Daily Rig Task Generator · sonnet46 129.2 AIC · ⌖ 9.44 AIC · ⊞ 6.8K ·

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@pelikhan
pelikhan marked this pull request as ready for review August 15, 2026 13:17
@pelikhan
pelikhan merged commit 1516b39 into main Aug 15, 2026
1 check passed
@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor Author

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /codebase-design and /grill-with-docs — requesting changes on correctness issues that would mislead readers who copy these patterns.

📋 Key Themes & Highlights

Key Themes

  • Shell injection (417): execSync with an interpolated branch name should use spawnSync + an argument array — samples are teaching material and this pattern is unsafe to copy.
  • File-scoped vs. class-scoped counters (418): methodCount reports all methods in the file, not per-class. Multi-class files will produce inflated, misleading values.
  • Double-parsing in Dockerfile ENV (419): Two overlapping regexes both match ENV KEY VALUE form; the second pass writes the key twice (to the same value, so no corruption, but the logic is fragile and confusing).
  • JSON round-trip overhead (415): Asking the LLM to serialize a lock entry to a JSON string so the tool can immediately JSON.parse it adds friction, increases token usage, and introduces a failure mode that repair() then has to catch.
  • Undocumented regex scope (416): The GraphQL regex silently truncates types with nested braces; a brief comment would prevent readers from applying it to real-world schemas.

Positive Highlights

  • as const on literal return values in 412 and 420 — great TypeScript practice.
  • ✅ Bracket notation for Record<string, unknown> access in 415 — correctly addressed noPropertyAccessFromIndexSignature.
  • ✅ Homogeneous output schema across both parallel() thunks in 414 — demonstrates the correct constraint clearly.
  • ✅ Consistent use of repair() on output-sensitive agents and steering() where correction via prompting is preferable.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 53.6 AIC · ⌖ 4.73 AIC · ⊞ 6.3K
Comment /matt to run again

let lastCommit: string;
try {
lastCommit = execSync(`git log -1 --format=%ci "${branchName}" 2>/dev/null`, { encoding: "utf8" }).trim();
} catch {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] Shell injection risk: branchName is interpolated directly into the shell command without sanitisation. A branch name containing shell metacharacters (e.g. "; rm -rf .) could execute arbitrary commands — a bad pattern for teaching material.

💡 Suggested fix: use spawnSync with an argument array
import { spawnSync } from "node:child_process";
const result = spawnSync("git", ["log", "-1", "--format=%ci", branchName], { encoding: "utf8" });
lastCommit = result.stdout.trim();

This avoids the shell entirely — no injection surface, clearer intent, and a better pattern for readers to learn from.

// Count all method-like patterns in the file as approximation
const allMethods = (content.match(/^\s+(?:(?:public|private|protected|async|static|readonly)\s+)*\w+\s*\(/gm) ?? []).length;
const abstractMethods = (content.match(abstractMethodRe) ?? []).length;
classes.push({ name, methodCount: allMethods, abstractMethodCount: abstractMethods, sourceFile: filePath });

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] The method-count logic is file-scoped, not class-scoped, so every abstract class in the file gets the same (inflated) methodCount. If the file has two abstract classes, each reports all methods in the entire file.

💡 What to do instead

To count per-class methods accurately you need to track the class body's brace depth after each abstract class match. For a sample, it's fine to simplify the claim — just narrow the comment or instructions to "approximate total methods in the file" so readers aren't misled:

// Count all method-like patterns in the file as approximation (not per-class)
const allMethods = (content.match(/.../) ?? []).length;

Samples that teach a wrong mental model propagate the mistake to every reader who copies the pattern.

const key = pair.slice(0, eqIdx);
const value = pair.slice(eqIdx + 1);
const envType = /^(BUILD_|CI_)/.test(key) ? "build-time" as const : "runtime" as const;
results[key] = { value, envType };

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] Double-parsing overlap: the singleRe regex (ENV KEY VALUE) and assignRe (ENV ...) will both match lines in KEY VALUE form, causing each such variable to be written to results twice. assignRe matches any ENV ... line — including single-form lines — and will find no = sign, silently discarding the pair, but if the line happens to match, a later sample derived from this could exhibit subtle duplication bugs.

💡 Cleaner approach

Run only one pass and branch on whether the segment contains =:

const envRe = /^ENV\s+(.+)$/gm;
while ((m = envRe.exec(content)) !== null) {
  const segment = m[1];
  if (segment.includes("=")) {
    // KEY=VALUE [KEY2=VALUE2 ...] form
    for (const pair of segment.split(/\s+/)) {
      const eq = pair.indexOf("=");
      if (eq > 0) results[pair.slice(0, eq)] = { value: pair.slice(eq + 1), envType: classify(pair.slice(0, eq)) };
    }
  } else {
    // KEY VALUE form (exactly two tokens)
    const [key, ...rest] = segment.split(/\s+/);
    if (key && rest.length) results[key] = { value: rest.join(" "), envType: classify(key) };
  }
}

const results: Array<{ name: string; kind: string; fieldCount: number; fields: string[]; sourceFile: string }> = [];
const re = /^(type|input|enum|interface|union)\s+(\w+)[^{]*\{([^}]*)\}/gm;
let match: RegExpExecArray | null;
while ((match = re.exec(content)) !== null) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] The regex /^(type|input|enum|interface|union)\s+(\w+)[^{]*\{([^}]*)\}/gm captures only single-line-to-first-} bodies. A GraphQL type whose first field opens a nested brace (e.g. a directive with arguments) will be captured incorrectly, and multi-line types spanning multiple } characters are truncated at the first closing brace.

💡 Why this matters in samples

For simple flat schemas this works fine. But the sample presents itself as a general-purpose extractor; readers who reuse it on a real schema with directives or union-extends will get silently wrong field lists. Adding a brief comment like // works for flat schemas without nested braces would set the correct expectation without changing the logic.

body: async ({ call, parallel }) => {
const [branchHealth, commitFrequency] = await parallel([
() => call(branchHealthAgent, "analyze"),
() => call(commitFrequencyAgent, "analyze"),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/grill-with-docs] call.json is used here to classify the overall health, but the inline comment says "Workflow role: ... classify overall repository health" without explaining to readers when to prefer call.json over spinning up a named agent(). Since this is teaching material, a one-line comment spelling out the trade-off (e.g. // call.json for lightweight one-shot LLM inference without a full agent definition) would make the sample significantly more instructive.

});

// Agent role: Check package-lock.json entries for completeness of resolved and integrity fields.
const lockfileIntegrityChecker = agent({

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/grill-with-docs] The instruction tells the agent to serialize each package entry as a JSON string (entryJson), then immediately parse it inside the tool handler. This round-trips through JSON serialization only to deserialize it — the agent could just pass a typed object, or the tool could accept s.unknown and cast it. The current approach also puts the serialization burden on the LLM, which may produce invalid JSON for complex entries and trigger unnecessary repair() cycles.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant