# Issues An issue is a markdown file. Open issues live in `issues/YYYYMMDD-slug.md`. Closed ones live in `issues/closed/` and carry a one-line resolution. That is the whole mechanism. The official word is **issue** — the directory is `issues/`, the tool is `issue.mjs`, the command is `issue new`. People will say "ticket" and everyone will know what they mean; just don't name anything that. ## Why the filesystem A tracker is a second place where truth lives, reachable only by a human with a login. Filesystem issues are in the diff, greppable, editable by anyone working the repo — agents included — and they travel with the branch that fixes them. The cost of filing one is low enough that things actually get filed. The tradeoff is real and worth stating: you lose assignment, cycles, boards, and cross-repository views. **When an issue grows into work that needs those, graduate it** — move it to a real tracker, record the pointer in the file, and close the filesystem copy. Do not track the same live state in two places. ## The header ``` # **Status:** Open **Systems:** **Author:** **Date:** **Resolution:** required only under issues/closed/ ``` Optional: `Severity`, `Related`, `Marker`. Optional all-or-none triage block: `Impact`, `Urgency`, `Ease`, `Confidence`, `Score reviewed`, `Score rationale`. **`Status` is exactly `Open` or exactly `Closed`.** The value includes the rest of the line and any continuation lines, so `Closed (fixed upstream)` is a different status. Put the qualifier in `Resolution`. This is the single most common way the format breaks, and the checker moves your words to the right field rather than rejecting them. Text spanning several lines moves to a body note with its line breaks and indentation preserved; a closed issue still needs a one-line `Resolution`. `Status` belongs in the header, before the first body heading. A quoted `Status` in the body is left untouched, including when the header's `Status` is missing. `--fix` leaves an unrecognized token such as `Open-ended` for the author to correct. ## Commands ```sh bun scripts/issue.mjs new "" --systems "" bun scripts/issue.mjs close issues/.md --resolution "fixed by " bun scripts/issue.mjs check issues/.md bun scripts/issue.mjs check --fix ``` With paths, `check` reads each named issue even before it is staged. Relative paths (including `./issues/…`) and absolute paths must name files under this repository's `issues/` or `issues/closed/`; `issues/README.md` is excluded. Invalid or unreadable paths exit 2, malformed issues exit 1. Without paths, it checks tracked issues. **Do not close an issue with a bare `git mv`.** The closed path requires `Status: Closed` and a non-empty `Resolution:`, and a bare move is the most common way the format check goes red. `--fix` repairs mechanical problems — it moves your own words between fields and never invents a resolution. ## Wiring it up `check` is the enforcement point. Register it inside one of your existing blocking checks rather than adding a sixth — it is fast, deterministic, and reports every malformed issue in one run rather than stopping at the first, which is the behavior every check in this stack is required to have. ## What this is not Not a workflow engine. There are two states. There is no triage queue, no assignee, no sprint, no automation that moves issues between states on your behalf. Those are what a tracker is for, and reaching for one is a signal that this issue has graduated.