My dad was a cabinet maker. His perpetually sawdusted workshop was dotted with contrivances that I sometimes mistook for junk (some stories there!) that actually were thoughtful, if scrappy, efforts to make some task simpler or safer.
Jig is a multi-tool CLI that bundles repo monitoring, a file-based issue tracker, a Claude Code security guard, and Homebrew/Scoop/Zed companion repo scaffolding. The issue tracker is derived from hmans/beans, itself inspired by steveyegge/beads.
jigtui: alias fortodo tuisync: alias fortodo syncdoctor: run all doctor checks (brew, scoop, zed, nope)prime: output agent instructions for issue trackingversion: print version infotodo: file-based issue tracker for AI-first workflowstui: interactive terminal UI that displaystodoissuesinit: create.issues/directory and configcreate: create a new issuelist: list issues with filtersshow: display issue detailsupdate: modify an issuedelete: remove an issuearchive: archive completed/scrapped issuesroadmap: render issue treequery: run GraphQL queries and mutationsdoctor: validate issue links and referencessync: sync issues to external trackersrefry: migrate from beans format
cite: monitor cited repositories for changesinit: add starter citations section to.jig.yamladd: add the given URL to citationsreview: fetch and display changes, updatelast_checked_sha
nope: Claude CodePreToolUseguard (reads JSON from stdin, exits 0 or 2)init: scaffold nope rules in.jig.yamland hook in.claude/settings.jsondoctor: validate nope configurationhelp: show nope guard reference
changelog: gather recent issues and commits for changelog generationcommit: stage changes, check for gitignore candidates, signal push intentbrew: Homebrew tap managementinit: create tap repo, push initial formula, injectupdate-homebrewCI jobdoctor: verify brew tap setup is healthy
scoop: Scoop bucket managementinit: create bucket repo, push initial manifest, injectupdate-scoopCI jobdoctor: verify scoop bucket setup is healthy
zed: Zed extension managementinit: create extension repo, push scaffold, injectsync-extensionCI jobdoctor: verify Zed extension setup is healthy
brew install toba/tap/jigOn Windows:
scoop bucket add toba https://github.com/toba/scoop-jig
scoop install jigOr build from source:
go install github.com/toba/jig@latestA git-diffable issue tracker that lives in your project. Issues are markdown files with YAML frontmatter stored in .issues/. Unlike similar tools, jig can sync bidirectionally with external trackers, and it's designed to be driven by LLM agents.
jig todo init # create .issues/ and config
jig todo create "Fix login bug" -t bug -s ready
jig todo list # list all issues
jig todo show abc-def # view an issue
jig todo tui # interactive terminal UI
jig todo sync # sync to ClickUp or GitHub IssuesBeans things and ...
- External sync: bidirectional sync with ClickUp and GitHub Issues (
jig todo sync) - Due dates: date field with sort support
- TUI improvements
- Status icons instead of text labels
- Sort picker (
okey) - Substring search instead of fuzzy match
- Tap
/twice to search descriptions too - Due date indicators
| Type | Purpose |
|---|---|
milestone |
A target release or checkpoint |
epic |
A thematic container for related work |
feature |
A user-facing capability or enhancement |
bug |
Something that is broken and needs fixing |
task |
A concrete piece of work (chore, sub-task) |
The most basic way to teach your agent about jig's issue tracker is to add the following to your AGENTS.md, CLAUDE.md, or equivalent:
**IMPORTANT**: before you do anything else, run the `jig prime` command and heed its output.
The prime output is designed to be token-efficient — about 680 words — so it doesn't eat your context window every time a session starts or compacts.
Add the following hooks to your project's .claude/settings.json:
{
"hooks": {
"SessionStart": [
{ "hooks": [{ "type": "command", "command": "jig prime" }] }
],
"PreCompact": [
{ "hooks": [{ "type": "command", "command": "jig prime" }] }
]
}
}The real power of jig's issue tracker comes from letting your coding agent manage tasks. With the hooks above, you can use natural language:
Are there any tasks we should be tracking for this project? If so, please create issues for them.
What should we work on next?
Please inspect this project's issues and reorganize them into epics and milestones.
jig syncs issues bidirectionally with ClickUp and GitHub Issues. Configure the integration in .jig.yaml under todo.sync, then run:
jig todo sync # Sync all issues
jig todo sync abc-def xyz-123 # Sync specific issues
jig todo sync --dry-run # Preview changes without applying
jig todo sync --force # Force update even if unchangedPer-issue sync state is stored in frontmatter:
---
title: Fix login bug
status: ready
sync:
clickup:
task_id: "868h4hd05"
synced_at: "2026-01-18T00:07:02Z"
github:
issue_number: "42"
synced_at: "2026-01-18T00:07:02Z"
---Requires CLICKUP_TOKEN environment variable. Syncs statuses, priorities, types, and blocking relationships as ClickUp task dependencies.
todo:
sync:
clickup:
list_id: "123456789"
assignee: 42
status_mapping:
draft: "backlog"
ready: "to do"
in-progress: "in progress"
completed: "complete"
scrapped: "closed"
priority_mapping:
critical: 1
high: 2
normal: 3
low: 4Requires GITHUB_TOKEN environment variable (or gh CLI auth). Maps statuses, priorities, and types to GitHub labels (e.g., status:in-progress, priority:high, type:bug). Blocking relationships are rendered as text in the issue body.
todo:
sync:
github:
repo: "owner/repo"This arose as a new pattern (to me) while working with agents. The agent makes it easy to fork a repo and make a bunch of updates. Great. But it was quickly obvious that these changes didn't constitute a proper contribution back to the source. There were too many changes, too specific to my use-case. I also began combining sources, further impeding formal contribution.
The cite subcommand addresses a couple things. It will help check your license for proper attribution even when there's not a formal dependency or fork in place. And it can be run to notify you of changes within those cited sources that might be important to factor into your own project.
jig cite init
jig cite add
jig cite review
jig cite doctorConfigure cited sources in .jig.yaml:
citations:
- repo: owner/repo
branch: main
paths:
high:
- "src/**/*.go"
medium:
- "go.mod"
low:
- "README.md"Files are classified as high, medium, or low relevance based on glob patterns. ** works — we use doublestar because Go's path.Match stubbornly refuses to support it.
When the agent wears you down with incessant prompts you've tried fruitlessly to always-allow, and you decide to go YOLO (dangerously-skip-permissions), a little nope remains prudent to prevent personal apocalypse.
This command applies Claude's PreToolUse guard so that even when allowed to run wild, you can say "nope" if it tries to erase your thesis, send bomb threats or wire funds to your many enemies.
jig nope initThis adds a nope: section to .jig.yaml with starter rules and wires up the hook in .claude/settings.json. Claude Code pipes a JSON payload to jig nope on stdin before each tool call. If a rule matches, the tool is blocked (exit 2). If nothing matches, it's allowed (exit 0).
The nope: section contains a rules list and an optional debug log path:
nope:
debug: .claude/nope.log # optional JSONL debug log (omit to disable)
rules:
# Regex pattern — matched against the tool_input JSON
- name: git-push
pattern: 'git\s+push'
message: "git push not allowed — only user should push"
# Built-in check — structural analysis, not just string matching
- name: pipe-commands
builtin: pipe
message: "piped commands not allowed — run commands separately"
# Scope rules to specific tools (default is Bash only)
- name: no-write-env
pattern: '"file_path"\s*:\s*"[^"]*\.env"'
tools: ["Write", "Edit"]
message: "writing to .env files not allowed"Rules are either regex patterns or built-in checks. Each rule has a name, a message, and either a pattern (regex) or builtin (structural check). The optional tools array scopes which tools the rule applies to (defaults to ["Bash"]; use ["*"] for all tools).
| Name | What it catches |
|---|---|
pipe |
Pipe operators outside quotes |
chained |
&&, ||, ; outside quotes |
redirect |
>, >> outside quotes |
subshell |
$(), backticks outside single quotes |
credential-read |
Reading .env, .pem, .key, SSH keys, etc. |
network |
curl, wget, ssh, etc. in command position |
Built-ins use proper shell tokenization — they understand quoting, so grep "foo|bar" won't trigger the pipe check. Regex patterns get (?s) prepended automatically so . matches newlines.
I am all for having the agent write nice commit messages but my eyes bleed a little very time I see tokens ticking away while it runs the same wrong commands three times before getting it right.
This command uses other configuration in .jig.yaml to determine whether there are issues to synchronize or a companion brew, scoop, or Zed extension repo to consider so the agent isn't always evaluating such things.
Instead, the agent is given the list of changes to summarize, along with the last tag, if any, and asked to respond with a description and likely next tag (version).
jig commitAgents are great at writing changelogs but terrible at gathering the raw material — they'll spend forty turns poking around git history and issue files before producing anything useful. This command collects recent issues (created, updated, completed) and optionally git commits into a single structured dump the agent can actually work with.
jig changelog --json # last 7 days of issues
jig changelog --json --days 30 # last 30 days
jig changelog --json --days 14 --git # with git commits
jig changelog --json --since 2026-01-01 # explicit start date
jig changelog --commits 50 --json # time range from last 50 commitsIssues are bucketed into created, updated, and completed — an issue lands in exactly one bucket based on priority: completed > created > updated. The --git flag adds a commits array with hash, subject, and date. Without --json you get a plain text summary that's still agent-friendly but won't offend human eyes.
I just got tired of re-figuring-out how to set up the companion repository for homebrew releases. At first I used an agent skill, which helped but I ended up with three different approaches for three repositories.
jig brew init --tap toba/homebrew-todoIt auto-detects the source repo, latest release tag, description, and license via gh. The formula SHA256 is resolved using the same three-strategy approach (.sha256 sidecar, checksums.txt, direct download). After running, tap updates happen automatically via CI.
jig brew init --tap toba/homebrew-todo --tag v1.2.3 --repo toba/todo --desc "My tool" --license MITUse --dry-run to preview without creating anything. Use --json for machine-readable output.
After running, add a HOMEBREW_TAP_TOKEN secret to the source repo — a GitHub PAT with Contents write access to the tap repo.
Same idea as brew, but for Windows. Creates a companion Scoop bucket repo with a JSON manifest covering both amd64 and arm64, and injects an update-scoop CI job into the release workflow.
jig scoop init --bucket toba/scoop-jigIt auto-detects the source repo, latest release tag, description, and license via gh. SHA256 hashes are resolved for both _windows_amd64.zip and _windows_arm64.zip archives. The manifest includes checkver and autoupdate sections so Scoop's tooling can pick up new versions automatically.
jig scoop init --bucket toba/scoop-jig --tag v1.2.3 --repo toba/jig --desc "My tool" --license MITUse --dry-run to preview without creating anything. Use --json for machine-readable output.
After running, add a HOMEBREW_TAP_TOKEN secret to the source repo — a GitHub PAT with Contents write access to the bucket repo (reuses the same token as Homebrew).
One-time setup for Zed extension automation. Creates a companion extension repo on GitHub with the full scaffold (extension.toml, Cargo.toml, src/lib.rs, bump-version script and workflow, LICENSE, README), and injects a sync-extension job into the source repo's release.yml.
jig zed init --ext toba/gozer --languages "Go Text Template,Go HTML Template"It auto-detects the source repo, latest release tag, and description via gh. The --languages flag is required — it sets which languages the extension provides LSP support for. After running, extension updates happen automatically via CI.
jig zed init --ext toba/gozer --languages "CSS" --tag v1.0.0 --repo toba/go-css-lsp --desc "CSS LSP" --lsp-name go-css-lspUse --dry-run to preview all generated files without creating anything. Use --json for machine-readable output.
After running, add an EXTENSION_PAT secret to the source repo — a GitHub PAT with Contents write access to the extension repo. Also run cargo generate-lockfile in the extension repo to create the initial Cargo.lock.
Everything lives in .jig.yaml. Sections are independent — you can use any subset.
todo:
issues:
path: .issues
editor: "code --wait"
sync:
github:
repo: "owner/repo"
upstream:
sources: [...]
nope:
debug: nope.log # optional JSONL debug log
rules: [...]Config reading uses the yaml.v3 Node API for partial read/write, so no section clobbers another.
A JSON Schema is available for editor autocomplete and validation. Add this modeline to the top of your .jig.yaml:
# yaml-language-server: $schema=https://raw.githubusercontent.com/toba/jig/main/schema.json- macOS, Linux, or Windows
ghCLI for upstream monitoring, brew, scoop, zed, and sync commands (nope guard and todo core have no external dependencies)
Apache-2.0

