
I Have Issues
A small utility for managing local ‘issues’ files to small projecst and or teams, particularly useful for projects that are working heavily with AI Agents in development.
Your issue tracker should review like source code.
I Have Issues keeps a project's issue list in a single .issues file inside the repository
it tracks — one UTF-8 JSON document, versioned with the code, readable offline, no server and no
account. Native apps for macOS, iOS and Android read and write it; so does your coding agent.
diff --git a/Ledger.issues b/Ledger.issues
--- a/Ledger.issues
+++ b/Ledger.issues
@@ -142,10 +142,13 @@ "issues" : [
{
"area" : "Export",
"assignees" : [ "dru" ],
"number" : 7,
"priority" : "high",
"reportedAt" : "2026-02-11T09:24:00Z",
- "status" : "open",
+ "resolution" : "Restored trailing newline in MarkdownExporter.swift:118",
+ "resolutionKind" : "fixed",
+ "resolvedAt" : "2026-02-14T16:02:00Z",
+ "status" : "resolved",
"title" : "Markdown export drops the trailing newline",
"type" : "bug",
"uuid" : "9F2C1A7E-4D30-4B8B-9C51-6E0A2F5D77B3"
},
Four lines added, one removed, in alphabetical order, at two-space indent. That is an agent closing a bug — and a change a human can approve in a pull request without leaving the review.
ISSUES.md was the right instinct. It just has no schema.
Coding agents made ISSUES.md a de facto convention: keep the work list next to the code so an
agent or a human can read what is outstanding and write back what it fixed. It spread because it
works.
It breaks down as the list grows. At five issues a hand-written markdown list is fine. At fifty
it drifts — Severity in one entry and Priority in the next, Component here and Area
there. No entry has a stable identity, so renumbering the list breaks every cross-reference.
"This blocks that" can only be said in prose. Merge conflicts are textual rather than semantic.
Every script and every agent that parses the file has to guess at whatever shape that project
settled on.
That is not a markdown problem. It is an anything-without-a-schema problem.
Hand-written Issues.md — drifted
## Bugs
- **Markdown export drops trailing newline**
- **Severity:** High
- **Component:** Export
## Enhancements
- **Widget should list blocked issues**
- **Priority:** P2
- **Area:** Widgets
- Waiting on the export bug above
Ledger.issues — schema
{
"issues" : [
{
"area" : "Export",
"priority" : "high",
"status" : "open",
"title" : "Markdown export drops trailing newline",
"type" : "bug",
"uuid" : "9F2C1A7E-4D30-4B8B-9C51-6E0A2F5D77B3"
},
{ ... }
],
"schemaVersion" : 1
}
Same two issues. In the second, every key is where a parser expects it, the file declares the schema it was written against, and the bug has an identity that survives being reordered.
Keep everything that made it good. Add the one thing that was never true.
One file. In the repo. Versioned with the code. Readable offline. No server, no account. All of that stays. What changes is that the file now has a schema — and markdown is not abandoned, it becomes an export target instead of the source of truth.
Format guarantees
Schema-versioned, not just "JSON"
schemaVersion is required. A file written by a newer build is refused outright — the app says
so and stops — rather than silently mangling fields it does not recognize. An older file is
accepted. The migration seam is a single documented guard in the decoder.
Diff-friendly by construction
Pretty-printed, two-space indent, keys sorted alphabetically at every nesting level, slashes unescaped, ISO-8601 UTC timestamps at whole-second precision, trailing newline. Encoding the same model twice produces identical bytes, so the file never rewrites itself on save.
No field for a credential, ever
.issues files get committed, forked, and printed into CI logs. The integrations block
carries only non-secret coordinates: owner, repository, org, project, default labels and paths.
Tokens live in Keychain on Apple platforms and Credential Manager on Windows, keyed off those
coordinates.
Tolerant decoding, except where a guess would lie
Absent keys fall back to documented defaults, unknown keys survive a round trip through an older
build, unknown enum values fall back to that enum's default. Two deliberate exceptions:
resolutionKind decodes to null rather than inventing a reason work stopped, and an unrecognized
remote-link provider is preserved verbatim and re-encoded byte-identically. A provider is sync
identity — quietly mapping an unknown one to github could aim a real sync at an unrelated issue
in someone's repository.
Every issue carries a permanent uuid.
The human-facing number — #007 in the markdown export — is display-only and safe to renumber.
Relations point at uuids, so reordering the list never breaks a blocks reference. The typed
relations are blocks, blockedBy, duplicateOf, parent, child and relatedTo.
Built so an agent can parse the backlog instead of pattern-matching prose.
The app installs a Claude Code skill into your project at .claude/commands/issues.md, at
project or user level. It teaches an agent four steps.
- Read the file. No network call, no API token, no rate limit. The backlog is already checked out.
- Select the work. Find the issues whose
statusisopen,inProgressorblocked. - Implement the fix. The description, reproduction steps, environment notes and investigation notes are all in the same document.
- Write the resolution back.
status: resolved, aresolutionKind, and aresolutionstring naming the file and line that changed — which is exactly the diff at the top of this page.
The apps watch the file and reload automatically when your agent, or a git checkout, changes it on disk.
A real tracker, not a JSON editor.
- Issue list. Sidebar split into Open and Resolved, with click-through to a detail view.
- Editor. Title, type (Bug, Feature, Task, Question), priority (Low, Medium, High, Critical), status (Open, In Progress, Blocked, Resolved), resolution kind (Fixed, Won't Fix, Duplicate, Cannot Reproduce, By Design), reported date and reporter, area, milestone, estimate, labels, assignees, description, steps to reproduce, environment notes, investigation notes and resolution text.
- Detail view. One-click Resolve As… and Reopen, inline-editable notes and resolution, numbered reproduction steps, threaded comments, typed related issues, remote links.
- Project settings. Project name and summary, GitHub coordinates (owner, repository, default labels, assignees, milestone) and Azure DevOps coordinates. No token is ever stored here.
- GitHub sync. Paste a personal access token — it goes to the Keychain and is never shown again — then push issues, labels, assignees and milestones to a repository, with a summary of created, updated and failed plus a per-issue error list. Push-only today: there is no pull, no fetch-and-diff and no conflict resolution yet.
- Markdown. Export a clean
## Open/## Resolveddocument. Import an existing hand-authoredIssues.md, including older free-form## Bugs/## Enhancementslayouts and- **Severity:**bullets: the importer assigns fresh uuids and parks any section it does not recognize into the issue's notes rather than dropping it. Migrating a markdown backlog costs nothing and loses nothing. - Widget. Home-screen and desktop widget showing the open-issue count, or the list of open issues.
Platforms
Available now: macOS, iOS, Android.
In progress: Windows, Linux, Haiku.
Byte-identical encoding is a rule the specification imposes on every port rather than a habit of
any one app, so a .issues file moves between the shipping clients without churning in git.
Who it is for
Projects already keeping an in-repo issue list read by a coding agent, that have hit the point where a markdown file's lack of structure causes real friction.
Developers who want their tracker in git history, in plain diffs, with no server and no account.
Anyone building an agent workflow that has to read and write issue state deterministically.
The apps are paid. The format is free. At $10,000, the source is released.
At $10,000 in donations, the full source is released under GPLv3, free of charge for any use. A commercial licence is available for anyone who needs to embed it in a closed-source product without GPLv3's source-disclosure obligation. The goal is $10,000; the source is released when the goal is met.