We Replaced Jira With Markdown Files
Early this year I was wiring Claude into Jira through an MCP server. It worked, and every session it felt slightly wrong: slow round trips, a schema I did not control, structure sitting somewhere the agent could not see while it was reading the code.
Which makes it a context problem rather than a tooling preference. Context engineering is not about pushing more context into the model; it is about putting the context where the agent is already looking. Ours could read every line of the codebase and not the ticket telling them what to change.
The fix was almost embarrassingly simple. Put the ticket in the repo, as markdown. I pitched it to a colleague, and off we went.
Seven months later: 15 projects across 10 repositories, 165 live tickets, 12 people on the board including non-developers, and no Jira licence.
This post is why we left and what we built. Two follow-ups cover the rest: the skill and the loop that let agents work these tickets, and the three review layers that keep the output honest.
What was actually wrong with Jira
The cost was easy to name: roughly €2,000 a year for something we used maybe 5% of. It was not the reason we left.
Every user had to be paid for, so the board was implicitly rationed. Performance degraded as projects grew. The features we wanted sat behind paid plugins. Automations were clumsy enough that we mostly did not write them. And the board was close to what we wanted without ever being it, because that last gap lived in someone else's product roadmap. None of that is fatal alone. Together it means the tool shapes the team instead of the other way around.
The constraint that ruled out the obvious answers
We are one team maintaining ten separate repositories that ship independently of one another, across TypeScript, C#, Java and PowerShell. A monorepo was never realistic.
That kills the usual alternatives. GitHub Issues comes closest and misses twice: issues are scoped to one repository, so cross-repo visibility becomes somebody's weekly spreadsheet, and despite feeling like part of the repo they are not in it. They live in a database behind an API. Not files, not on the branch, not in the diff, and not something an agent editing the code can read without a round trip. Every hosted alternative moves the work further away still.
So we inverted it: distribute the tickets, centralise the view.
One file per ticket
Every repository carries a tickets/ folder. A ticket is one markdown file:
---
id: APP-42
type: story
title: '[Profile] Add user avatar upload'
status: in-progress
priority: high
size: m
assignee: developer@example.com
reported_by: colleague@example.com
tasks:
- name: Implementation plan
status: done
- name: Upload component
status: in-progress
---
## Objective
## Context
## Acceptance Criteria
## Scope
## Design
## Technical Notes
## Implementation Plan
## Diagram
## Work Log
The frontmatter is the machine-readable half, and none of it is free-form. Every field is schema-validated: the status vocabulary, priorities, t-shirt sizes, the ID matching its filename, timestamps in one format. A pre-commit hook and a CI step both run that validation, so a malformed ticket never reaches the default branch.
The body is the human half, with fixed headings. That is what makes a section addressable: a tool can replace Acceptance Criteria by name without touching anything around it, which you cannot do reliably to a free-form description field. ## Diagram holds Mermaid, and most substantial tickets have one, because a state machine or a data flow is faster to check than the paragraph describing it. The same pre-commit hook parses every Mermaid block with the real Mermaid parser, so a diagram that would not render cannot be committed.
Which leads to the rule holding it together: nobody edits these files by hand. Writes go through a CLI of typed operations (set a status, claim a task, tick a criterion, append a work log entry, replace a section) that stamps timestamps, refuses invalid transitions, and keeps the file valid by construction. Hand-editing markdown is how fields drift and a board stops being trustworthy. The format is open to read and closed to casual writing.
Each repository also holds a small derived .tickets.config.json with its prefix, default branch and allowed values, so validation is local and calls nothing external. An admin edits project settings in the portal; the file is written out to every affected repository.
The board is a projection
A viewer application (Angular, Firebase, a GitHub App for commits) scans every configured repository and aggregates the tickets into one Kanban board. Edits commit back to the correct branch: the ticket's feature branch if one exists, the default branch otherwise. Because every change is a commit, git history is the audit trail.
The one piece of ticket content not in git is board ordering, which lives in Firestore next to the things that were never content: profiles, saved filters, role lists, the project registry. Dragging a card to reprioritise would otherwise rewrite files across ten repositories and produce merge conflicts carrying no information. Content is git-canonical; ordering is not content.
Two decisions I would keep in any rebuild. The lanes are asymmetric: in-progress is twice the width of the others and shows the task checklist and the live branch inline, which matters far more than I expected once several tickets run at once. Workflow steps are subtasks, not columns: code review, manual test and E2E are entries in the ticket's task list. For a team of two to five, a column per step produces a board of nearly-empty columns, while task-level detail puts the standup answer on the card.
Nobody pays to be on the board
We already had Microsoft Entra SSO, so authentication cost nothing and no seat has a price on it. That reads like a footnote, and it is the change I value most: when visibility stops being metered, you stop deciding who deserves it.
It also took the system somewhere I did not plan. Five of the fifteen projects have no repository at all. They are folders in a shared host repo covering application management, business automation, engineering optimisation, AI enablement and general company work. What is in them now: onboarding a new colleague, enabling SSO, formalising a technical quick-scan process, getting the team through a Claude course. None of it is code, all of it sits on the same board, next to the software work it competes with for time.
Two ways in, one authoring guide
WBTickets has its own MCP server alongside the eleven others we run: 18 tools for reads, writes, milestones and attachments, hitting the same repositories through the same commit gateway as the board.
That is how the projects without a codebase work. A colleague in claude.ai, with no checkout and no git, describes a problem in their own words and ends up with a schema-valid ticket committed to a repository. create_ticket is deliberately two-step: called with no arguments it creates nothing and returns the authoring guide, which tells the agent what to ask and to get sign-off before writing. That last part is instruction rather than enforcement. The server will happily create a ticket on a first call that arrives with arguments; what the two-step buys is that the guide is in front of the agent before it has anything to commit.
For code repositories we prefer the other route, running on that same guide. A ticket for a real codebase is written from a checkout by an agent that reads the code first: what exists, what the change touches, which older ticket decided the design that is there now. That is the difference between a Scope with real boundaries and one that merely sounds plausible. The write still lands on the default branch through the same path; only the analysis is local.
Attachments get their own trick. Screenshots are how non-developers explain a bug, but pushing image bytes through a model's context is pure waste. So view_attachments returns metadata only and opens a small app inside the chat; the app fetches and uploads the actual bytes through separate tools that never put them in the model's context at all. You see the screenshot, drag a new one in, and the agent's context stays clean.
The loop closes at the far end: reported_by records who asked, and they are notified when the ticket reaches done. They never have to open the board.
The board matches the work that was delivered
I have never seen a ticket board outside the codebase that still matched what was actually built. Not a failure of any particular team: it is distance. Scope changes, deviations and the reasoning behind them get settled in a pull request or a call, and updating the ticket is a separate action somebody has to remember to take, later, from memory. Some of it lands days late as a summary. Most of it never lands at all, which is why the interesting question about any board is not what it says but when it last agreed with reality.
Here the agent doing the work is what writes it down, in the same commit as the code. A deviation gets a work log entry when it happens, with the reason, while the reason is still in context rather than reconstructed afterwards. So by the time the pull request opens, the ticket describes what was built and not only what was planned, and where the two disagree that disagreement is on the record with its argument attached.
That is the part I would miss most if we went back. A board that is merely tidy tells you how disciplined the team is about bookkeeping. A board that is written by the work tells you what happened.
What came nearly free afterwards
Once the substrate was markdown in git, the adjacent things stopped being projects. Milestones as roadmap items with horizons instead of dates. Release notes generated from git tags with ticket IDs resolved to board links. Design artifacts: a self-contained HTML mockup committed beside the ticket, reviewed in the same pull request as the code and rendered in a sandboxed iframe on the board.
The next one is in build: a knowledge base to replace Confluence and its ~300 articles. What made it worth starting is that almost none of it is new. It inherits the auth, the attachments, the markdown rendering and the same git-canonical instinct, so the architecture argument was about content workflow rather than infrastructure.
Same primitive every time. That is the compounding return of a format every tool already understands.
The build-versus-buy line moved
We are a small in-house IT department. Warmtebouw installs heating, cooling and ventilation; we are the handful of people who build the software around that. This is not a heroic story, and that is the part worth noticing.
You rarely bought a tool because its feature list was unbeatable. You bought it because building and maintaining something that fit your team cost far more than the licence did. That arithmetic has changed. Writing a bespoke system, and more importantly keeping it correct and documented while you use it, is now cheap enough that a small team can do it alongside the actual work. We are one of many teams quietly discovering that the licence was buying convenience we can now produce ourselves, and better, because ours fits.
It does not generalise to everything. The tools worth replacing are the ones where your own workflow is the product and the vendor's genericity is the tax you pay. Nobody sane is rebuilding their ERP this way.
Which brings me to the vendors. Atlassian is now positioning itself as the layer in the middle of agentic work, and I would not want to be selling that. The middle is precisely the position an agent routes around. If the work lives in the repository and the agent is already there, a hosted system of record stops being where the work happens and becomes a place you synchronise to. That is a difficult thing to charge per seat for, and a harder one to defend once teams notice the alternative is a folder of markdown files.
The honest ledger
You own the maintenance. Jira's operating burden is zero and ours is not. This began as a side project and grew in the gaps between real work. The core worked from the moment there were tickets on the board; everything since has been sharpening it.
What makes that sustainable is who maintains it: the team that uses it every day. A rough edge gets filed down by the person it just annoyed, usually in the same session they hit it, and the fix is filed as a ticket in the system it fixes. There is no maintenance budget to defend, because the work is spread thinly across all the other work.
What we bought is a system that fits our workflow exactly rather than 60% of it, a board open to everyone in the company, and every remaining failure mode being ours to fix. The licence saving is real and it is the least interesting part.
The board was the deliverable. The consequence was that the description of the work now lives where the agent is already looking, and that changed how the work gets done far more than any board could. Here is what that looks like in practice.