Issues and review comments as files, in any git repository, about anything in it.
An issue is a Markdown file; its directory is its status. A comment is a file nobody edits. No server, no account, nothing installed, and any agent can read the board cold.
# any harness that reads the Agent Skills convention
DIR=.claude/skills/oif # or .agents/skills/oif, .cursor/skills/oif
mkdir -p "$DIR" && curl -fsSL https://oif.md/skill.md -o "$DIR/SKILL.md"
Nothing executes. It is a Markdown file. To check a board against
the specification, pip install oifmd then
oifmd validate <board>.
The rest of this page is the skill in full. An agent handed only this URL can create a board, file an issue, move it, comment and close it without fetching anything else.
ls, cat and git mvAn OIF board is a directory of Markdown files. The directory an issue sits in is its status. The filename carries its identity. A record may be *about* anything in the repository. Spec: https://oif.md/SPEC.md
A board is any directory holding board.md and issues/. Look for one at the repository root, then one level down, then under board/ or .oif/. If there is none and you have been asked to start one, see "Start a board" below.
Before acting on a file, check what the board already says about it:
grep -rl -- 'path/to/file.md' <board>/
Paths in about are relative to the repository root, not to the board.
Someone may have filed that it is wrong, stale, or already being fixed.
<board>/
├── board.md columns in order, optional kinds, optional key
└── issues/
├── backlog/column.md
├── doing/column.md
└── done/column.md columns marked complete: true in board.md
Read board.md first. Its columns list is authoritative. If it has a kinds list, every issue's kind must be one of those names and a child issue's kind must be in its parent's contains. Every column directory contains a column.md that says what belongs there and the exit criteria. Read it before moving an issue in. column.md, index.md and log.md are never issues.
If there is no board.md, write these three files. Adjust the columns to suit; the order in board.md is the order of the board.
<board>/board.md:
---
type: board
oif: "0.1"
key: app
title: My board
columns:
- name: backlog
- name: todo
- name: doing
wip: 3
- name: done
complete: true
---
What this board is for, and how the team works.
<board>/issues/<column>/column.md, one per column:
---
type: column
title: Doing
description: Work someone has picked up and is actively on.
---
Exit: acceptance criteria all checked and the change merged.
Every declared column needs a directory with a column.md in it. Git does not track empty directories, so that file is what keeps an empty column on the board.
Optionally add kinds to board.md to declare the vocabulary for an issue's kind, and which kinds may contain which:
kinds:
- name: epic
contains: [story]
- name: story
contains: [task, bug]
- name: task
- name: bug
issues/<column>/<slug>-<id>.md
<id> is six characters from 0123456789abcdefghjkmnpqrstvwxyz(no i, l, o, u), random, never changed.
<slug> is lowercase words joined by -, derived from the title.Mint an id with any of:
LC_ALL=C tr -dc '0-9a-hjkmnp-tv-z' < /dev/urandom | head -c 6; echo
python3 -c "import secrets;print(''.join(secrets.choice('0123456789abcdefghjkmnpqrstvwxyz') for _ in range(6)))"
Never number issues sequentially. Never put id, status, state or column in frontmatter.
---
type: issue
resource: oif:<board key>/<id>
title: Short title
kind: task
priority: medium
assignees: [coder/1.4]
requested_by: human:sam
tags: []
depends_on: []
created: 2026-09-13T03:10:00Z
---
Description as free Markdown.
## Acceptance Criteria
- [ ] Checkable statement
A comment is a separate file, comments/<issue-id>/<comment-id>.md:
---
type: comment
at: 2026-09-13T03:40:00Z
by: coder/1.4
---
Comment text.
Actors: human:<id>, <agent>/<version>, process:<id>.
Read. cat issues/*/*-<id>.md comments/<id>/*.md gets the issue and its full history in one command.
Point at something. Add about to an issue or comment to say what it concerns. Each entry needs path or resource; add commit so the reference survives the target being renamed, since git log --follow resolves the new name from that revision.
about:
- path: docs/orders.md
commit: 3f9c2e1
Never write to the target, and never put a file beside it. Everything you write goes under the board root. That is what lets a board describe a repository nobody on the board owns.
Create. Mint an id, write the file into the target column directory with type: issue, title and created set. If board.md has a key, set resource: oif:<key>/<id>. Keep every other key optional.
Move. git mv issues/<from>/<file> issues/<to>/. Nothing inside the file changes.
Remark. When there is something worth recording but no work to do and no state to move through — "I checked this and it holds", "careful, this is more settled than it reads", "this is the good one" — write a standalone comment at comments/<new-id>.md with an about key. Do not file an issue that is born complete; the column would carry nothing. Conventional kind values are confirms, disputes, caution and note.
Comment. Write a new file at comments/<issue-id>/<new-id>.md, minting the comment id the same way as an issue id. Frontmatter needs type: comment, at (ISO 8601 with an offset) and by (an actor); add any other keys you need. The body is the comment text. Never edit or delete an existing comment file; a correction is a new comment.
Only if board.md says comments: inline: append to the end of the issue file instead, under a final ## Comments level-2 section, each comment a ### <ISO-8601 timestamp> <actor> heading optionally followed by key=value pairs with no spaces in values.
Do not set merge=union on issue files. When two branches each append a comment inline, git collapses lines the two bodies happen to share and one comment's body is lost with no conflict shown.
Edit. Change frontmatter or body as needed. Preserve keys you do not understand. Preserve existing comments verbatim.
Close. Move to a column marked complete: true. Optionally set resolution (fixed, duplicate, wontfix, ...).
Delete. Not an operation. Move to a hidden complete column such as archived if the board has one.
Refer to another issue by its id (7k2x9m) inside the board, or <key>-<id> (app-7k2x9m) across boards. Find it with ls issues/*/*-7k2x9m.md. Never link by path; paths change on move.
<slug>-<id>.md with a six-character idboard.md columnskind is declared in board.md kinds, when that list existstype: issue and no id, status, state or columnresource, if present, ends with the filename's id## Comments, if present, is the last level-2 sectiondepends_on, parent and related id exists on the boardIf oifmd is installed, oifmd validate <board> runs all of these.