@ironforgesoftware/junco
v0.14.0
Published
A harness-agnostic coding-agent worker that turns Markdown tickets into git pull requests.
Maintainers
Readme
junco
Issues in. Pull requests out.
junco.ironforgesoftware.com — the one-page tour.
Junco is a daemon that runs on your machine. Label a GitHub issue junco and it
plans the work as an issue comment; approve with a label, and it executes with a
supervised coding agent in an isolated git worktree and opens the pull request —
all watched from a fullscreen terminal dashboard. It drives the agent against any
OpenAI-compatible inference endpoint you point it at — or a hosted provider
from the embedded catalog. Your code, your git, and your credentials stay in a
loop you control. The other door is a folder: Markdown
tickets with a small YAML header, authored by any tool or human — junco is
harness-agnostic on the dispatch side.
🐦 junco acme/reef-api ●2 review · ⚑1 PR · ✓14 · daemon ● up 6h · ◐1 ⏳2
╭ 1 repos ─────────╮╭ 2 issues · 14 ─────────────────────╮╭ 3 PRs · reef-api ──────╮
│▌acme/reef-api 2● ││▌● #52 Fix reef color… plan-ready ││▌✗ #52 fix-color-lut ✗2 │
│ acme/tide-cli ││ ◐ #46 Bleaching alert working ││ ◐ #48 tide-cache ◍1 │
│──────────────────││ ○ #61 Add tide tables 3h ││ ● #41 alert-copy ✓4 │
│ queue ││ ✓ #44 Coral survey done 2d ││ │
│ ◐ #46 · turn 14 ││ ││ │
│ 2 waiting ││ 2/14 ││ │
╰──────────────────╯╰────────────────────────────────────╯╰────────────────────────╯
↑/↓ move · ←/→ panes · enter preview · m import · o approve · c chat · / filter · ? helpThe loop: label → plan → approve → PR
- Label an issue
junco. The daemon's next sweep verifies the labeler has write access, plans the work in a read-only session, and posts the plan as an issue comment — the issue flips tojunco:plan-ready. - Read the plan. Edit it if you like. The comment is ordinary markdown, and whatever it says at approval time is what executes.
- Apply
junco:approved. Junco verifies who applied it (a write+ collaborator) and that it postdates the plan comment, then queues an execution ticket —junco:queued, thenjunco:workingwhile the agent runs in an isolated worktree with verification and a diff-vs-spec critic. - The pull request arrives as a draft carrying a deterministic
Closes owner/repo#Nline, and the issue flips tojunco:done— orjunco:failed, with the reason as a comment.
Three properties worth knowing:
- Fails closed. Ticket frontmatter is always built by the bridge — never from model output or issue text — and any verification error stops the dispatch.
- Questions skip planning. Add
junco:askalongside the trigger label for read-only Q&A, answered as a comment: no branch, no PR. - Offline-tolerant. When GitHub is unreachable, labels, comments, and PR
pushes queue in a durable outbox — FIFO replay, idempotent, dead-lettered after
3 attempts.
junco outboxinspects it; finished work is never lost to a dead network.
One issue, end to end:
$ junco logs -f
09:14:07 INFO github bridge: dispatched issue {"nwo":"acme/reef-api","issue":52,"id":"gh-acme-reef-api-52-plan","kind":"plan"}
09:14:52 INFO [gh-acme-reef-api-52-plan] finalized {"dst":"done/gh-acme-reef-api-52-plan.md","status":"completed"}
── plan on #52 · junco:plan-ready · a human reads it, applies junco:approved ──
09:31:02 INFO github bridge: approved plan dispatched for execution {"nwo":"acme/reef-api","issue":52,"id":"gh-acme-reef-api-52"}
09:31:05 INFO claimed {"src":"inbox/gh-acme-reef-api-52.md","dst":"processing/gh-acme-reef-api-52.md"}
09:42:31 INFO [gh-acme-reef-api-52] spec verification: 2/2 checks passed
09:42:58 INFO [gh-acme-reef-api-52] critic: pass
09:43:41 INFO [gh-acme-reef-api-52] pushed junco/gh-acme-reef-api-52 (3 new commits)
09:43:56 INFO [gh-acme-reef-api-52] opened PR https://github.com/acme/reef-api/pull/57
09:43:57 INFO [gh-acme-reef-api-52] finalized (pr-flow) {"dst":"done/gh-acme-reef-api-52.md","status":"completed"}
09:43:58 INFO idle→ GitHub mode guide — setup, lifecycle labels, trust model.
Why junco
- Plans before code — the plan is an editable comment on the issue, and nothing executes until an approval that junco verifies (who applied it, and that it came after the plan).
- A dashboard worth living in — a fullscreen terminal UI for the whole loop: watch repos, read plans, approve, track the queue, track your open PRs, and run any junco command from a palette without leaving it.
- Tickets in, pull requests out — a ticket is a Markdown file with a small
YAML header. Junco claims it, works in an isolated git worktree, verifies the
result, runs a diff-vs-spec critic, and opens a draft PR. Tickets without a
repo:are Q&A: answered in place, read-only, no git involved. - Fork-PR mode —
junco import owner/repo#Nplans and PRs an issue on a repo you don't own: it forks, clones the fork into a managed directory, and opens the draft PR upstream — no labels or comments on their repo. - Supervised, not hopeful — loop guards catch stuck agents, timeouts salvage the commits already made, transient failures retry with backoff, and every run leaves a full transcript.
- Offline-tolerant — when GitHub is unreachable, an outbox queues the comments, labels, and PR pushes durably and drains itself on reconnect.
- Local-first by design — your machine, your git, your
ghauth, your choice of inference endpoint. There is no third service in the loop unless you choose one. - Its own GitHub identity, if you want one —
junco auth login(or the setup wizard's Account chapter) gives the daemon a dedicated bot account, so PRs, comments, and commits are attributed to the bot instead of you, and the account that approves work is not the one that dispatched it.
It audits, you decide what to file
Audit and investigate are junco's two read-only modes: audit sweeps a whole repo and turns what it finds into a backlog of candidate issues, while investigate (below) deep-reads one already-filed issue and turns your understanding of it into a single draft comment — audit generates backlog, investigate deepens one item of it.
junco audit <path|owner/repo|owner/repo#N> audits a repo — npm audit for the
dependency tree plus a read-only agent pass over the code — and parks the findings for
review instead of filing them right away. junco audit review lists what's
pending; junco audit review <id> shows each finding's fingerprint, severity,
and title; junco audit file <id> --all (or --only <fingerprint,…>) files the
ones you confirm as GitHub issues titled [<severity>] <title> (<ruleId>). Every
finding carries a fingerprint, and filing dedupes on it against the repo's most
recent 500 issues — any author, closed ones included — so nothing is filed twice.
It works on any watched repo, owned or not. On a repo you own, filed issues get
junco:finding + severity/<level> labels (best-effort), and --auto-plan adds
the trigger label so junco plans its own findings for you to approve. On a repo
you don't own, issues file label-free — junco never assumes triage rights it
doesn't have — and --auto-plan has no effect there.
Point it at one issue instead of the whole repo — junco audit owner/repo#N
— and the audit scopes to the code that issue implicates; filed findings carry
a Context: line GitHub cross-references onto the issue's timeline
automatically (no comment is posted; that's junco investigate, below). Unlike
the bare owner/repo form above, an issue reference auto-provisions an
unwatched repo the same way junco import/junco investigate do.
→ Repo audit guide
It investigates, you decide what to post
junco investigate <owner/repo#N|issue-url> reads a single issue and investigates
it against the repo, read-only — root cause, evidence, repro steps, a
suggested fix direction — then parks the result as a comment draft instead
of posting it. junco investigate review lists pending drafts; junco investigate
review <id> previews exactly what would post, footer included; junco investigate
edit <id> opens it in $EDITOR for a full rewrite; junco investigate
post <id> is the human confirm step that actually posts the comment. Every
posted comment carries a disclosure footer by default (--no-footer to omit
it). It works identically on owned and unowned repos — an unwatched repo is
auto-forked and provisioned the same way junco import does.
→ Issue investigation guide
Chat with the agent
Press c on any repo, issue or PR in the dashboard to open an ongoing, read-only chat
with the coding agent about that repository — from an issue or PR it prefills
/issue N or /pr N into the composer, not sent. Ask anything ("what does this module
do?", "is there a cheaper way to do #42?"), and when you want work done, ask for a
ticket: the agent drafts it inline and junco parks it for you to review, the same confirm gate
audit/investigate above use. s submits the draft, e opens it in $EDITOR,
r cycles its route (auto → inbox → issue), D discards it — nothing is ever
submitted, filed, or dispatched without that confirm. Ask the agent to submit a draft
it just parked and, with chat.submitTool on (the default), it calls its one action
tool, junco_submit, itself — proposing the same submit as a y/n card instead of
the s key, and reporting back what actually happened once you decide. Every dispatch
branch is reachable from the conversation: a plain ticket, a ticket set, a plan set, an
amend on an open PR, an apply-ticket patch, or a request to audit/investigate. /pr N
and /issue N pull that PR or issue's context into the conversation. The session
lives in the daemon, not the dashboard, so it survives a dashboard quit or a daemon
restart; junco transcript --chat owner/repo prints one outside the TUI. The
chat's tools are the same read-only subset a Q&A ticket gets, plus that one action
tool; /chat/* never leaves loopback.
→ ARCHITECTURE.md § The chat path ·
Dashboard
Sixty seconds to a running worker
Requires Node ≥ 22.19, plus git and an authenticated gh for PR flows. The execution sandbox is on by default: macOS uses the built-in Seatbelt (nothing to install); Linux needs bubblewrap (apt install bubblewrap / dnf install bubblewrap) — or set sandbox.backend: "none" / sandbox.enabled: false.
npx @ironforgesoftware/junco # first run → setup wizard; afterwards → dashboard (daemon ensured)junco dashboard (or bare junco on a first run) opens a full-screen guided
walkthrough — workspace, model setup (an inference endpoint with live
discovery, or a hosted provider from the built-in catalog), repo containment,
the GitHub bridge, and the recommended extras — then creates the queue,
verifies the result with a flight check, and lands you in the dashboard.
Re-run it anytime from the command palette ("setup") to tune an existing
config (it only writes what you change). junco config init scaffolds
defaults non-interactively — the headless equivalent for scripted setups.
(Prefer a global install: npm install -g @ironforgesoftware/junco, then the
command is just junco.)
junco dashboard # the cockpit: watch repos, dispatch, approve, monitor PRs
junco start # or run the daemon in the foreground; Ctrl-C to stop
junco submit my-task.md # feed it a Markdown ticket directly
junco skill install --harness claude # link the junco-dispatch skill into your agent harnessOr drop a ticket in a folder
The inbox is the second door: drop a Markdown ticket (junco submit, or any tool
writing files) and it runs the same 14-phase pipeline — claimed by atomic rename,
executed in a worktree, verified, reviewed by the critic, opened as a draft PR —
with requeue-and-backoff on transient failures. junco schema prints the
frontmatter contract, examples/ has templates, and the bundled
junco-dispatch skill teaches coding agents to write well-formed tickets.
→ Tickets guide
When the exact bytes are already known, skip the agent: an apply ticket
carries a git format-patch series in a junco-patch fence — or
junco submit --patch <series.patch> --repo <path> composes one for you — and
Junco lands it with git am --3way and opens the PR with zero model turns.
Verification still gates the PR; a series that no longer applies cleanly
escalates to a single agent session that treats the patch as a spec.
→ Apply tickets
Documentation
| Guide | What's inside |
| ------------------------------------------ | ----------------------------------------------------------------------------------------------------------- |
| Tickets | Ticket flavors, frontmatter reference, examples, submission, the PR-flow lifecycle |
| Configuration | The config.json skeleton, key knobs, hot-reload, and the junco config CLI |
| GitHub mode | Setup, the plan → approve → PR loop, lifecycle labels, offline behavior, trust model |
| Repo audit | junco audit — audit any watched repo, review parked findings, file the ones you confirm |
| Issue investigation | junco investigate — investigate an issue read-only, review the drafted comment, post the ones you confirm |
| Dashboard | Every pane, key, and the command palette |
| Bot account | Give the daemon its own GitHub identity — setup, how it works, doctor checks, migration notes |
| Operations | Health endpoint, running as a service, security model, troubleshooting |
| ARCHITECTURE.md | The runtime, module by module — accurate and maintained |
CLI at a glance
| | |
| ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| junco start / junco restart | run the daemon / restart the installed service |
| junco submit <file> | queue a ticket (also reads stdin) |
| junco submit --patch <file> --repo <path> | compose an apply ticket from a git format-patch series — zero model turns (--title, --why, --verify) |
| junco submit --plan <file> --repo <path> | compile a junco-plan fence into a dependency-ordered ticket set, one PR per task (needs planSets.enabled) |
| junco submit --as-issue <file> | file the ticket as a parked, unlabeled issue via the bot account — your trigger label launches it (also --plan) |
| junco lint <file> | validate a ticket (plan-lint + repo/branch preflight) without submitting |
| junco submit --dry-run <file> | report destination, discarded frontmatter, and lint without submitting |
| junco dashboard | the fullscreen TUI |
| junco import <owner/repo#N \| url> | plan and PR any repo's issue — direct branches when the bot can push, fork-PR mode otherwise |
| junco status / junco list / junco logs -f | daemon, queue, and log visibility |
| junco prs | list junco-authored pull requests across watched repos |
| junco audit <path\|owner/repo\|owner/repo#N> [--auto-plan] | audit a repo, or scope to one issue (owned or external); findings await review (audit review, audit file, audit discard) |
| junco investigate <owner/repo#N\|url> | investigate an issue (owned or external); draft awaits review (investigate review, investigate edit, investigate post) |
| junco retry <name…\|--all> | move failed tickets back to the inbox |
| junco rm <name> | delete a queued ticket from the inbox |
| junco outbox [flush] | inspect or push the offline GitHub backlog |
| junco unwatch <owner/repo> [--plan] | stop watching a repo and delete its junco-owned state (--plan previews as JSON) |
| junco data [--json] / junco data migrate | print the data tree (paths, counts, provenance); migrate unifies legacy roots (configuration guide) |
| junco worktree prune <path> | prune a stale or backup worktree (lock-guarded; refuses a live one) |
| junco doctor | preflight config, git/gh auth, endpoint, model |
| junco transcript <ticket-id> \| --chat <owner/repo> | print a ticket's recorded event transcript — runs, tool calls, results, the agent's answer (--thinking, --tools); --chat prints a repo's dashboard chat transcript instead |
| junco replay <ticket-id\|path.jsonl> | re-run a recorded transcript through the guards under a chosen policy — a what-if report, not a live rerun |
| junco auth login | log the bot account in (bot account guide) |
| junco auth grant <owner/repo> | grant the bot push access to a repo — invite as you, accept as the bot (bot account guide) |
| junco config / junco schema / junco service | config path/list/get/set/init, ticket schema, render a launchd/systemd service file to stdout |
| junco update | install the latest release and drain-restart the supervised daemon |
| junco skill install [--harness <name\|path>] | link the junco-dispatch skill into agent-harness skills dirs (claude, codex, pi, omp, opencode) |
| junco run-once / junco inbox-path | process one ticket and exit (dev/cron; no lock) / print the inbox path |
| junco --version | print the running version |
Contributing
Contributions are welcome — junco is young, and the codebase is still small enough to hold in your head.
git clone https://github.com/ironforgesoftware/junco && cd junco
npm install
npm test # vitest, ~5,200 tests, ~40 s- Run the full gate before a PR:
npm run lint && npm run format:check && npm run typecheck && npm run build && npm test && npm run test:e2e - Development is test-first with a commit per unit of work; the suite is green at
every commit. Conventional commit messages (
feat:,fix:, …). - ARCHITECTURE.md is accurate and maintained — read it before touching the runtime, and keep it true when you do.
- For features, open an issue first — plans are cheap, rework isn't.
The longer version — conventions, testing expectations, commit and PR policy —
lives in CONTRIBUTING.md. And junco can submit tickets against
itself — drop a PR-flow ticket with repo: pointing at this repository.
License
Named after the dark-eyed junco — a small, unassuming snowbird that works through winter.
