AI / Developer tooling
Software AI Factory
A local board that relays Azure DevOps Work Items and GitHub issues through pipelines of Claude Code runs, each in its own git worktree, and asks a human to approve any tool outside an allowlist.
- Role
- Sole engineer: design, build and ship
- When
- 2026
- Source
- Private · walkthrough on request
- Stack
- Bun
- Hono
- SQLite
- React
- Vite
- shadcn/ui
- Claude Code CLI
- Web Push
- 444 commits between 20 Oct and 10 Nov 2026, with seven ADRs recording the board's rules.
- Runs work items from Azure DevOps and GitHub on one board, each in its own isolated worktree and branch.
- Reviews open Azure DevOps pull requests automatically, and votes 'Waiting for author' when no work item is linked.
Problem
Running Claude Code on real tickets means repeating the same setup by hand. You make a worktree and branch, paste the ticket, watch for permission prompts, move the ticket in the tracker and clean up afterward. Do that for several tickets at once across Azure DevOps and GitHub and things start slipping: a worktree nobody deletes, a branch made from unpushed local commits, a run stuck on an approval no one saw. Software AI Factory puts all of it on a board. Work items are lined up into pipelines of steps, and each step is a Claude Code run that the board starts, watches and moves along.
My role
I was the sole engineer and handled design, build and shipping; git history shows a single author across 444 commits. I wrote the domain glossary (CONTEXT.md), the ADRs, the Bun server, the React PWA, and the launchd service script that runs two instances (work and personal) side by side.
Architecture
A Bun process serves a Hono API and the built PWA on 127.0.0.1 and keeps its state in SQLite (bun:sqlite). Tracker gateways talk to Azure DevOps and GitHub through the user’s az / gh sign-in, with a PAT in the macOS Keychain as the fallback. A poller refreshes the backlog and moves lined-up work items into a pipeline once they carry the ready tag.
Each run spawns the claude CLI headless with stream-json input and output. The bucket’s allowlist goes in --allowedTools. Any other tool call is routed through --permission-prompt-tool to a small MCP server the board starts locally, where it waits for Approve or Deny in the Inbox. The browser gets events over server-sent events, and Web Push notifications fire when a run finishes, fails or gets blocked.
Key decisions
- A bucket either runs a prompt or sets a tracker state, never both (ADR 0001). That way it’s predictable what a drag will do. Later amendments let prompt buckets post their result as a comment and applied the same rules to GitHub.
- The factory owns the worktree; the prompt doesn’t (ADR 0002). Each work item gets
<tracker>-<number>/repoandartifacts/. Clean Up deletes exactly that folder and branch, and its confirmation lists only the commits no other ref holds. After a real incident, branches now start from the remote’s base branch instead of the local one. - Work items advance on their own (ADR 0003). Anything short of a clean success stops the relay with a sticky notification. Polling stands in for webhooks, which a local app cannot receive.
- GitHub as a second tracker, with only open and closed as states (ADR 0004). I rejected labels-as-states and Projects fields because GitHub does not enforce them.
- PR reviews sit outside pipelines (ADR 0005). Every PR gets the same single review, so pipeline vocabulary would add nothing.
- Nudges steer a live run (ADR 0006). Input stays open, so a message can be heard next or now. A “now” message is sent as an interrupt control request followed by the message, because the CLI’s
priorityflag alone did not cancel tool calls. - shadcn/ui for behaviour only (ADR 0007). The look comes from the board’s own tokens, which shadcn’s variables are mapped onto.
Outcomes
- Tests run the in-process API against fake tracker gateways, a scripted fake Claude runner, a recording push sender and throwaway git repos, with a fresh SQLite database per test.
- Two isolated instances run as launchd agents, each signed in to its own Claude Code account.