skills/scripts/SKILL.md
Conventions for repository scripts (naming, execution, linting, testing).
npx skillsauth add bkinsey808/songshare-effect scriptsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Requires: file-read, terminal (Bun). No network access needed.
Use this skill when:
scripts/.Execution workflow:
.bun.ts entrypoints thin and move logic into pure modules.npm run lint when script code changes are substantial.Output requirements:
This skill documents the conventions and expectations for repository scripts. Follow these rules when adding or changing scripts so they remain discoverable, testable, and consistent with the project's lint/type conventions.
scripts/:
scripts/my-script.bun.tsscripts/my-script/my-script.bun.tskebab-case.bun.ts (e.g., check-skill-line-count.bun.ts) — this signals the file is a Bun-executable script.collectSkillFiles.ts, countLines.ts)constants.ts)scripts/
check-skill-line-count/
check-skill-line-count.bun.ts ← entry point (shebang, thin shell only)
checkSkillFiles.ts ← pure exported function with logic; unit-testable
collectSkillFiles.ts ← export default collectSkillFiles(...)
countLines.ts ← export default countLines(...)
checkSkillFiles.test.ts ← unit tests for the pure logic module
collectSkillFiles.test.ts ← unit tests colocated with source
countLines.test.ts
Keep .bun.ts entry points as thin shells: they resolve repoRoot via import.meta.dir, call a pure exported function, and handle side-effects (process.exit, stderr.write, stdout.write). All non-trivial logic lives in its own module so it can be unit-tested under Node/Vitest without spawning Bun.
// check-skill-line-count.bun.ts ← thin shell
import path from "node:path";
import { checkSkillFiles } from "./checkSkillFiles";
async function main(): Promise<void> {
const repoRoot = path.resolve(import.meta.dir, "../..");
const result = await checkSkillFiles(repoRoot);
if (result.hasError) {
for (const msg of result.errors) {
process.stderr.write(`${msg}\n`);
}
process.exit(1);
}
process.stdout.write(`✓ Checked ${result.checkedCount} file(s).\n`);
}
await main();
The pure module returns a typed result object rather than calling process.exit:
// checkSkillFiles.ts ← pure, testable under Node
export type CheckResult = { hasError: boolean; checkedCount: number; errors: string[] };
export type CheckOptions = { maxLines?: number; searchDirs?: string[]; /* ... */ };
export async function checkSkillFiles(repoRoot: string, opts: CheckOptions = {}): Promise<CheckResult> { ... }
Why options object? ESLint's
max-paramsrule (default max 3) will trigger if you pass many positional helper args. Group injectable dependencies into a singleoptsparameter.
.bun.ts entry point executed with bun (or npx bun in CI). Avoid multi-step Bash scripts; use shell only for tiny wrappers that delegate immediately to Bun.#!/usr/bin/env bun
import { readFileSync } from "node:fs";
npx bun so CI and local environments behave consistently; do not rely on a global bun binary. Example:
npx bun ./scripts/my-script/my-script.bun.tspackage.json entry to simplify invocation:
"scripts": {
"check:skill-line-count": "bun run ./scripts/check-skill-line-count/check-skill-line-count.bun.ts"
}
tsconfig is strict. Avoid any. Use unknown and explicit narrowing where needed.undefined and type guards.continue statements in loops; prefer structured control flow..ts files). Use @returns where applicable.constants.ts).npx oxfmt . (or npm run format)npx oxlint --config .oxlintrc.json --type-aware . (or npm run lint).test.ts to the source file name:
scripts/check-skill-line-count/countLines.tsscripts/check-skill-line-count/countLines.test.tscheckSkillFiles.ts) over the entry point (.bun.ts). The entry point is a thin shell and does not need its own spec; the logic module does.bun in unit tests. Tests that call spawnSync("bun", ...) will fail in environments where Bun is not installed. Extract logic into a pure Node-importable module instead.test.skip cleanly in restricted environments.Before submitting a PR run:
npx oxfmt --check . or npm run format:checknpx oxlint --config .oxlintrc.json --type-aware . or npm run lintnpm run lintnpm run test:unit -- <path-to-test> (tests related to your changes)kebab-case.bun.ts entry points are immediately recognisable as Bun scripts.npx bun ensures CI portability without requiring global Bun installs — but npx bun may also be unavailable in locked-down CI; having a pure Node-testable module is the reliable fallback.docs/ai/rules.md.file-splitting.unit-test-best-practices.tools
Zustand state management patterns for this project — store creation, selectors, Immer middleware, async actions with loading states, devtools, persist, and testing. Use when authoring or editing Zustand stores (use*Store files) or components that subscribe to stores. Do NOT use for React component structure or TypeScript-only utilities.
testing
How to write, update, or split skill files in this repo. Use when creating a new SKILL.md, updating an existing one, or deciding whether to put content in a skill vs. docs/.
development
Complete guide for testing React hooks — renderHook, Documentation by Harness, installStore, fixtures, subscription patterns, lint/compiler traps, and pre-completion checklist. Read docs/testing/unit-test-hook-best-practices.md for the full reference.
development
Vitest unit test authoring for this repo — setup, mocking, API handler testing, and common pitfalls for non-hook code. Use when the user asks to add, update, fix, or review unit tests for utilities, components, API handlers, or scripts. Do NOT use for React hook tests — load unit-test-hook-best-practices instead.