The recommended setup is to fork this repo and customize it for your own workflows. Add ways for your domain, tweak the triggers, build your own Lumon handbooks. Your fork stays yours.
When you build something that would benefit everyone — a new domain, a better trigger pattern, a macro that detects something clever — we'd love a PR back to upstream. The framework improves when people bring different workflows to it.
As of 1.0, your dev checkout is not your install. ~/.claude is a projection of an XDG
application; the app source lives in $XDG_DATA_HOME/agent-ways. So you develop from a
separate clone (e.g. ~/src/agent-ways) and choose when your changes reach your install —
editing ~/.claude directly no longer "just works." See docs/development.md
for the full setup, the sandbox/reconcile/worktree testing patterns, and why the worktree must
hang off your standalone clone rather than $XDG_DATA.
- Create
hooks/ways/{domain}/{wayname}/{wayname}.mdwith YAML frontmatter - Define your trigger:
pattern:for regex,match: semanticfor fuzzy matching - Write compact, actionable guidance (every token costs context)
- Test it: trigger the pattern and verify the guidance appears once
See docs/hooks-and-ways/extending.md for the full guide.
Open an issue. Include which hook or way is involved, your OS/shell, and any error output.
- Keep changes focused — one way or one fix per PR
- Test your trigger patterns against both positive and negative cases
- If adding a new domain, include a brief rationale in the PR description
Hooks and macros are bash. Keep them portable (macOS bash 3.2 compatible — no declare -A, no mapfile, no grep -P), use shellcheck if available, and keep scripts under 200 lines where possible. Hook scripts should be thin dispatchers to the ways binary.
The ways binary is Rust (tools/ways-cli/). Run cargo test before submitting changes. See ADR-111 for the consolidation rationale.
The Rust toolchain is deliberately unpinned. This is an internal tool with no MSRV commitment, and new clippy lints from upstream Rust releases are treated as free code-quality upgrades — .clamp() is strictly better than .max().min(), and so on.
To make that drift visible instead of silent:
- Per-PR gate:
.github/workflows/clippy.ymlrunscargo clippy --workspace --all-targets -- -D warningson every PR touchingtools/**. A lint triggering means the PR is blocked until the warning is fixed. - Weekly drift canary: the same workflow runs on a schedule against
mainwith the latest stable toolchain. On failure it opens (or comments on) aclippy drift:issue so new lints become small, targeted follow-up PRs instead of accumulating silently.
Fix drift in small batches. If a lint is genuinely wrong for a specific case, #[allow(clippy::…)] with a one-line reason is fine.
The .gitignore uses an exclusive pattern: * (ignore everything) with explicit ! exceptions for tracked files. This is intentional, not lazy.
This repo controls ~/.claude/ — directly when installed in-place, or via the projection after 1.0 — the directory that governs how Claude Code thinks and acts. Every file here can influence agent behavior: hooks execute shell commands, ways inject guidance, CLAUDE.md steers reasoning, settings.json controls permissions. An accidental commit of a malicious or poorly-written file could steer Claude to do undesirable things for anyone who pulls it.
The exclusive gitignore ensures:
- No accidental file inclusion. New files must be explicitly opted in via
.gitignore. You can't push a file you didn't mean to track. - Clear audit surface.
git diff .gitignoreshows exactly what's tracked. Reviewers can see the full inclusion list in one place. - Defense against ignorance and malice. Both well-meaning contributors who don't realize their file will affect Claude's behavior, and adversarial PRs that try to slip in steering content.
When adding a new tracked file, add a !filename or !path/ exception to .gitignore and explain why it needs to be tracked in your PR description.