A lightweight spec for AI agent task queues — the companion to AGENTS.md.
Website · Spec · Examples · MCP Server · Linter
AGENTS.md tells agents how to work. TASKS.md tells them what to work on.
Create a TASKS.md at your repo root:
# Tasks
<!-- policy: Run tests before every commit. Prefer fixing root causes over symptoms. -->
## P0
- [ ] Fix authentication crash on token refresh
- **ID**: auth-fix
- **Details**: JWT refresh returns 500 on expired tokens
- **Files**: `src/auth/refresh.ts`, `src/middleware/auth.ts`
- **Acceptance**: Refresh works, tests pass, regression test added
## P1
- [ ] Add rate limiting to public API endpoints
- **Blocked by**: auth-fix
- [ ] Migrate database queries to prepared statements
## P2
- [ ] Update README with new API endpointsMost tasks are just checkboxes under priority headings. Tasks with dependencies get an ID so blockers can reference them stably. All metadata is optional.
Then add this to your AGENTS.md so agents know to use it:
## Task Management
- Read TASKS.md for available work before asking the user
- Claim tasks by appending (@your-name) before starting work
- Remove completed tasks from the file (history is in git log)That's it. Your agent will read TASKS.md on session start and work through the queue.
Nine commands take a fresh repo from zero to a queue an agent can pick from. Output snippets below are real (tasks here is npx -y @tasks-md/cli — npm install -g @tasks-md/cli once to drop the npx -y prefix).
-
Bootstrap a project.
mkdir my-project && cd my-project && git init -q echo "# README" > README.md && git add README.md && git commit -m "feat: init"
-
Scaffold the queue —
tasks initwritesTASKS.mdand merges a## Task Managementsection intoAGENTS.mdif present.touch AGENTS.md && npx -y @tasks-md/cli initPrints
✓ Created TASKS.mdand✓ Added Task Management section to AGENTS.md. -
Install the
/next-taskcommand for your agent — auto-detects from agent dirs (.claude/,.cursor/,.devin/, etc.). See story 3 → Auto-detect algorithm for the full table.npx -y @tasks-md/cli install
-
Edit
TASKS.md— paste these two tasks under the existing priority headings:## P0 - [ ] Fix the crash on startup - **ID**: fix-startup - **Details**: Service exits 1 when DATABASE_URL is missing. ## P1 - [ ] Add request logging middleware - **Tags**: backend
-
Pick the next task — read-only inspection of what
/next-taskwould claim.npx -y @tasks-md/cli pick
Picked "Fix the crash on startup" (P0) File: TASKS.md:5 ID: fix-startup Details: Service exits 1 when DATABASE_URL is missing. Candidates: 2 -
Validate the queue against the spec.
npx -y @tasks-md/lint TASKS.md # → Checked 1 file(s), found 0 error(s) -
Check queue health.
npx -y @tasks-md/cli stats
📋 Queue Overview P0 P1 P2 P3 Total 1 1 0 0 2 Blocked: 0 Claimed: 0 Available: 2 Files: 1 -
Commit, then look at queue changes since the last commit.
git add TASKS.md && git commit -m "chore: queue 2 items" echo "- [ ] Update README" >> TASKS.md npx -y @tasks-md/cli diff # → ➕ Added (1): Update README
-
Run the autonomous loop —
/next-taskfrom inside your agent picks, claims, works, removes, and repeats. See story 3 for the precise picking algorithm and story 6 when you want issues from GitHub / Jira / Linear feeding the queue automatically.
You think faster than agents can code. Ideas come in bursts — while an agent implements one feature, you've already thought of three more. Without a queue, those ideas live in your head or scatter across chat windows. TASKS.md is your buffer: write tasks down as they come, and agents work through them at their own pace.
Planning first leads to better results. When you write a task down — even a one-liner — you're forced to think about what you actually want before the agent starts coding. That small act of planning is the difference between an agent that builds the right thing and one that guesses. TASKS.md makes planning the natural first step, not an afterthought.
Zero friction beats any tool. Opening Jira to write a task takes you out of flow — you switch context, fill in fields, pick a project, assign a sprint. With TASKS.md, you add a line to a file that's already open in your editor. The lower the friction, the more likely you are to actually write tasks down — and written tasks are the whole point.
One Markdown file that any tool can read and write:
- Zero setup — No accounts, no APIs, no tokens. Create a file and start writing.
- In your editor — Add tasks without leaving your IDE. No browser tab, no context switch.
- Version-controlled — In git, next to the code. Every change is tracked.
- Agent-native — LLMs parse Markdown natively. No API client needed to read a file.
- Vendor-neutral — Works with any agent, any IDE, any CI system, today.
- Offline — Works on a plane. No server required.
- Plan — Write tasks under P0–P3 priority headings as ideas come to you
- Delegate — Agent reads the file, claims a task with
(@agent-name), implements it - Remove — Completed tasks are deleted from the file; history lives in git log
- Repeat — You keep adding tasks while agents keep working through them
You're always adding to the queue; agents are always draining it. No ideas get lost, and agents never run out of work.
The quality of your task description directly affects the quality of the agent's output. A task is a small contract between you and the agent — the more specific you are, the better the result.
A one-liner is fine for obvious work:
- [ ] Add input validation to the /users endpointAdd metadata when the task needs context:
- [ ] Fix race condition in WebSocket reconnect
- **Details**: When the server restarts, clients reconnect but sometimes
miss messages sent during the reconnect window. Add a sequence number
to messages and request missed messages after reconnecting.
- **Files**: `src/ws/client.ts`, `src/ws/server.ts`
- **Acceptance**: No dropped messages during server restart in integration testTips for writing tasks agents can actually complete:
- One session, one task — If it takes you more than a sentence to describe, it might be two tasks
- Include file paths — Agents explore faster when they know where to look
- Define "done" — An Acceptance field turns a vague ask into a testable outcome
- Use IDs for dependencies — If task B depends on task A, give A an ID and add
**Blocked by**: task-ato B. The agent will skip B until A is gone. - Pre-register the metric for non-trivial changes — when a task is a feature, refactor, or non-cosmetic bugfix, write a Hypothesis (what observable will move and by how much), a Success / Pivot threshold, and a Measurement (the exact runnable command). This is the rule-#9 pre-registration block; it prevents picking a flattering metric after seeing the result.
Priority: ## P0 through ## P3 — a widely-used scale (PagerDuty, Google SRE). P0 is "drop everything", P3 is "nice to have".
Tasks: Markdown checkboxes (- [ ]). Each task should be completable in a single agent session.
IDs: **ID**: kebab-case — stable identifiers for tasks that other tasks depend on. Don't rename once assigned.
Blockers: **Blocked by**: auth-fix, rate-limit — references task IDs across all files. A task is unblocked when the referenced IDs no longer exist in any file.
Blocked for a reason: **Blocked**: needs-user-approval — ... — free-form text for blocks that aren't another task. Use it when the agent can't complete the task without an external change (missing approval, refused policy, missing credentials). Any non-empty value marks the task as blocked; the lint keeps the reason field from going empty. Agents running /next-task add this field themselves when they detect an action that is blocked by default (see Refuse forbidden work). See the spec for details.
Research / Last-enriched: **Research**: <notes> + **Last-enriched**: YYYY-MM-DD — agent-managed fields for research notes accumulated while the task is blocked. When /next-task runs on a queue where every task is blocked, it spends the turn adding read-only research (drafted message text, file paths, consumer sketches) to the task's Research field and stamps Last-enriched so the next session knows how fresh the notes are. Enrichment never touches the block itself — only the metadata around it. See Enriching blocked tasks in the spec.
Plan / Parent: **Plan**: + **Parent**: task-id — agent-managed fields for complex-task planning and decomposition. /next-task adds a Plan checklist before coding on multi-file or architectural tasks, and uses Parent when splitting a large task into smaller top-level tasks. Users do not need to add either field manually.
Tags: **Tags**: backend, auth — lowercase labels for filtering and routing to specialized agents.
Estimate / Verification / Risk: **Estimate**: 2-3d (free-form duration), **Verification**: <runnable steps> (procedure for confirming done — distinct from Acceptance, which is the criterion), **Risk**: <what could go wrong>. Mitigation: <how>. — author-managed fields that surface session-fit, the doneness procedure, and the failure mode considered up front.
Rule-#9 pre-registration: **Hypothesis**: + **Success**: + **Pivot**: + **Measurement**: + **Anchor**: — five fields used together to declare what observable a non-trivial change expects to move before the code is written. Hypothesis captures the predicted effect, Success and Pivot are the keep / abandon thresholds, Measurement is the exact runnable command (no English instructions), and Anchor is the literature citation justifying the threshold. Pre-registering the metric prevents post-hoc fishing for flattering observables (Munafò et al. 2017); the Pivot threshold pre-registers the give-up criterion (Ries 2011). Originating implementation: Minsky (vision.md § 9). See Rule-#9 pre-registration block in the spec.
Metadata: Optional nested fields — ID, Tags, Details, Files, Acceptance, Plan, Blocked by, Blocked, Parent, Research, Last-enriched, Estimate, Verification, Risk, Hypothesis, Success, Pivot, Measurement, Anchor. Teams can add custom fields beyond these supported fields.
Sub-tasks: Nested checkboxes under a parent. The agent who claims the parent owns all sub-tasks. Remove the entire block when done. Use sub-tasks when steps are sequential and owned by one agent; promote to separate top-level tasks when steps can be parallelized or span multiple sessions.
Multiple files: One root TASKS.md for small repos. Subdirectory files for monorepos. Split when a file exceeds ~50 tasks.
Policies: Project rules embedded in HTML comments that agents follow when picking and executing tasks. Use <!-- policy: ... --> between # Tasks and the first section for file-wide rules, or after a ## P* heading for section-scoped rules. Policies are invisible in rendered Markdown but readable by agents. See the spec for details.
See the full specification for all rules and edge cases.
- Web application
- CLI tool
- Monorepo
- Multi-agent workflow
- Complex tasks — multiline details, rich acceptance criteria, sub-tasks with metadata
- Python API — FastAPI with SQLAlchemy, pytest, mypy, ruff
- Rust CLI — Cargo project with clippy, assert_cmd, crates.io publishing
- Mobile app — React Native with biometrics, offline sync, Detox E2E
The most useful thing about TASKS.md is a single command: "pick the next task and do it." Install the command for your agent, then type /next-task to start an autonomous work loop or /next-task <task-id> to work one exact task.
Auto-detect your agents and install the command:
npx @tasks-md/cli installOr copy manually into your project (commit it so your team gets it too):
| Agent | Install |
|---|---|
| Claude Code | cp -r commands/claude/skills/next-task .claude/skills/ |
| Codex | cp -r commands/codex/skills/next-task .agents/skills/ |
| Cursor | cp commands/cursor/next-task.md .cursor/commands/ |
| Devin | cp -r commands/devin/skills/next-task .devin/skills/ |
| Gemini CLI | cp commands/gemini/next-task.toml .gemini/commands/ |
| Windsurf | cp commands/windsurf/next-task.md .windsurf/workflows/ |
All paths are project-local (inside your repo). See commands/ for source files and format details.
| Mode | Use when | Example |
|---|---|---|
| Queue pick | You want the agent to drain the highest-priority actionable work | /next-task |
| Targeted task | You know the exact task ID to run or resume | /next-task auth-fix |
| Standing audit loop | You want an audit-only pass that adds follow-up tasks without fixing them immediately | /next-task standing-audit-gap-loop |
The standing audit loop is a standard compact task pattern: give it
**ID**: standing-audit-gap-loop, **Tags**: standing-loop, audit, queue,
and put repo-specific inputs in **Details**: / **Files**:. The agent reads
that brief, audits the repo, adds or refines TASKS.md items, removes the
standing-loop task, commits, and stops. See
Standing audit loops for the full template.
When you type /next-task or /next-task <task-id>, the agent runs this flow:
- Stop check — Runs
scripts/check-zero-ship-streak.mjsif the repo ships it and exits immediately onSTOPoutput. Catches exhausted audit cascades (last 3 commits onorigin/masterwere docs-only with nocloses <task-id>) and fully-blocked queues (100% of tasks marked with non-empty**Blocked**metadata) before wasting a session on busywork - Snapshot — Reads git status, current branch, and TASKS.md in one shot to orient without redundant tool calls
- Preserve — If the worktree is dirty, keeps existing edits in place, avoids them when possible, and stages only its own hunks when it must touch a shared file
- Tidy — Merges ready PRs, closes stale ones, deletes merged branches, pulls main
- Find — Discovers all
TASKS.mdfiles from the git root down - Policies — Reads
<!-- policy: ... -->comments from the file and follows them as project rules throughout the session - Target (optional) — If a task ID follows the command, trims it and searches for an exact
**ID**:match. Missing, duplicate, claimed-by-another-agent, and blocked targets are reported and stop the run; actionable targets bypass priority ordering but still go through policies, safety checks, verification, and task-block removal. After shipping a targeted task, includingstanding-audit-gap-loop, the agent stops instead of draining unrelated queue items. - Resume — Checks for a previously claimed task (
(@agent-id)) and picks up where it left off - Pick — Without a target ID, selects the highest-priority unblocked, unclaimed task. Skips tasks with
**Blocked by**:whose dependencies aren't resolved and tasks with a non-empty**Blocked**:reason. Prefers tasks that unblock others (impact-first) and harder tasks over simpler ones - Refuse forbidden work — Before claiming, checks whether the task requires a blocked-by-default action (posting in Slack / Teams / Discord, creating or commenting on Jira or GitHub issues, publishing packages, sending emails, pushing to protected branches, etc.). If so, adds
**Blocked**: <reason>to the task with a short code likeneeds-user-approvaland moves on. In targeted mode, it stops after committing the block. Opening pull requests withgh pr create, reading dashboards, and local-only actions stay allowed by default. - Enrich blocked tasks — When every remaining task is blocked and none has been enriched in the last 7 days, spends the turn on read-only research. Reads the task's
**Files**:, greps the codebase for related terms, drafts the exact Slack/Jira/PR-review text when applicable, and appends findings to the task's**Research**:field (plus**Files**:/**Acceptance**:when warranted). Stamps**Last-enriched**: YYYY-MM-DDso future sessions can tell how fresh the notes are. Never touches**Blocked**:or**Blocked by**:— enrichment leaves context behind, it doesn't unblock. - Plan — For complex tasks (multi-file, architectural, > 1 hour), explores the code and writes a
**Plan**:sub-task checklist into the task block before touching any code - Claim — Appends
(@agent-id)to the task line so other agents skip it - Work — Reads the task's metadata, checks AGENTS.md for project conventions, makes changes, runs tests
- Scout — While working, actively looks for bugs, missing tests, stale docs, and other gaps in code it touches — records them as new tasks in TASKS.md so the queue grows smarter with every completed task
- Complete — Removes the entire task block from TASKS.md, commits, pushes
- Loop — In queue mode, returns to step 5 and picks the next task until the queue is empty
- Roam — When the current repo's queue is empty and every blocked task is freshly enriched, scans
~/apps/*/TASKS.mdfor work in other repos and switches automatically - Audit — When ALL repos are empty, runs a 5-tier cascade on the current repo:
- Tier 1: Verify (typecheck, lint, test, build)
- Tier 2: Security & dead code
- Tier 3: Doc drift & stale references
- Tier 4: Dependency modernization (universal — works for any repo type)
- Tier 5: DX polish (help text, error messages, onboarding friction)
- Writes findings as tasks and implements the first one — re-runs on each invocation
- Terminal — When all repos are clean across all 5 tiers, prints a summary and stops the loop cleanly
You Agent
────────────────── ──────────────────
Write tasks as ideas come → /next-task or /next-task <task-id>
Add more tasks → Claims P0 task, starts working
Add more tasks → Completes task, picks next one
Review agent's commits ← Commits, removes task, loops
Add more tasks → ...keeps draining the queue
You're always adding to the queue. The agent is always draining it. This is the core loop — planning is your job, execution is the agent's.
For unsupervised runs longer than a single coding session, the
/next-task loop alone isn't enough. Without guardrails, agents
eventually find micro-doc-drift to "fix" once the real queue is
exhausted, generate single-finding PRs, exceed admin-merge volume on
shared branches, and ignore orchestrator stop signals.
taskgrind/ provides a canonical rule set + 4
enforcement scripts that prevent these failure modes:
| File | What it does |
|---|---|
prompt-template.md |
10 hard rules — copy to your repo's taskgrind.md, fill in placeholders |
scripts/check-zero-ship-streak.mjs |
Pre-flight STOP/CONTINUE check — already wired into the next-task skill |
scripts/check-admin-merge-rate.mjs |
Counts admin self-merges in trailing 24h, exits non-zero at ≥5 |
scripts/safe-admin-merge.sh |
Wrapper around gh pr merge --admin that runs the rate check first |
scripts/lint-pr-shape.mjs |
CI gate — refuses single-finding doc-only PRs without closes <task-id> |
See taskgrind/README.md for adoption options
(copy / symlink / future npx) and the lessons that motivated each
rule.
The @tasks-md/cli provides task queue management — pick tasks, lint files, sync from issue trackers, and install agent commands.
| Command | What it does |
|---|---|
tasks init |
Scaffold TASKS.md + AGENTS.md in the current repo |
tasks install |
Install /next-task for detected agents (Claude Code, Cursor, Devin, etc.) |
tasks pick |
Pick the highest-priority unblocked, unclaimed, non-standing-loop task (--json for scripts) |
tasks list |
List every task matching filters — CLI counterpart of MCP list_tasks (--json for scripts) |
tasks watch |
Watch TASKS.md files and auto-lint on save (--fix auto-corrects on save) |
tasks stats |
Show queue overview and throughput from git history (--json for scripts) |
tasks diff |
Show queue changes since a git ref (--json for scripts) |
tasks sync <provider> |
Sync issues from github, jira, or linear |
tasks generate-commands |
Regenerate agent command variants from canonical sources |
Quick examples:
npx @tasks-md/cli pick # pick highest-priority unblocked non-standing task
npx @tasks-md/cli list --unclaimed --unblocked # list every pickable task
npx @tasks-md/cli stats # queue overview and throughput
npx @tasks-md/lint TASKS.md # validate against spec (separate package)
npx @tasks-md/cli sync github --merge # sync GitHub Issues into TASKS.mdRun npx @tasks-md/cli <command> --help for full options on any command.
The tasks-mcp server lets any MCP-compatible agent manage TASKS.md files programmatically — list, pick, target exact IDs, claim, unclaim, complete, and add tasks without file parsing.
Use pick_task for both queue mode and targeted mode. With no task_id, it walks P0→P3 and returns the best unblocked, unclaimed task using the same auto-pick rules as the CLI, including skipping standing-loop tasks. With task_id, it bypasses queue ordering, looks for one exact **ID**, and returns a structured status for missing, duplicate, already_claimed, blocked, ready, resumed, or claimed. Pass agent_name to claim an actionable target or resume a target already claimed by that same agent. This composes with /next-task <task-id> and standing loops like standing-audit-gap-loop without custom file parsing.
{
"mcpServers": {
"tasks": {
"command": "npx",
"args": ["tasks-mcp"]
}
}
}The @tasks-md/lint CLI validates TASKS.md files against the spec — checks structure, priority ordering, ID format, duplicate IDs, dangling blocker references, and tag casing. Directory targets include direct .md files and nested TASKS.md files for monorepos.
npx @tasks-md/lint TASKS.md # lint one file
npx @tasks-md/lint TASKS.md examples/ # lint multiple files/directories
npx @tasks-md/lint --fix TASKS.md # auto-fix (removes completed tasks)Add one line to your CI workflow to validate TASKS.md on every push:
- uses: tasksmd/tasks.md/.github/actions/lint@mainSee .github/actions/lint/ for options.
They solve a different problem. Issue trackers are for team coordination — prioritizing features, tracking sprints, assigning across people, reporting to stakeholders. TASKS.md is for agent execution — a local, fast, file-based queue that agents read and write without API calls.
Key differences:
| Issue trackers | TASKS.md | |
|---|---|---|
| Audience | Product managers, teams | Agents, solo devs |
| Granularity | Features, bugs, epics | Implementation steps |
| Access | API calls, auth tokens | Read a file |
| Speed | Browser/API round-trip | Edit a line in your editor |
| Works offline | No | Yes |
| Agent can write | Needs API client + auth | Append to a file |
| Git-native | Separate system | Same repo, same PR |
They complement each other — one Jira ticket or GitHub Issue often becomes multiple TASKS.md entries. Use the tracker for what to build; use TASKS.md for how the agent builds it.
Absolutely — that's the expected setup for teams. The issue tracker is your source of truth for product work. When you pick up an issue, break it into implementation steps in TASKS.md and let the agent execute them. The agent doesn't need access to your tracker; it just needs the file.
## P1
- [ ] Implement user profile page (PROJ-142)
- **Details**: See Jira PROJ-142 for designs. Build the profile
page with avatar, bio, and settings link.
- **Files**: `src/pages/profile.tsx`, `src/api/user.ts`
- **Acceptance**: Page renders, matches Figma, tests passTODO.md has no spec and thousands of incompatible formats in the wild. A "todo list" is a human wish list; a "task queue" is an active work queue for agents. The naming fits the emerging pattern: AGENTS.md (instructions), TASKS.md (work queue).
Migration: mv TODO.md TASKS.md, add P0–P3 headings, convert to checkboxes.
No. A solo developer with one agent benefits from persistent context across sessions. You write tasks, the agent works through them. An orchestrator helps when you have multiple agents, but it's not required. TASKS.md is intentionally simple enough that any agent can use it without special tooling.
Each agent claims a unique task (different line). Git auto-merges deletions on non-adjacent lines. Conflicts are rare and trivial.
As detailed as needed for the agent to succeed without asking you. A one-liner works for obvious changes (Add input validation to /users). For anything ambiguous, add Details, Files, and Acceptance so the agent knows what to do, where to look, and when it's done.
Yes. It works as a personal backlog for any developer. The format is just prioritized Markdown checkboxes — you don't need an agent to benefit from writing tasks down before starting work. The planning habit alone improves outcomes.
Default to sub-tasks — nested checkboxes that one agent works through sequentially. Sub-tasks keep context (Details, Acceptance) in one place and show progress without cluttering the queue.
Promote to separate top-level tasks when steps can be parallelized, span multiple sessions, or each produce a shippable artifact on their own. Use **Blocked by**: to express the dependency:
- [ ] Set up auth database schema
- **ID**: auth-schema
- [ ] Implement JWT token refresh
- **Blocked by**: auth-schemaDecision rule: can one agent finish everything in a single session? Use sub-tasks. Does any step need a different agent or could it ship alone? Separate tasks.
The agent should tell you it's stuck and move on to the next task. The stuck task stays in the queue with its (@agent-id) claim. You can either add more detail to help the next attempt, or remove the claim so another agent (or a fresh session) can try.
Yes — that's what the claiming mechanism is for. Each agent appends (@agent-id) to the task it picks up. Other agents see the claim and skip to the next unclaimed task. In multi-agent setups, agents should commit and push claims immediately to avoid races.
No. Remove them. Git log is your history. Keeping completed tasks in the file adds noise and makes it harder for agents to scan the queue. The spec enforces this — @tasks-md/lint will flag checked-off tasks as errors.
They're companions. AGENTS.md tells agents how your project works (build commands, conventions, architecture). TASKS.md tells agents what to work on (prioritized queue). Together, an agent can start a session, read both files, and be immediately productive — no human prompting needed.
- Why Your AI Agent Needs a Backlog — the motivation behind TASKS.md
- AGENTS.md — the companion spec for agent instructions
- Proposal: TASKS.md as a companion standard — discussion on the agents.md repo
All four npm packages share a single version and are published together:
| Package | npm |
|---|---|
@tasks-md/parser |
|
@tasks-md/lint |
|
@tasks-md/cli |
|
tasks-mcp |
- Go to GitHub Releases → New
- Create a new tag with a
vprefix (e.g.v0.3.1) targetingmain - Add release notes (GitHub can auto-generate them)
- Click Publish release
Or from the CLI:
gh release create v0.3.1 --generate-notesThe publish workflow runs automatically and:
- Syncs all
package.jsonversions to match the tag - Builds and runs the full test suite
- Publishes all 4 packages to npm in dependency order
- Commits the version bump back to
main
The workflow requires an NPM_TOKEN secret:
- Create an npm Automation token at npmjs.com/settings → Access Tokens (automation tokens bypass 2FA/OTP)
- Add it as a repository secret named
NPM_TOKENat Settings → Secrets → Actions
If you need to publish without a GitHub Release (e.g. from a local machine):
npm adduser # authenticate once
scripts/sync-versions.sh 0.3.1 # bump all package versions
scripts/publish-all.sh # publish in dependency order (requires OTP)We track work in our own TASKS.md. Contributions welcome — see CONTRIBUTING.md:
- Improve the specification
- Add examples for your stack
- Add or improve agent commands for your tool
- Report bugs or suggest features via GitHub Issues
This project follows the Contributor Covenant.
This is an independent personal open-source project by Fyodor Ivanischev. It is not affiliated with, endorsed by, sponsored by, or otherwise connected to any current or former employer of the author. Any opinions, designs, or decisions expressed here are the author's own. The codebase contains no proprietary material from any employer.