Skip to content

Cygnusfear/maitake

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

116 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

maitake

One primitive for your entire decision trail.
Tickets, reviews, PRs, docs β€” all stored as git notes. Nothing leaves your machine unless you say so.

go install github.com/cygnusfear/maitake/cmd/mai@latest

Why

Your tickets are in Jira. Reviews on GitHub. Docs in Notion. ADRs in a wiki. None of it travels with the code.

maitake stores everything β€” tickets, code reviews, pull requests, docs, warnings, decisions β€” as git notes attached to your repo. One format, one CLI, one place.

πŸ€– Agent-native
Agents create tickets, leave file-level findings, open PRs, and update docs through the same CLI. Every command supports --json output. No API keys, no platform auth, no network required. If you can run git, you can run mai.

πŸ•΅οΈ Private by default
Your draft tickets, internal conversations, and PII stay local unless you choose otherwise. Git notes don't push with git push. maitake won't sync anything until you explicitly configure a remote β€” and even then, github.com is blocked out of the box.

πŸ”– One primitive.
A ticket, a review finding, a doc, and a PR are all the same thing: a JSON line in refs/notes/maitake with a kind field. The vocabulary is open. The storage is append-only. Concurrent writes merge without conflicts via set-union.

πŸ’Ό Forge-agnostic.
Sync issues, PRs, docs to anything: GitHub, Forgejo, Gitea, or your own server via hooks. Switch forges without losing a single event. The decision trail of why and how stays with your codebase forever.

πŸ„β€πŸŸ« A substrate for other apps.
maitake is a primitive you want to build on. A kanban board, multi-agent coordinator, a diff viewer, an Obsidian clone β€” all powered by maitake underneath.

30-second tour

mai init                                      # hooks + config (local only)
mai ticket "Fix auth race" -p 1 -l auth --target src/auth.ts
mai start mt-5c4a
mai add-note mt-5c4a --file src/auth.ts --line 42 "Race condition here if not implemented correctly"
mai context src/auth.ts                       # see everything about a file
mai close mt-5c4a -m "Fixed with mutex"

Everything is JSON, append-only, and mergeable. Every event records the current git branch automatically.

How it works

Each ticket, warning, review finding, or comment is one JSON line in a git note. Nothing is mutated β€” state is computed by folding events:

{"id":"mt-5c4a","kind":"ticket","title":"Fix auth race","branch":"main","timestamp":"..."}
{"kind":"event","field":"status","value":"in_progress","branch":"feature/auth","timestamp":"..."}
{"kind":"comment","body":"Found root cause","branch":"feature/auth","timestamp":"..."}
{"kind":"event","field":"status","value":"closed","branch":"main","timestamp":"..."}

Closed from main β€” the branch was merged. The event stream tells the story.

Comparison

maitake tk lat.md mycelium entire.io git-bug git-appraise
Storage git notes .tickets/ files lat.md/ files git notes shadow branch custom git refs git notes
Scope tickets, reviews, PRs, docs, warnings, ADRs tickets knowledge graph open-vocabulary notes session checkpoints issues reviews
Unified primitive βœ“ β€” β€” βœ“ (notes only) β€” β€” β€”
Private by default βœ“ (nothing pushes without config) β€” (files in working tree) β€” (files in working tree) manual setup configurable β€” βœ“
PII / secret scanning built-in hooks, blocked hosts β€” β€” warns only β€” β€” β€”
File-level targeting βœ“ (file + line) β€” @lat: comments βœ“ β€” β€” βœ“
Agent-native CLI βœ“ (JSON) βœ“ (markdown) βœ“ (needs OpenAI key) βœ“ (bash) background capture partial β€”
Doc sync βœ“ (CRDT, Obsidian-compatible) β€” β€” β€” β€” β€” β€”
Event-sourced βœ“ (append-only, set-union merge) β€” (mutable files) β€” (mutable files) β€” (mutable notes) βœ“ βœ“ βœ“
Language Go Bash TypeScript Bash TypeScript Go Go

Commands

Create

Command What
mai ticket [title] [opts] Ticket (task by default)
mai warn <path> [message] Warning on a file
mai review [title] [opts] Code review (open, needs response)
mai artifact [title] [opts] Record/output (born closed β€” ADRs, research, mid-mortems)
mai create [title] [opts] Any kind β€” use -k

Options: -k kind, -t title, --type type, -p priority, -a assignee, -l a,b (tags), --target path, -d description

Pull Requests

Git-native PRs β€” no GitHub, no Forgejo, no platform lock-in. Stored as kind: pr notes.

# Create (from a feature branch)
mai pr "Add auth middleware" --into main   # β†’ mai-5c4a  feature/auth β†’ main
mai pr                                     # list open PRs (auto-closes merged ones)

# Inspect
mai pr show <id>                           # details + diff summary + review verdict
mai pr show <id> --diff                    # include full inline diff
mai pr diff <id>                           # full diff between source and target
mai pr diff <id> --stat                    # summary only

# Review
mai pr accept <id> [-m message]            # LGTM (resolved comment)
mai pr reject <id> -m 'reason'             # request changes (unresolved comment)
mai pr comment <id> -m 'msg'               # general comment
mai pr comment <id> -m 'msg' --file <path> --line N  # inline comment

# Merge
mai pr submit <id>                         # merge source β†’ target, close PR
mai pr submit <id> --force                 # skip unresolved comment check

PRs that are merged outside mai (via git merge, GitHub, etc.) auto-close when listed.

Lifecycle

mai start <id>                                  # β†’ in_progress
mai close <id> [-m message]                     # β†’ closed
mai reopen <id>                                 # β†’ open
mai add-note <id> [text]                        # comment
mai add-note <id> --file <path> [text]          # file-level comment
mai add-note <id> --file <path> --line N [text] # line-level comment
mai tag <id> +tag / -tag                        # add/remove tag
mai assign <id> <name>                          # set assignee
mai dep <id> <dep-id>                           # add dependency
mai undep <id> <dep-id>                         # remove dependency
mai link <id> <id>                              # symmetric link
mai unlink <id> <id>                            # remove link

Query

mai show <id>                   # full state with comments
mai ls                          # open + in_progress (work queue)
mai ls --status=all             # everything
mai ls -k warning               # filter by kind
mai search "auth race"          # BM25 full-text search across all notes
mai search "fix" -k ticket      # search within a kind
mai search "merge" --limit 5    # top N results
mai closed                      # recently closed
mai context <path>              # everything targeting a file
mai ready                       # unblocked work
mai blocked                     # stuck on deps
mai dep tree <id>               # dependency graph
mai kinds                       # all kinds in use
mai doctor                      # graph health

Machine-readable output

mai --json ls                   # JSON array of summaries
mai --json show <id>            # JSON state with events + comments
mai --json search "query"       # JSON array of {id, score, state}
mai --json context <path>       # JSON array of states
mai -C /path/to/repo --json ls  # query a different repo

Setup & sync

mai init [--remote R] [--block H]   # hooks + config + .gitignore
mai sync                            # manual fetch + merge + push
mai migrate [--dir .tickets/] [--dry-run]  # import tk tickets

Setup

mai init                            # local only β€” no remote, no push
mai init --remote forgejo           # enable auto-push to a remote
mai init --remote forgejo --block github.com  # push to forgejo, block github

This creates:

  1. .maitake/hooks/pre-write β€” scans for secrets before every write (gitleaks with regex fallback)
  2. .maitake/config.toml β€” sync remote + blocked hosts
  3. .gitignore entry β€” keeps .maitake/ out of the repo

Without --remote, nothing syncs anywhere. With --remote, every write auto-pushes refs/notes/maitake to that remote (debounced, conflict-safe). github.com is blocked by default even when a remote is configured.

Config

[sync]
remote = "forgejo"
blocked-hosts = ["github.com", "gitlab.com"]

[docs]
sync = "auto"
dir = ".mai-docs"

[hooks]
pre-write = true
post-push = true

Sync

When a remote is configured, every write auto-pushes refs/notes/maitake. On conflict: fetch + set-union merge + retry. Push failures warn but never block.

Manual sync pulls remote changes:

mai sync    # fetch + merge + push

Hooks

Hooks live in .maitake/hooks/ (per-repo) or ~/.maitake/hooks/ (global fallback). Per-repo wins when both exist.

Hook When Receives
pre-write Before every note write JSON note on stdin
post-push After every successful auto-push MAI_REMOTE, MAI_REF, MAI_REPO_PATH env vars

Exit non-zero from pre-write to reject the write. post-push failures warn but don't block.

Example hooks

# Secret scanning (installed by default with mai init)
cp examples/hooks/pre-write-gitleaks .maitake/hooks/pre-write

# Sync to GitHub Issues (requires gh CLI)
cp examples/hooks/post-push-github .maitake/hooks/post-push

# Sync to Forgejo Issues (requires curl + jq)
cp examples/hooks/post-push-forgejo .maitake/hooks/post-push

Global hooks

Set up once for all repos:

mkdir -p ~/.maitake/hooks
cp examples/hooks/pre-write-gitleaks ~/.maitake/hooks/pre-write
cp examples/hooks/post-push-github ~/.maitake/hooks/post-push
chmod +x ~/.maitake/hooks/*

Every repo gets these unless it provides its own.

Attach anything to a file

Tickets, warnings, decisions, and artifacts can target files directly. This is how you stick the why onto the what.

# Warning on a file
mai warn src/auth.ts "Race condition in token refresh"

# ADR explaining a design decision, attached to the file it affects
mai adr "Why topology lives in SpacetimeDB" --target src/physics/rebuild.rs \
  -d "Convergence overhead is lower than mirroring + writeback cost. Revisit at 500+ entities."

# Artifact (born closed) β€” research, analysis, post-mortem
mai artifact "Perf analysis" --target src/physics/rebuild.rs -d "..."

# Ticket targeting multiple files
mai ticket "Auth hardening" --target src/auth.ts --target src/http.ts

Comments within a ticket can also target files and lines:

mai add-note mt-5c4a --file src/auth.ts "Add mutex around token refresh"
mai add-note mt-5c4a --file src/http.ts --line 15 "Missing backoff"

mai context <path> shows everything attached to a file β€” tickets, warnings, decisions, review findings β€” filtered to only that file's comments:

mai context src/auth.ts    # what do we know about this file?

Branch tracking

Every JSON event records the git branch at write time. No flags needed β€” it's automatic.

{"kind":"ticket","title":"Fix auth","branch":"feature/auth","timestamp":"..."}
{"kind":"event","field":"status","value":"closed","branch":"main","timestamp":"..."}

Closed from main tells you the feature branch was merged.

Index cache

The index caches in ~/.maitake/cache/, keyed by the notes ref tip SHA. Cache invalidates automatically on every write. Cold start reads from git; warm start skips all git round-trips.

Artifacts

mai artifact creates notes with type: artifact β€” born closed. They don't appear in mai ls or mai context unless you query with --status=all. Use for ADRs, research results, oracle findings, mid-mortems, and other records that aren't active work.

Reviews (mai review) are open by default β€” they need a response.

Migration from tk

mai migrate --dir .tickets/           # import all tickets
mai migrate --dir .tickets/ --dry-run # preview without writing

Preserves original IDs, timestamps, deps, links, parent refs, Forgejo issue numbers, and comments. Old-format files without YAML frontmatter are skipped.

Privacy

Git notes don't push by default β€” git push ignores them entirely. maitake only pushes to the remote you configure in .maitake/config.toml. Blocked hosts are checked before every push. No remote configured = nothing leaves your machine.

Design

  • Event-sourced β€” immutable JSON lines, state computed by folding
  • Append-only β€” changes via events, never mutation
  • Set-union merge β€” cat | sort | uniq resolves conflicts (inherited from git-appraise)
  • Kind-agnostic β€” tickets, warnings, constraints, decisions, reviews are all notes with different kind fields
  • Full-text search β€” BM25 scoring with field weighting (title 3Γ—, tags 2Γ—, body 1Γ—, comments 0.5Γ—). Combines with kind/status/tag filtering.
  • Performance β€” 10,000 notes: index build <20ms, query <1ms. Cache eliminates git reads on warm start.

References

  • wedow/ticket β€” git-backed ticket tracker (maitake's predecessor used this as starting point)
  • openprose/mycelium β€” git notes substrate for agent communication
  • google/git-appraise β€” code review on git notes (Apache 2.0, repository package adapted)
  • 1st1/lat.md β€” markdown knowledge graph for codebases
  • entire.io β€” AI session checkpoints stored in git

About

πŸ„β€πŸŸ« Git-native tickets, notes, and code review

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors