Druware Software Designs
Druware Software DesignsNative software & developer tools · Est. 1995
I Have Issues

I Have Issues

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.

Updated

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.

  1. Read the file. No network call, no API token, no rate limit. The backlog is already checked out.
  2. Select the work. Find the issues whose status is open, inProgress or blocked.
  3. Implement the fix. The description, reproduction steps, environment notes and investigation notes are all in the same document.
  4. Write the resolution back. status: resolved, a resolutionKind, and a resolution string 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 / ## Resolved document. Import an existing hand-authored Issues.md, including older free-form ## Bugs / ## Enhancements layouts 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.

cards
Powered by paypal
Today **I Have Issues** is a paid product. The `.issues` format specification is free and open for anyone to implement — it is a data format, not software — and the documents you create with the apps are unencumbered.

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.

News

ISSUES.md Alternative: In-Repo Issue Tracking for AI Coding Agents

If you use Claude Code, Cursor, or Windsurf on a real project, you've probably already reinvented ISSUES.md. Nobody standardized it, but it spread anyway, because it solves a real problem: give the agent a work list it can read without a network call, and a place to write back what it fixed. No server, no account, no context switch out of the editor. It lives and dies with the commit history like everything else in the repo.

It also breaks down. Not immediately — at five issues, a hand-written markdown list is completely fine. The trouble starts around fifty.

I Have Issues for macOS showing an in-repo issue tracker backlog stored as a schema-versioned JSON .issues file

Why does ISSUES.md break down at fifty issues?

A markdown checklist has no schema. Nothing enforces that every entry looks the same, so nothing keeps them looking the same. Six months in, a typical ISSUES.md looks like this:

## Bugs

- **Markdown export drops the trailing newline**
  - **Severity:** High
  - **Component:** Export

## Enhancements

- **Add a compact view for the issue list**
  - **Priority:** P2
  - **Area:** Widgets

Two sections, two field names for the same concept — Severity in one, Priority in the other; Component here, Area there. Neither entry has a stable identity, so renumbering the list breaks every cross-reference that pointed at "#12." There's no way to say "this blocks that" except in prose, which a script or an agent has to parse and hope it guessed right. And when two branches both touch the file, git resolves the conflict as text, not data — it has no idea both edits touched the same issue's status field, only that two regions of the same lines changed.

None of that is really a markdown problem. It's an anything-without-a-schema problem. You'd hit the same wall with a TODO.md or a hand-rolled YAML list. The format was never the point — the convention was right, the file just had no shape.

The insight: keep the file, add a schema

The good parts of ISSUES.md are worth keeping exactly as they are: one file, in the repository, versioned with the code, readable offline, no server, no account. What was missing was never the location — it was structure. .issues is that same idea with a schema attached: a single UTF-8 JSON file beside the code it tracks, with required structure, a version number, and a canonical byte-for-byte encoding so it reads cleanly in a git diff. Markdown doesn't disappear — the apps still export a clean ## Open / ## Resolved rendering — it just stops being the source of truth.

What a resolved issue looks like as a diff

This is the part that matters for a coding agent's workflow: closing an issue is a small, reviewable change, not a rewrite of a paragraph. Here's an agent marking a bug fixed, as a real git diff inside the .issues file:

   {
     "area" : "Export",
     "priority" : "high",
-    "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" : "3B8A0C51-2F44-4E6D-B0A7-1C9E5D4F8A02"
   }

Four lines added, one removed, sorted alphabetically at two-space indent — a change a human reviewer can approve in a pull request without leaving the diff view. The resolution string names the file and line that changed, so the audit trail from "reported" to "fixed" lives in the same document, in the same commit history, as the fix itself.

The same backlog, before and after a schema

Same two issues from the drifted markdown list above, expressed as schema'd JSON instead:

{
  "issues" : [
    {
      "area" : "Export",
      "priority" : "high",
      "status" : "open",
      "title" : "Markdown export drops the trailing newline",
      "type" : "bug",
      "uuid" : "3B8A0C51-2F44-4E6D-B0A7-1C9E5D4F8A02"
    },
    {
      "area" : "Widgets",
      "priority" : "medium",
      "status" : "open",
      "title" : "Add a compact view for the issue list",
      "type" : "feature",
      "uuid" : "7E1D2A44-9B6C-4F03-8E71-2A5C0D9B4416"
    }
  ],
  "schemaVersion" : 1
}

One field name per concept, always — area, priority, status, not a coin flip between Severity and Priority depending on who wrote the entry. Every issue carries a permanent uuid. The human-facing number you'd see in a markdown export is display-only and safe to renumber; typed relations — blocks, blockedBy, duplicateOf, parent, child, relatedTo — point at uuids, so reordering the list never breaks a reference.

What the format actually guarantees

  • schemaVersion is required, and currently 1. A file written by a newer build than yours is refused outright, rather than silently mangled. An older file is accepted.
  • 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, a trailing newline. Encoding the same model twice produces identical bytes — the file doesn't rewrite itself on every 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, project. Tokens live in the OS credential store (Keychain, Credential Manager, KWallet, libsecret), keyed off those coordinates, never the document.
  • Tolerant decoding, except where a guess would lie. Unknown keys survive round trips through older builds. Two fields are the deliberate exception: resolutionKind decodes to null rather than inventing a reason work stopped, and an unrecognized remote-link provider is preserved verbatim rather than coerced — because a provider is sync identity, and mapping an unrecognized one to github could aim a real sync at the wrong repository.

How an agent actually uses this

I Have Issues installs a skill into your project for Claude Code (.claude/commands/issues.md), Cursor (.cursor/rules/issues.mdc), and Windsurf (.windsurf/rules/issues.md) — byte-identical instructions from whichever client offers it. It teaches a four-step loop: read the file (no network call, no API token, no rate limit — the backlog is already checked out with the rest of the repo); select issues whose status is open, inProgress, or blocked; implement the fix using the description, reproduction steps, and environment notes already in the document; write the resolution back. The apps watch the file on disk and reload automatically when an agent — or a plain git checkout — changes it underneath them.

Proof the spec is real: independent implementations agree on the bytes

Anyone can claim a format is well-specified. The interesting test is whether two independently-written codebases, sharing no code, produce the same bytes from the same input. The shared C++ core, libs/issueskit, is pure C++17 standard library — no GTK, no Qt, no platform toolkit linked in. That invariant is enforced by check-portable.sh, a script that greps every #include for Be API, GTK/GLib, and Qt/KDE headers and fails the build on a hit. Run today, its test suite passed all 167 checks: 77 on the core format contract, 36 on token-store account-key derivation, 44 on sync (the library implements the flow even though no client currently exposes it), and 10 on byte-exact round-tripping.

More important than the count: the Swift and Kotlin ports are independent reimplementations of the .issues format — separate codebases, not consumers of that C++ core. That makes the written specification the actual contract, instead of "whatever one codebase happens to do." The Android suite decodes a sample file produced by the Apple app and asserts its own re-encoded output matches it byte-for-byte — genuine cross-implementation parity, not just internal self-consistency. That's a rare thing to say about a file format built by one developer.

The honest engineering story: what building on real hardware caught

Reading code doesn't find every bug; running it does. Porting to Linux surfaced problems no amount of review would have caught: the KDE client, downported from Qt 6 / KDE Frameworks 6 to Qt 5 / KDE Frameworks 5 to match a Debian 12 floor, had an illegal -- sequence inside an XML comment in its UI resource file — the kind of thing that aborts the app at startup every time — plus an ECM 5 install-directory variable that needed the exact spelling KXMLGUI5DIR. The GNOME client, downported the same way to GTK 4.8 / libadwaita 1.2, now completes meson setup, ninja, ninja install, and launch on real Debian 12 aarch64 hardware. Verifying the already-shipping Windows client turned up a genuine portability defect it had been carrying quietly: C# raw string literals inherit the source file's line endings, so on a checkout with core.autocrlf=true, the markdown export emitted CRLF line endings. Found, fixed, and pinned by a test.

To be precise about status, because overstating it would undercut the point of this whole project: GNOME and KDE build and run on Debian 12 today. Neither is packaged or released. Haiku is written but has never been compiled — no Haiku machine has been available.

Who this is for — and who it isn't

This is for a project already keeping (or wanting to keep) an in-repo backlog that an agent reads, once that backlog has outgrown a hand-written list — drifting field names, no stable IDs, painful merges. It's for developers who want their issue tracker to live in git history, in plain diffs, with no server and no account. It's for anyone building or extending an agent workflow that needs to read and write issue state deterministically instead of parsing free prose.

It is not a replacement for GitHub Issues, Jira, or Linear for a larger team that needs per-issue permissions, a web UI, notifications, or multi-team workflows. There's no server component and no web client, by design — and no real-time collaborative editing, because .issues is a file, merged the way any other source file is merged.

Availability

I Have Issues is free on macOS (App Store, macOS 14.6+, direct download also available) and free on Windows 11 (direct download, x64 and ARM64), built against the .NET 10 SDK. GNOME and KDE builds run on Debian 12 but aren't packaged for distribution yet. iOS and Android aren't available yet. The format specification itself is free and open for anyone to implement — it's a data format, not software.

FAQ

Why not just use GitHub Issues? GitHub Issues is a hosted product with a web UI, notifications, and per-issue permissions — genuinely useful for teams that need those things. .issues solves a narrower problem: an agent-readable backlog that lives in the repo, works offline, and needs no account. The two aren't mutually exclusive, but .issues doesn't try to replace GitHub Issues.

What happens on a merge conflict? The same thing that happens to any JSON file merged by git: a textual conflict if two branches touched the same or adjacent lines — resolving an issue, for instance, touches resolution, resolutionKind, resolvedAt, and status together, all next to each other once keys are sorted. The schema doesn't make conflicts rarer. It makes them legible: a conflict lands on a named field of an identifiable issue, something you can reason about and resolve deliberately, instead of a conflict buried inside a free-form paragraph.

Is it free? Yes, on every platform it currently ships on — macOS and Windows. The format spec is free and open for anyone to implement. Donations fund development; at $10,000 in total donations, the full source is released under GPLv3. A commercial licence will be available separately for anyone embedding it in a closed-source product without GPLv3's source-disclosure requirement.

Does it sync with GitHub Issues or Azure DevOps? Not currently, on any client. The format reserves fields for it, and integration data written by one client round-trips safely through another — but there's no working sync today. It's planned, not present.

Can I just hand-edit the JSON instead of using an app? Yes. It's a plain UTF-8 JSON file with a documented schema — a text editor or a script works fine. The apps exist for the sidebar-and-detail-panel workflow, not because the format requires them.

And KDE Too.

This morning we have made available the Gnome implementation of the I Have Issues application. Download, unzip, follow the directions in the README to build and install. Bear in mind, this is not a primary use platform for us, so the application does not get quite as much use as the other platforms.

I Have Issues for Gnome ( Linux )

This morning we have made available the Gnome implementation of the I Have Issues application. Download, unzip, follow the directions in the README to build and install. Bear in mind, this is not a primary use platform for us, so the application does not get quite as much use as the other platforms.

Download for Gnome

I Have Issues Published to the General Public

With todays approval by Apple, I Have Issues is now fully in the wild. Mac OS app available via direct download and the Apple App Store, Windows 11 builds for both x64 and arm64 available via direct download. Donations accepted.

This is not a huge application. It really may have little value to anyone but me, but I hope others find it useful. The Issues.md is a fine idea, but in practice too many variations make it difficult to manage. This was my answer. A small, fast, defined, local, repo source control ready issue management tool and file that can be used by any AI Agent, as well as any developer, on any platform, with Gnome, KDE, and Haiku-OS all on the list of codebases that we are working on publishing.

History

v1.0.3 (41) Released

  • Submitted to the Apple App Store on MacOS
  • Direct Download for macOS available today
  • Direct Download for Window available on Thursday