Skip to main content
Glama

butchr

Agents support selectable Claude (default) and Codex providers. See provider configuration and deployment.

The software factory, rewritten. A single local daemon that:

  1. Runs your resource-agent rules — each rule is a JQL query plus a brief and agent preferences, read from BUTCHR_RULES_FILE or $XDG_CONFIG_HOME/butchr/rules.json (when the default file is absent, or its rules array is empty, nothing is staffed — there are no built-in rules; a BUTCHR_RULES_FILE that does not exist stops the daemon at startup). See src/rules/rules.ts for the schema. Rules may also name resourceProvider: "github-issue" with a GitHub issue search query; those are staffed only when GITHUB_TOKEN_FILE and BUTCHR_GITHUB_ORGS are set, in <workspace root>/github-issue/<rule>/<owner%2Frepo%23n>, and their agents work the issue with the github_get_issue and github_add_comment tools, which act only on the agent's own issue — a limit on the tools, not on a shell-capable agent that can read GITHUB_TOKEN_FILE (see src/rules/github-issue-type.ts and src/tools/github-issue.ts). Rules naming resourceProvider: "jira-idea" take JQL but staff only Jira Product Discovery ideas (issue type Idea in a product_discovery project), in <workspace root>/jira-idea/<rule>/<IDEA>, with the jira_idea_get, jira_idea_github_issues and jira_idea_add_comment tools. A jira-idea rule's inwardConnectionRules may name github-issue rules: its agents then hear changes to GitHub issues those rules match that the idea links to through a Jira remote link (read-only). When both providers run, github_link_jira_idea and jira_idea_link_github_issue let an agent create that remote link on request, only between resources those same rules currently match (src/tools/idea-github-link.ts). jira-work rules never staff an idea however broad their JQL (see src/resources/jira-idea.ts and docs/jira-idea.md). Rules naming resourceProvider: "zendesk-ticket" take Zendesk search syntax and are staffed only when BUTCHR_ZENDESK_ACCEPT_SHELL_CREDENTIAL_RISK=agents-can-read-the-zendesk-token, ZENDESK_SUBDOMAIN and an owner-only ZENDESK_OAUTH_TOKEN_FILE (an OAuth token; email/API-token auth is refused) are set, in <workspace root>/zendesk-ticket/<rule>/<subdomain%23id>, with zendesk_get_ticket and zendesk_add_internal_note, whose only write is a private internal note. Agents run shell commands as the daemon's user and can read that token, so the tool limit is not a security boundary; Zendesk's account permissions are (see docs/zendesk-ticket.md). The daemon refuses to start while agents from the old <workspace root>/<ISSUE> layout are still running (see src/daemon/legacy-preflight.ts).

  2. Runs one agent per (rule, matched ticket) — while a rule's query returns a ticket, a herdr agent works it via @brooswit/drovr, in <workspace root>/jira-work/<rule>/<ISSUE>; when the query stops returning it, the agent is stopped. Two rules matching one ticket run two independent agents. A rule's childRule makes its agents hear changes to tickets that rule matches which already Implements their ticket; its inwardConnectionRules do the same over an existing Relates link, one way only — the rules it lists do not hear it back unless they list it too (read-only; Butchr never creates links). Workspaces from before rules (<workspace root>/<ISSUE>) are left untouched and never adopted.

  3. Pushes updates to those agents — over MCP (@brooswit/thatch): agents connect to the daemon identifying which issue they work on, and the daemon channels ticket/comment/link changes up the Implements chain to the right one.

  4. Shows a live view — a webapp listing the active agents; click one and butchr opens a terminal window running herdr agent attach on it, so you drop straight into that agent's shell. No browser extension, no embedded terminal — terminals are real herdr terminals.

Change detection is @brooswit/sundry's watch over a Jira JQL feed. Blocking agent prompts are handled through herdr's own blocked detection + send_keys (herdr owns the terminal; butchr reads and answers).

Architecture

one Elysia process
├── /mcp          agents connect here (thatch), identifying via x-issue (ticket) + x-butchr-agent (rule agent) headers
├── /agents,/health   read-only live view
└── loops (landing incrementally)
    ├── rules         every enabled rule's JQL → one desired agent per (rule, ticket)
    ├── reconcile     desired rule agents ↔ herdr agents: spawn / stop
    └── notify        ticket/comment/link change → thatch channel → the agent for whatever that ticket implements

src/: config · atlassian (Jira client) · daemon (the app + notify) · web (live view). More loops land as their own modules.

Related MCP server: Jira MCP Server

Install & run

On each machine:

npm i -g @brooswit/butchr     # or pin a version
cp .env.example .env          # fill in ATLASSIAN_SITE / EMAIL / TOKEN_FILE
butchr                        # reads .env / the environment

As of 0.10.0, also set BUTCHR_ASSIGNEE_STORY and BUTCHR_ASSIGNEE_TASK (Atlassian accountIds) in .env before deploying — jira_create_issue assigns a Story/Task by role from these, and REFUSES to create one of that type if its role is unset and the caller passed no explicit assignee. Epics are unaffected.

Optionally set BUTCHR_CAPTURE_DIR to change where the session-limit watcher durably captures a pane's ANSI-stripped text when its own detection is inconclusive (the phrase is present but unrecognised, or recognised with no parseable reset time) — default .captures under the workspace root (BUTCHR_WORKSPACES, or ~/butchr-workspaces). Bounded to at most one capture per issue/trigger/pane incarnation and 50 files total (oldest evicted first); an operator turns a capture into a test fixture by deleting its # -prefixed header block.

herdr's own blocked classification is not the only blocked-detector: a pane herdr reports idle/done continuously for BUTCHR_IDLE_DIALOG_MINUTES (default 2) whose text parses as a dialog at the END of the pane is treated as blocked too — read, auto-answered if the shape is known, escalated to the pane's own ticket otherwise (an unrecognised idle-blocked dialog escalating, rather than freezing the agent silently, is the whole point — see src/agents/idle-dialog.ts). Every escalation additionally captures the full pane text to the same BUTCHR_CAPTURE_DIR, under an <ISSUE>-escalation-<timestamp>.txt name, and the Jira comment references that path (never the raw text) so the next unrecognised dialog can be fixtured from the escalation itself.

From source (development):

bun run start

Label-write permission. butchr writes agent:*/pr:* labels quietly (notifyUsers=false) so watchers aren't spammed on every status flip — but Jira Cloud only honours that for an account holding the Administrator project role (or global Administer Jira) on the board's project. Grant the daemon's Atlassian account that role on each project it labels tickets in. Without it, labels still sync — nothing is disabled — but every label change sends the ticket's watchers a Jira notification, and the daemon says so once at startup, e.g.:

[labels] KAN: account booswrit@gmail.com lacks ADMINISTER_PROJECTS — label writes will NOTIFY watchers. Remedy: grant booswrit@gmail.com the Administrator project role on KAN, or accept notifying label writes.

No config knob is needed — the daemon detects this per project automatically. Current state as of 2026-08-28: brooswit is a site admin (has it everywhere); booswrit was granted the KAN Administrator role.

Development

bun run check    # generate + typecheck + tests + coverage ≥90%   (what CI runs)

bun run check is the single verification command — run it in full, not step by step. It runs tsc under bun --bun rather than through a shebang-resolved node, so no particular node version is required. Before the gate runs, it prints a one-line preflight naming the runtimes actually in use (e.g. preflight: bun 1.4.0, node v12.22.9, tsc 5.6.3 (typecheck runs under bun)); paste that line along with the rest of the output when reporting a gate result.

Every src/ change needs a changelog.d/<TICKET>.md fragment (CI enforces it) — see changelog.d/README.md. The version is assigned at merge, not on a branch: do not bump package.json or add a dated CHANGELOG.md heading yourself.

Predecessor (300 releases of history) preserved at brooswit/butchr-legacy.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    An MCP server that enables interaction with Jira to fetch issues by key and perform JQL searches. It provides a foundation for integrating multiple work systems, with planned support for Slack and GitHub.
    338 npm
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    A comprehensive, production-ready MCP server for seamless Jira Cloud integration, enabling AI agents and custom applications to manage boards, issues, users, projects, and workflows via natural language commands.
    642 npm
    4
    MIT
  • A
    license
    A
    quality
    F
    maintenance
    A local MCP server for Jira Cloud-driven development. Connects MCP clients to Jira for searching issues, reading tasks, parsing attachments, and mapping projects to local repos.
    10
    10 npm
    MIT