AI-first CLI for managing markdown-based issues — no database, no server, just files in your repo.
issuectl tracks issues, tasks, features, and epics as plain markdown
files with YAML frontmatter, stored under
issues/open/<slug>/item.md. Each issue gets a random
intensifier-adjective-noun slug (e.g. extremely-quiet-otter) so that
work in parallel branches and worktrees never collides.
The CLI is designed to be driven by AI agents (e.g. via the /issue
Claude Code skill) — strict input
validation, structured JSON output, no interactive prompts — but humans
can use it from a terminal too.
- Zero infrastructure. Issues live in your repo. Diff them, branch them, blame them, review them in PRs.
- AI-friendly. Every command speaks
--json, validates inputs strictly, and returns meaningful exit codes. Designed to be a tool for agents rather than a UI for humans. - Markdown-first. Issues are just files. Edit them in your editor,
attach screenshots and analysis docs, search them with
grep. - Worktree-friendly context handoff. In worktree-based agent flows,
an issue body doubles as a durable, self-contained prompt: one agent
investigates and writes up
## Reproduction/## Analysis/## Scope, then a follow-up agent in a fresh worktree reads the issue and implements directly from it. Frontmatter carries the routing (assignee, status, epic, related); the body is the work order. No external task tracker to sync, no chat history to reconstruct — the file inissues/is the context. - Round-trip safe. Frontmatter mutations preserve field order and unknown keys. Body text is left verbatim.
- Collision-free by construction. Random
intensifier-adjective-nounslugs (~100M combinations) replace sequential numbering. Two branches creating issues independently can be merged in any order without renaming.
list/show/search/stats— browse with filters and JSON outputnew/update/close— create, mutate, and resolve issues with strict validationdoctor— health-check the repo (slug sanity, duplicates, orphans)skill install/skill print— install or preview the/issueskill template for Claude Code or Codex CLI (or both)serve— run a local Trello-style web board (read-only)fmt— normalizeitem.mdfiles (canonical key order, sorted arrays, ATX headings) so reviews focus on real changesmerge-driver— opt-in git custom merge driver that union-mergeslabels/related/blocked_by/commitsand picks the newerupdated:instead of conflicting on every cross-branch edit--root <PATH>— operate on an external repo from any working directory
Pick whichever channel suits your platform. After installing, verify with:
issuectl --versionbrew install jarimustonen/issuectl/issuectlThe first run automatically taps jarimustonen/homebrew-issuectl. To
upgrade later: brew upgrade issuectl.
cargo install issuectlDownloads the prebuilt binary for your OS/arch and drops it in
~/.cargo/bin (or equivalent):
curl --proto '=https' --tlsv1.2 -LsSf \
https://github.com/jarimustonen/issuectl/releases/latest/download/issuectl-installer.sh | shOr grab a tarball manually from the releases page — binaries are signed-checksummed and available for macOS (Intel + Apple Silicon) and Linux x86_64.
git clone https://github.com/jarimustonen/issuectl
cd issuectl
cargo install --path .After installing, set up your repo and create your first issue:
cd path/to/your/repo
# Install the /issue skill so Claude Code or Codex CLI can drive issuectl.
# This also creates issues/AGENTS.md and issues/{open,closed}/ if they
# don't exist yet.
issuectl skill install --agent all
# Create your first issue (random slug auto-generated):
issuectl new --type bug --title "Login loops on Safari" \
--reporter alice --assignee bob --priority high
# → Created extremely-quiet-otter: Login loops on Safari
# /your/repo/issues/open/extremely-quiet-otter/item.md
# Browse:
issuectl list
issuectl show extremely-quiet-otter
# Move it through the workflow:
issuectl update extremely-quiet-otter --status in-progress
issuectl update extremely-quiet-otter --add-commit "abc1234:fix redirect state init"
issuectl close extremely-quiet-otter # status → fixed (default for bugs)JSON output for any command (for scripting and AI agents):
issuectl --json list -t bug --status open
issuectl --json show extremely-quiet-otterissuectl list List open issues (default)
issuectl ls -a alice Filter by assignee
issuectl ls -t bug -p high Combine filters
issuectl ls --all Include closed issues
issuectl ls --closed --json Closed issues, machine-readable
issuectl show <slug> Show single issue details
issuectl search redirect [--all] Keyword search in title/slug/body
issuectl stats [--json] Summary statistics
Filter flags: -a/--assignee, -t/--type, -p/--priority,
-s/--status, -e/--epic (slug), -l/--label, --all, --closed.
issuectl new --type bug --title "Login loops" \
--reporter alice --assignee bob
# Random slug auto-generated; pass --slug <kebab> to override.
issuectl new --type epic --title "API v2 migration" \
--owner cara --priority high
issuectl update <slug> --status in-progress
issuectl update <slug> --add-commit "abc123:fix login state" --add-label frontend
issuectl update <slug> --add-related "@other-slug" --epic api-v2-migration
issuectl update <slug> --no-epic --remove-label stale
issuectl close <slug> Defaults: `fixed` for bugs, `done` otherwise
issuectl close <slug> --status wontfix --commit "abc123:design decision"
Cross-references in body markdown use @<slug> (e.g. @extremely-quiet-otter).
The epic: and related: frontmatter fields store bare slugs / @<slug>.
Strict validation: invalid --type, --priority, or --status values
are rejected with the list of valid options. Closing statuses (done,
fixed, wontfix, duplicate, cannot-reproduce, obsolete)
automatically move the directory from open/ to closed/ and stamp
closed: with today's date. Setting a non-closing status on a closed
issue moves it back to open/ and clears closed:.
issuectl doctor Read-only health-check report
issuectl --json doctor Machine-readable report
issuectl skill install Install /issue skill (default: Claude Code)
issuectl skill install --agent codex Install Codex prompt instead
issuectl skill install --agent all Install both
issuectl skill print [--agent codex] Preview the template without installing
doctor performs the following checks:
- Slug sanity. Flags slugs that don't pass
is_valid()(lowercase ASCII kebab-case, at least two segments, letters/digits only). - Duplicates. Flags any slug used twice across
open/+closed/. - Missing item.md. Flags directories without an
item.md. - Orphan epic refs. Flags
epic:values that don't resolve to an existing slug.
issuectl serve Start the local board on http://127.0.0.1:7878
issuectl serve --port 9000 Pick a different port
issuectl serve --host 0.0.0.0 Bind to all interfaces (LAN access)
serve runs a small read-only web server that renders issues/ as a
Trello-style Kanban board (Open / In progress / Testing / Closed columns,
plus an "Other" catchall for unrecognised statuses). Filter by type,
assignee, epic, or label and search across slug and title; filter state
persists in the URL, so reloads and bookmarks work. Click any card to
open a modal with the rendered markdown body, frontmatter, and any
additional *.md files alongside item.md. The server re-reads the
filesystem on every request, so editing an item.md and refreshing the
browser shows the change without restarting.
Bind defaults to 127.0.0.1 (local-only). --host 0.0.0.0 exposes the
board to your network: there is no authentication and no TLS, so use
it only on trusted networks — serve prints a stderr warning when bound
to a non-loopback interface as a reminder. Edits via the browser will
land in a follow-up release.
issuectl --root ~/code/some-other-project list
issuectl --root /path/to/another/repo statsIssues are markdown files with YAML frontmatter:
---
created: 2026-05-02
updated: 2026-05-02
type: bug
reporter: alice
assignee: bob
status: open
priority: normal
epic: api-v2-migration
related: ["@notably-brave-otter", "@simply-fierce-comet"]
labels: [frontend, auth]
commits:
- hash: abc1234
summary: "fix(auth): redirect after SSO"
---
# Issue title
_Source: which service / page / feature_
## Description
...issuectl fmt # rewrite every issues/<slug>/item.md
issuectl fmt some-slug # specific slug(s)
issuectl fmt --check # exit non-zero if anything would change (CI)
issuectl fmt --diff # print a unified diff, no writes
issuectl --json fmt --check # per-file JSON resultsfmt is idempotent — issuectl fmt && issuectl fmt --check always
exits 0. Normalises:
- frontmatter key order:
created,updated,closed,type,status,priority,reporter,assignee,owner,epic,blocked_by,related,labels,commits, then any unknown keys alphabetically; - arrays (
labels/related/blocked_by) sorted;commitskeeps its order (it's chronological); - markdown setext headings (
====) rewritten to ATX (#); - one blank line between
---close and the body, no trailing whitespace, single final newline.
issuectl merge-driver is a custom three-way merge driver for
issues/**/*.md. It union-merges labels / related / blocked_by,
keeps commits as a hash-keyed log, and picks the newer updated: —
mitigating the most common cross-branch conflict mode for file-based
issue trackers. Scalar fields that diverge on both sides still produce
a conflict (the driver never silently picks a side).
To enable it for a repo:
# Add to .gitattributes (commit this):
echo 'issues/**/item.md merge=issuectl-yaml' >> .gitattributes
# Configure the driver locally (per-clone, not committed):
git config merge.issuectl-yaml.driver \
"issuectl merge-driver --base %O --ours %A --theirs %B --output %A"Or print and apply via:
issuectl install-merge-driver # print the snippets
issuectl install-merge-driver --apply # also run `git config` for youinstall-merge-driver never modifies .gitattributes — that file is
shared, so its contents are your decision.
See issues/AGENTS.md for the full schema reference, status workflow, and conventions.
issuectl skill install writes a /issue skill template into a target
repo so an AI agent can drive issue management through issuectl
rather than poking at the filesystem directly. Two formats are
supported:
| Agent | Destination | Format |
|---|---|---|
| Claude Code | .claude/skills/issue/SKILL.md |
YAML frontmatter + markdown body |
| Codex CLI | .codex/prompts/issue.md |
Plain markdown prompt |
issuectl skill install # Claude Code skill (default)
issuectl skill install --agent codex # Codex prompt
issuectl skill install --agent all # both
issuectl skill print # preview Claude template to stdout
issuectl skill print --agent codex # preview Codex templateThe skill instructs the agent to delegate Search/List/Show/Create/Update/Close
to issuectl, but leaves body markdown editing (## Reproduction,
epic ## Issues/## Phases sections, screenshot attachments) to the
agent since those are out of scope for the CLI.
Source templates live at
templates/issue-skill.md (Claude) and
templates/issue-prompt.md (Codex) if you
want to customize before installing.
| Flag | Scope | Description |
|---|---|---|
--root |
global | Override repo root (the dir containing issues/) |
--json |
global | Emit JSON to stdout instead of human-readable tables |
Without --root, issuectl walks up from cwd looking for issues/ or
.git.
Requires a Rust toolchain (2021 edition or newer).
cargo build
cargo test
cargo clippy --all-targetsSee AGENTS-AI-FIRST-CLI.md for the design principles every command follows.
Contributions are welcome. See CONTRIBUTING.md for the PR process, dev setup, and coding conventions.
To report a security vulnerability, please follow the process in SECURITY.md.
MIT — see LICENSE.