Contributing
Development Setup
git clone <repo-url>
cd claude-skill-tools
npm install
npm run buildThe build step runs the TypeScript compiler and copies asset directories (prompts/ and hooks/) into dist/. You need Node.js >= 18.
There is no linter configured. TypeScript strict mode is on and serves as the primary static analysis tool. The project is an ESM package ("type": "module" in package.json) with NodeNext module resolution, so all internal imports use .js extensions.
Running Tests
npm test # Single run (vitest run)
npm run test:watch # Watch mode (re-runs on file changes)
npm run test:coverage # Single run with v8 coverage reportTest tiers
Tests live in tests/ and are split into two tiers:
Tier 1 (tests/tier1/) — Pure function tests. No filesystem access, no mocking, no side effects. These are fast and deterministic. If you are adding a utility function or a parser, write a tier 1 test.
Tier 2 (tests/tier2/) — Filesystem tests. Each test creates a temporary directory with a .git/ directory, changes the working directory, and calls _resetRepoRootCache() in beforeEach to ensure path resolution picks up the temp dir. The original working directory is restored in afterAll. Use tier 2 when you need to test behavior that reads or writes files.
Running specific tests
# Single file
npx vitest run tests/tier1/slugify.test.ts
# All tests in a tier
npx vitest run tests/tier1
# By test name pattern
npx vitest run -t "slugifyContext"Test conventions
- Import paths use
.jsextensions (Vitest resolves them to.ts) - Intercept
process.exitwithvi.spyOn(process, "exit").mockImplementation(...) - Suppress console noise with
vi.spyOn(console, "log").mockImplementation(() => {}) - Shared fixtures are in
tests/helpers/fixtures.ts(createTempDir,removeTempDir,writeJson,writeFile)
Code Conventions
These conventions apply to all code in the repository:
Argument parsing
CLI arguments are parsed manually with switch/case blocks. There is no CLI framework (no yargs, no commander). Keep argument handling co-located with command dispatch.
Dependencies
There are zero runtime dependencies. Only typescript and @types/node exist as dev dependencies. If you need HTTP, use node:http. If you need to parse JSON, use JSON.parse. If you need to run a shell command, use node:child_process.
Do not add runtime dependencies without a compelling justification.
Imports
All internal imports use .js extensions per NodeNext module resolution:
import { resolveRepoRoot } from "../shared/paths.js";
import { die, banner } from "../shared/ui.js";State management
All persistent state is stored as JSON files on disk. Composer state goes in .claude/.skill-state/composer/, sandbox state in .claude/.skill-state/sandbox/. User-level durable data (session maps, config) goes in ~/claude-skill-tools/.
Never introduce a database, an ORM, or a key-value store.
Error handling
Use die() from shared/ui.ts for fatal errors. It prints a formatted error message with optional suggestion lines and calls process.exit(1):
die("Sandbox not found", [`Run 'sandbox create' first`, `Check 'sandbox list' for existing sandboxes`]);Shell commands
Use spawnSync for synchronous execution and spawn for background processes. Always check exit codes. Never use exec or execSync (they run through a shell and have buffer limits).
Types
No any types. TypeScript strict mode is enforced. Define explicit interfaces for state objects, configuration, and function parameters.
Target runtime
Node.js >= 18. Do not use APIs that require Node 20+ unless there is no alternative.
Adding a New Composition Type
Compositions are defined in src/composer/config/compositions.ts. A composition is a named sequence of steps that the composer executes in order.
1. Define the composition
Create a Composition object with:
- name: Display name for the composition
- description: One-line description shown in help text
- steps: Array of
Stepobjects
Each Step has:
- label: Human-readable step name (shown in the step loop UI)
- cmd: Template string with
{placeholder}variables resolved at runtime - type: A
StepTypevalue (sandbox-create,claude-interactive,ralph,sandbox-start,status-check,pr-dry-run,ado-pr-create) - autoAdvance (optional): If true, the step loop advances automatically without prompting
2. Register the composition
Add the composition to the COMPOSITIONS map, keyed by a short identifier:
COMPOSITIONS.set("my-workflow", {
name: "My Custom Workflow",
description: "Does something useful",
steps: [
{ label: "Create sandbox", cmd: "sandbox create {slug}", type: "sandbox-create" },
{ label: "Run developer", cmd: "sandbox start --role developer", type: "sandbox-start" },
// ... more steps
],
});3. Test it
Run the composition interactively to verify each step resolves and executes correctly:
composer compose my-workflow --slug test-runAdding a New Role Prompt
Role prompts are markdown files in the prompts/ directory. Each prompt defines a specialized agent role used by sandbox sessions and composer steps.
Structure
Follow the established pattern:
- Role description: What this agent is and what it does
- Process steps: Numbered sequence of actions the agent should take
- Output format: What files or artifacts the agent produces
- Rules and constraints: Hard boundaries on the agent’s behavior
Example
# Role: Tester
You are a testing specialist. Your job is to write comprehensive tests for the implementation in this sandbox.
## Process
1. Read the feature request and spec
2. Review the implementation code
3. Write tests covering happy path, edge cases, and error conditions
## Output
- Create test files in the appropriate tests/ directory
- Follow the existing test conventions in the repo
## Rules
- Do not modify implementation code
- Do not modify files outside the sandbox directory
- Write deterministic tests (no timing-dependent assertions)Test the prompt
sandbox start --role <name> --context "test"Verify that the agent follows the prompt’s rules and produces the expected output format.
Adding a CLI Command
Entry points
Each CLI tool has a thin entry point shim in src/bin/ that re-exports the main module:
src/bin/composer.tsre-exportssrc/composer/composer.tssrc/bin/sandbox.tsre-exportssrc/sandbox/sandbox.tssrc/bin/session-explorer.tsre-exportssrc/session-explorer/index.ts
Command dispatch
Commands are dispatched via switch/case in the tool’s main function. To add a new command:
-
Add usage text: Update the help/usage output in the main function to document the new command and its arguments.
-
Implement the handler: Write the command function in the appropriate commands file (e.g.,
src/composer/commands.tsfor composer commands, or directly in the sandbox/session-explorer main file for simpler commands). -
Wire up dispatch: Add a case to the switch/case block in the main function:
case "my-command":
await cmdMyCommand(args);
break;- Handle arguments: Parse command-specific arguments from the remaining
argsarray using manual parsing (indexOf, find, filter). Do not introduce an argument parsing library.
Pull Request Process
- Branch from main: Create a feature branch with a descriptive name.
- Keep commits focused: Each commit should represent a single logical change. Write clear commit messages that explain what changed and why.
- Run tests: Ensure
npm testpasses before opening the PR. - Build check: Ensure
npm run buildcompletes without errors. - Describe the change: In the PR description, explain what the change does, why it is needed, and how it was tested. If the change affects the CLI interface, include example invocations.
- Keep PRs reviewable: Prefer smaller, focused PRs over large omnibus changes. If a feature requires significant work, break it into incremental PRs.
Reporting Issues
When reporting a bug or unexpected behavior, include:
- Version: The output of
git rev-parse HEADor the installed version - Operating system: macOS, Linux, or Windows, and the version
- Node.js version: The output of
node --version - Steps to reproduce: The exact commands you ran, in order
- Expected behavior: What you expected to happen
- Actual behavior: What actually happened, including any error messages or stack traces
- Relevant state files: If applicable, the contents of the session or sandbox state JSON file (redact any sensitive paths or tokens)