IMPORTANT: These rules must NEVER be violated:
- Never modify global git config — Do not change global git user name, email, or any other global settings. This applies to both manual work and automated tests. Tests must use local config only.
- Never use this repository for testing — This project's own git repo must never be used as a test subject for worktree commands. Tests must always create isolated temporary repositories.
When manually testing git operations (e.g. cloning, worktree creation) in scratch directories:
- Never run
git config --global— not even temporarily. Usegit config --localor per-command env vars instead. - Set test identity via environment variables, not config:
GIT_AUTHOR_NAME="Test" GIT_AUTHOR_EMAIL="test@test.com" \ GIT_COMMITTER_NAME="Test" GIT_COMMITTER_EMAIL="test@test.com" \ git commit -m "test"
- Never commit to this repo's branches from scratch/temp directories — stray commits from test repos must not leak onto working branches.
- Always
cdback to the worktree directory after testing — a deleted temp directory as cwd will silently break subsequent shell commands. - Clean up temp directories when done:
rm -rf /tmp/daft-test-*or usemktemp -d.
mise run dev # Build + create symlinks (quick dev setup)
mise run test # Run all tests (unit + integration)
mise run test:unit # Rust unit tests only
mise run test:integration # Integration tests (bash + YAML, full matrix)
mise run test:manual -- --ci # YAML manual tests only (all scenarios)
mise run test:manual -- --ci checkout # YAML tests for one command
mise run test:manual -- checkout:basic # Interactive mode for one scenario
mise run clippy # Lint (must pass with zero warnings)
mise run fmt # Auto-format code
mise run fmt:check # Verify formatting
mise run ci # Simulate full CI locally
mise run bench:tests:integration # Benchmark bash vs YAML (TUI)IMPORTANT: Before committing, always run mise run fmt, mise run clippy, and
mise run test:unit. These checks are required and enforced in CI.
IMPORTANT: Every bug fix must include a regression test that reproduces the
issue. Add a YAML scenario in tests/manual/scenarios/ or a unit test that
fails without the fix and passes with it.
This project follows the XDG Base Directory Specification. Use the dirs crate
for cross-platform path resolution. Never hardcode ~/ paths for config or data
storage.
Multicall binary: All commands route through a single daft binary
(src/main.rs). The binary examines argv[0] to determine which command was
invoked, then dispatches to the matching module in src/commands/. Symlinks
like git-worktree-clone → daft enable Git subcommand discovery. Shortcut
aliases (e.g., gwtco) are resolved in src/shortcuts.rs before routing.
Shell integration: daft shell-init generates shell wrappers that create a
temp file and pass its path via DAFT_CD_FILE. When set, commands write the cd
target to that file, and the wrapper reads it after the command finishes to cd
into new worktrees. Stdout flows directly to the terminal.
Hooks system: Lifecycle hooks in .daft/hooks/ with trust-based security.
Hook types: post-clone, worktree-pre-create, worktree-post-create,
worktree-pre-remove, worktree-post-remove. Old names without worktree-
prefix are deprecated (removed in v2.0.0).
TUI navigation: All interactive TUI interfaces that support arrow key
navigation must also support Vim-style hjkl keys.
- Branch names:
daft-<issue number>/<shortened issue name> - PRs target
masterand are always squash merged (linear history required) - PR titles use conventional commit format:
feat: add dark mode toggle - Issue references go in PR body, not title:
Fixes #42
Every PR must be tagged with:
- Assignee: repository owner (
avihut) - Label: matches the conventional commit type (
feat,fix,docs,refactor,style,perf,test,chore,ci) - Milestone:
Public Launch(current active milestone)
Conventional Commits format:
<type>[scope]: <description>
Types: feat, fix, docs, style, refactor, perf, test, chore, ci
Uses release-plz: push to master → Release PR
auto-created → merge it → GitHub Release + tag → binary builds. All commits
produce patch bumps; edit Cargo.toml in the Release PR for minor/major bumps.
- Create
src/commands/<name>.rswith clapArgsstruct (includeabout,long_about,arg(help)attributes for man pages) - Add module to
src/commands/mod.rs - Add routing in
src/main.rs - Add to
COMMANDSarray andget_command_for_name()inxtask/src/main.rs - Add to help output in
src/commands/docs.rs(get_command_categories()) - Run
mise run man:genand commit the generated man page - Add YAML test scenarios in
tests/manual/scenarios/<name>/(seetests/README.mdfor schema reference) - Add bash integration tests in
tests/integration/following existing patterns
Shell completions live in src/commands/completions/. When adding, removing, or
renaming commands, verbs, or arguments, update all of these:
mod.rs—COMMANDS,VERB_ALIAS_GROUPS,get_command_for_name()bash.rs—DAFT_BASH_COMPLETIONS(verb alias cases, top-level subcommand list)zsh.rs—DAFT_ZSH_COMPLETIONS(verb alias cases, top-level subcommand list)fish.rs—DAFT_FISH_COMPLETIONS(subcommand registrations, branch completion triggers), verb alias flag commentfig.rs— Fig/Amazon Q spec generation
Flag completions for git-worktree-* commands are auto-generated from clap
Args structs, but the hardcoded string constants in bash/zsh/fish contain
verb names and subcommand lists that must be updated manually.
Pre-generated in man/ and committed. Regenerate after changing command help
text:
mise run man:gen # Generate/update man pages
mise run man:verify # Check if man pages are up-to-date (also runs in CI)Manual test plans live in test-plans/. Each file is a markdown checklist tied
to a branch via YAML frontmatter:
---
branch: feat/progressive-adoption
---
# Progressive Adoption
## Layout resolution
- [ ] Default layout is sibling when no config exists
- [ ] CLI --layout flag overrides config- File name: descriptive feature name, not the branch name
(
progressive-adoption.md, notfeat-progressive-adoption.md) branch:frontmatter: must match the full branch name — used by the sandboxtest-plancommand to auto-resolve the plan for the current worktree- Committed to the repo: serves as documentation of what was manually tested
- In the sandbox:
test-planopens the current branch's plan in treemd,test-plan <name>opens a specific plan by filename
docs/ contains the project documentation (VitePress/Markdown). Update when
adding or changing user-facing features.
docs/getting-started/— installation, quick start, shell integrationdocs/guide/— in-depth guides (hooks, configuration, workflow, shortcuts)docs/cli/— one reference page per command, followdocs/cli/daft-doctor.mdas template- Every page needs
titleanddescriptionYAML frontmatter - No emoji in docs
- Update
SKILL.mdwhen changes affect how an agent should interact with daft — new or removed commands, changed feature behavior, configuration format changes (e.g., hooks moving from shell scripts to YAML), renamed hook types, new template variables, etc. The skill is what teaches AI coding agents to use daft correctly.
The docs site at daft.avihu.dev is built from docs/ using VitePress + Biome,
with Bun as the package manager.
mise run docs:site # Dev server at localhost:5173
mise run docs:site:build # Build the site
mise run docs:site:preview # Preview built site
mise run docs:site:check # Lint config with Biome
mise run docs:site:format # Auto-fix config with Biome- Prettier (root):
*.{md,yml,yaml}files everywhere - Biome (docs):
docs/.vitepress/config.tsanddocs/.vitepress/theme/only - Auto-deploys to Cloudflare Pages on push to
masterwhendocs/**changes - Playwright screenshots: Save to
.playwright-mcp/directory (gitignored), not the project root. Use filename like.playwright-mcp/screenshot.png.