sd0xdev/sd0x-harness

generate-runner

Generate a customized precommit runner for any ecosystem.

First seen Mar 27, 2026

Installation

$ npx skills add sd0xdev/sd0x-harness --skill generate-runner

Summary

  • Generate a customized precommit runner for any ecosystem.
  • Use when: non-Node projects need a runner, eject from generic runner, customize runner for project.
  • Not for: running precommit (use /precommit), installing runner (handled by auto-install).
  • Output: customized runner script with eject header.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from sd0xdev/sd0x-harness · top by installs.

npx skills add sd0xdev/sd0x-harness

Browse all from sd0xdev/sd0x-harness

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 188
License LICENSE
Default branch main
Open issues 3
Status Active

Skill metadata

Parsed from SKILL.md frontmatter.

Allowed toolsRead, Grep, Glob, Bash(node:*), Bash(chmod:*), Bash(bash:*), Write, AskUserQuestion
Declared agents claude-code

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 8,296 B
  • docs SUMMARY.md 322 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 17 installs

SKILL.md

Generate Runner

Trigger

  • Keywords: generate runner, create runner, custom runner, eject runner, runner for python, runner for rust, runner for go

When NOT to Use

Need Use Instead
Run precommit checks /precommit or /precommit-fast
Install existing runner /project-setup (auto-installs)
Configure the shipped runner's lint globs Check first that the runtime runner has no @generated_at header, then edit .claude/runner-config.json — and set lintArgMode for the same script role, or the globs stay inert (see § Lint Argument Injection)

Lint Argument Injection

Scope: the runners this plugin ships — scripts/precommit-runner.js and scripts/verify-runner.js. What /precommit, /precommit-fast and /verify actually execute is the runtime copy at .claude/scripts/*, installed by /install-scripts. A runner generated by this skill is written to the same path but is user-owned: its template does not append lint arguments, so lintGlobs / lintArgMode have no effect on it. The discriminator is the eject header (§ Step 4) — all three of @generatedat, @pluginversion, @template, each on its own line with a value, in the opening comment block. Read the file before configuring it.

Known limitation. A project that installed a runner before this fix keeps running the old version, which passes ESLint-only flags into whatever lint script the repo declares — a file-rewriting non-ESLint linter reads them as paths and edits sources. Fixing the plugin source does not reach that copy, and detecting it reliably needs per-release provenance, which is deliberately out of scope. Remedy: /install-scripts precommit-runner.js verify-runner.js --force, with --dry-run first to read the write set (the installer copies "scripts + dependencies" without defining the closure, so the dry run is the authoritative list). The runners are named rather than swept in by --all, which would force-overwrite every core script; confirm lib/utils.js — where the fixed runners decide whether to inject — appears in the report.

The shipped runner appends nothing to your lint script unless you ask it to. lintGlobs alone does nothing; it needs lintArgMode for the same script role.

Setting Where Shape
lintArgMode .claude/runner-config.json, or package.json → sd0x {"lint": "eslint"} / {"lint:fix": "none"} — keyed by script role, never a bare string. Accepted values: "eslint" and "none"
lintGlobs same two places ["src/**/*.{ts,tsx,js,jsx}", …] — applied only when that role's lintArgMode is "eslint"

Precedence, per role: .claude/runner-config.json first, first valid value wins; package.json → sd0x is the fallback. An unusable value — a bare string, a value outside eslint/none, a non-role-keyed shape — is warned about and falls through rather than latching, so "none" in .claude can suppress an opt-in package.json declares. (An unreadable .claude/runner-config.json warns via loadLintGlobs(); an unreadable package.json stays silent.)

In package.json, to inject ESLint's flags and globs into lint:fix only:

{ "sd0x": { "lintArgMode": { "lint:fix": "eslint" }, "lintGlobs": ["src/**/*.{ts,js}"] } }

precommit reads the lint:fix role; verify reads lint — keyed separately because a repo routinely runs different engines for each (this one runs markdownlint for lint:fix).

Why it is opt-in. The runner used to inject by default and guess from the script text whether the recipient was ESLint. markdownlint-cli2 treats every unrecognised argument as a file glob, so under --fix it rewrote JavaScript as Markdown; one run corrupted 71 files in this repo. Four successive detection grammars each misclassified something, so detection was removed rather than deepened.

Workflow

flowchart LR
    A[Detect Ecosystem] --> B[Select Template]
    B --> C[Customize]
    C --> D[Write Runner]
    D --> E[Verify]

Step 1: Detect Ecosystem

Scan project root for manifest files:

Manifest Ecosystem Template ID
pnpm-lock.yaml Node.js (pnpm) node-pnpm
yarn.lock Node.js (yarn) node-yarn
package-lock.json or package.json Node.js (npm) node-npm
pyproject.toml Python python
Cargo.toml Rust rust
go.mod Go go

If multiple detected, prefer Node.js > Python > Rust > Go. If none detected, ask user.

Step 2: Select Template

Load template from references/templates.md for the detected ecosystem.

Step 3: Customize

Read project-specific configuration:

Source What
package.json scripts Lint command, test command, build command
.claude/runner-config.json Not read for lint configuration. Neither lintGlobs nor lintArgMode reaches a generated runner — the template does not append lint arguments at all (§ Lint Argument Injection), so listing either here would present an inert value as a customization input
Lock file Package manager selection

Step 4: Write Runner

Write to .claude/scripts/precommit-runner.js (Node) or .claude/scripts/precommit-runner.sh (non-Node).

A non-Node runner is generated for manual invocation, not for /precommit. /precommit,
/precommit-fast and /verify probe the .js path only and execute it on existence; none of
them detects a .sh runner today. So a Python, Rust or Go runner generated here is a real,
working script that those commands will not run — invoke it directly, or keep the ecosystem's
checks in the shipped runner, which orchestrates pytest/cargo/go as first-class steps
(skills/precommit/SKILL.md § Step 2). Teaching the three commands to detect and order a .sh
runner is a change to runner dispatch, not to this skill, and is not implied by generating one.

Include eject header:

@generated_at <ISO 8601>
@plugin_version <current version>
@template <template-id>
@ecosystem <ecosystem>

Conflict handling: If target file exists, AskUserQuestion with diff preview.

Step 5: Verify

  • File written successfully
  • Script is executable (non-Node: chmod +x)
  • Syntax check, named so it can actually be run: node --check <path> for the Node runner, bash -n <path> for a shell one

Arguments

Argument Description Default
--ecosystem <name> Force ecosystem (skip detection) auto-detect
--output <path> Custom output path Ecosystem-dependent, per § Step 4: .claude/scripts/precommit-runner.js for Node, .claude/scripts/precommit-runner.sh otherwise
--force Overwrite existing without asking off

Output

## Generated Runner

- Ecosystem: <detected>
- Template: <template-id>
- Output: <path>
- Package manager: <pm>

The generated runner is **user-owned** — plugin updates will not overwrite it.
Edit freely to customize for your project.

Verification

  • Ecosystem correctly detected
  • Template matches ecosystem
  • Eject header present with correct metadata
  • Runner script is valid (no syntax errors)
  • Existing file conflict handled (ask or --force)

References

  • Per-ecosystem templates: references/templates.md

Examples

Input: /generate-runner
Action: Detect Node.js (yarn) → load node-yarn template → customize → write .claude/scripts/precommit-runner.js

Input: /generate-runner --ecosystem python
Action: Load python template → customize → write .claude/scripts/precommit-runner.sh

Input: /generate-runner --force
Action: Detect ecosystem → overwrite existing runner without asking