adr
Write an Architecture Decision Record (ADR) for a feature — Context / Decision / Status / Consequences / Alternatives, filed as…
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
$ npx -y skills add sd0xdev/sd0x-dev-flow --skill generate-runner --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/generate-runnerContext preview
The summary Claude sees to decide when to auto-load this skill.
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
name: generate-runner description: "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." allowed-tools: Read, Grep, Glob, Bash(node:*), Bash(chmod:*), Bash(bash:*), Write, AskUserQuestion
| 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) |
**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 `@generated_at`, `@plugin_version`, `@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.
flowchart LR
A[Detect Ecosystem] --> B[Select Template]
B --> C[Customize]
C --> D[Write Runner]
D --> E[Verify]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.
Load template from `references/templates.md` for the detected ecosystem.
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 |
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
Language: English | 繁體中文 | 简体中文 | 日本語 | 한국어 | Español The harness layer for Claude Code. Let the model choose the path. Keep "done" verifiable. Full control plane on Claude Code. Skills-only distribution for Codex CLI and other compatible agents.
Repo: sd0xdev/sd0x-dev-flow
Write an Architecture Decision Record (ADR) for a feature — Context / Decision / Status / Consequences / Alternatives, filed as…
Architecture design and documentation. Produces 3-architecture.md with component diagrams, data flow, integration points, and architecture decisions. Reads…
Context-aware Q&A with auto context gathering. Use when: user has a quick question about codebase, git history, rules, docs, or skills during development. Not…
Industry best practices conformance audit with mandatory adversarial debate. Produces audit artifact: verdict (OK/WARN/FAIL) + gap roadmap + debate proof. Use…
Bug fix workflow. Use when: fixing bugs, resolving issues, regression fixes. Not for: new features (use feature-dev), understanding code (use code-explore).…
Bump package and plugin version in sync. Updates package.json, .claude-plugin/plugin.json, and install-state manifest to the same version. Use when: user says…