Skip to content

GiantRavens/punchlist

Repository files navigation

  o
+--\----+
|   \   |
| punch |
|  list |
+-------+

punchlist

A lightweight, local, markdown-native task and ticket system for humans and AI agents — no database, no app, no account required.

Why punchlist?

Most task tools are apps: databases behind a UI, locked to a service, requiring context-switches away from your work. GitHub Issues are powerful but heavy — and don't travel with your local project. Plain text files are too loose to query or automate.

Punchlist sits in the middle: every task is a plain markdown file with YAML frontmatter, committed alongside your code, editable in any text editor, queryable with --json, and legible to both humans and AI agents without translation.

  • No database. Tasks are .md files in a tasks/ folder.
  • No app. Open any task in nvim, VS Code, Obsidian, or anything else.
  • No lock-in. Plain markdown and YAML. If you stop using pin, your files are still there.
  • Travels with your project. Commit your tasks alongside your code.
  • Works with your AI assistant. JSON output, structured metadata, dependency tracking, and acceptance criteria make punchlist a first-class context layer for AI agents and coding tools.

Quick start

pin init                              # initialize any folder as a punchlist project
pin "write release plan" pri:1        # create a task (defaults to TODO)
pin ls                                # list all tasks
pin browse                            # interactive TUI browser

Creating tasks

The pin command grammar is natural and tolerant. State is optional; if omitted, the task defaults to TODO.

pin "write outline"
pin todo "draft messaging brief" pri:1
pin todo "send pr draft" by:2026-01-15
pin todo "ship notes" by:tomorrow
pin todo "review plan" by:friday
pin todo "write release plan" pri:1 by:2026-01-09 tags:{launch,pr}
pin todo "Deploy to prod" depends:3,4 pri:1
pin done "shipped quick fix for onboarding typo"
pin begun "triage the backlog"
pin block "waiting on vendor response"
pin todo ../work "queue follow-up"    # create in another project folder

Each task gets a numbered markdown file (tasks/001-write-outline.md) with YAML frontmatter and a body you can freely edit.

Scope: Each initialized folder has its own independent task list. Use an explicit path to target a different project:

pin ls ../work                        # list tasks in another project
pin todo ../work "queue follow-up"    # create a task in a specific project

pin walks up from the current directory to find the nearest .punchlist/ folder, so it's usually correct — but worth checking when working across sibling projects.


Listing, searching, and inspecting tasks

pin ls                        # all tasks, grouped by state
pin ls todo                   # filter by state
pin ls todo --tag launch      # filter by tag
pin ls --order id             # sort by id instead of state
pin ls todo --chunk 10        # show first 10 matches
pin ls todo --chunk 10 --page 2
pin ls todo --by-priority
pin ls todo --by-date-reverse
pin search "release"          # full-text search across title, body, frontmatter
pin show 12                   # full task detail

Machine-readable JSON output

All read commands support --json for use in scripts and AI pipelines:

pin ls --json
pin ls todo --json
pin show --json 12
pin search --json "keyword"
pin ls --ready --json | jq '.[0].id'    # next actionable task

pin ls --json returns a task array without body. pin show --json includes the full body, metadata, and acceptance criteria.


Task states

States are fully configurable. Default states from pin init:

State Aliases Browse hotkey
TODO todo t
BEGUN begun, started, inprogress b
FOLLOWUP followup, confirm f
DEFER defer l
NOTDO notdo x
DONE done, complete, completed d

Change state with any state token:

pin start 12
pin done 12
pin block 12
pin todo 12
pin REDLIGHT 12           # any custom state token works
pin begun "triage the backlog"    # state + new task title creates a task in that state

Add or rename states by editing states: in .punchlist/config.yaml. All commands respect the configured ordering.


Browse

pin browse
pin browse todo

pin browse opens a keyboard-driven TUI viewer. Move between tasks with /, K/J, or space; scroll long task bodies with /, pgup/pgdn, home, and end; update state with hotkeys, add notes with n, open in your editor with e, set priority with 1–9/0, quit with q. When a state hotkey is used in browse, the state is applied and the cursor advances to the next task.


Task details

pin note 12 "call vendor and confirm timeline"    # append a note
pin tag 12 15 "today, blocked"                    # add tags to one or more tasks
pin due 12 "next tuesday"                         # set or change due date
pin due 12 2026-01-15
pin edit 12                                       # open in $EDITOR

Multiple task selection

pin done 2 3 6-9
pin del "[2-3, 7]"     # quote brackets in zsh to avoid glob expansion

AI agent workflow

Punchlist is designed as a first-class AI agent substrate: structured enough to be queried and automated, simple enough to edit by hand.

Task metadata — provenance and context

Capture where a task came from, who assigned it, and to whom:

pin meta 1 source=standup-2026-02-27 from=alice to=bob
pin meta 1                      # display all metadata
pin meta 1 from=                # delete a key (empty value)
pin show --json 1 | jq .meta   # read in JSON

Metadata lives in meta: YAML frontmatter — invisible to human users who don't need it, fully accessible to agents that do.

Acceptance criteria

Structured checkboxes from a ## Acceptance section in the task body:

pin acceptance 1       # list criteria with indices (alias: pin checks)
pin check 1 2          # toggle item 2 checked/unchecked
pin show --json 1 | jq .acceptance    # structured array: text, checked, index

Dependencies and planning

pin "Deploy" depends:1,2 pri:1         # create task with dependencies
pin deps 5                              # forward deps + reverse lookup
pin ls --ready                          # only tasks whose deps are all DONE
pin ls --ready --json | jq '.[0].id'   # pick next actionable task

Suggested task template

---
state: todo
pri: 2
by: 2026-02-01
tags: [ai, assistant]
depends_on: [3, 4]
meta:
  source: standup-2026-02-27
  from: alice
  to: bob
---

# <short, testable outcome>

## Goal
<one sentence>

## Acceptance
- [ ] <verifiable result>
- [ ] <test or command to run>

## Context
- Files: <paths>
- Links: <issues/docs>

## Notes for assistant
- Constraints: <style/tech>
- Assumptions: <if any>

Workflow tips

  • Keep tasks small and specific — one outcome per ticket.
  • Include concrete acceptance checks; use pin check to toggle them as you go.
  • Use pin meta to capture provenance: source meeting, who assigned it, and to whom.
  • Use depends: and pin ls --ready so agents always pick the right next task.
  • Treat pin note as a running log — decisions, commands run, blockers hit.
  • See AGENTS.md for machine-facing guidance and docs/assistant-brief.md for an extended agent workflow guide.

MCP server — structured AI access

The punchlist MCP server gives AI assistants (Claude Desktop, Claude Code, Cursor, or any MCP-compatible client) direct, structured access to your tasks. Instead of grepping markdown files and burning tokens on raw text, the AI navigates typed data: domains, configs, filtered queries, metadata-only listings.

The MCP server is a standalone Python process that reads and writes the same files pin does. They coexist — use pin from the terminal, use MCP from your AI tools, edit tasks in Obsidian or nvim. The filesystem is the shared contract.

Why use it?

Without MCP With MCP
AI greps 600 task files to find blocked items punchlist_list(state="BLOCK") — metadata only, ~500 tokens
AI guesses at file naming and config format punchlist_create(title="...") — correct by construction
AI reads entire task bodies to answer summary questions punchlist_summary — counts by state, priority, tags
Context window fills with raw markdown Metadata-first: bodies only on explicit punchlist_get

Setup

cd mcp/
python3 -m venv .venv
source .venv/bin/activate
pip install -e .

Configure for Claude Code

Add to your project's .mcp.json or ~/.claude.json:

{
  "mcpServers": {
    "punchlist": {
      "command": "/path/to/punchlist/mcp/.venv/bin/python3",
      "args": ["/path/to/punchlist/mcp/server.py", "--root", "/path/to/workspace"]
    }
  }
}

Configure for Claude Desktop

Same JSON format, placed in:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Linux: ~/.config/Claude/claude_desktop_config.json

Available tools

Tool Purpose Token cost
punchlist_discover List all domains, states, task counts Very low
punchlist_list Filtered metadata (no body) — state, tag, priority, search, date Low
punchlist_get Full task with body, notes, log Medium
punchlist_search Full-text across titles and bodies Medium
punchlist_create Create task — auto-ID, correct filename, config update Low
punchlist_update Change state/priority/tags, add notes — auto-logged Low
punchlist_summary Dashboard: counts by state, priority, top tags Medium
punchlist_cross_domain Query across all domains at once Medium–High

Multi-domain workspaces

Point --root at a parent directory and the server discovers all nested punchlist domains automatically:

workspace/
  .punchlist/config.yaml    ← root config (shared defaults)
  home/
    .punchlist/config.yaml  ← domain: "home"
    tasks/
  work/
    .punchlist/config.yaml  ← domain: "work"
    quantum/
      .punchlist/config.yaml ← domain: "work/quantum" (nested)
      tasks/

Each domain has its own states, ID counter, and task set. The root config provides fallback defaults.

Works alongside Obsidian

Task files are plain markdown with YAML frontmatter — Obsidian reads them natively. Point an Obsidian vault at your workspace, edit tasks in Obsidian's editor, and the MCP server sees changes immediately (no caching, no sync). The MCP server adds structured query and mutation that Obsidian doesn't have; Obsidian adds rich editing and linking that the MCP doesn't need.

Typical AI workflow

1. punchlist_discover        → orient: what domains, what states
2. punchlist_summary         → lay of the land: counts, recent activity
3. punchlist_list(state=...) → filtered metadata
4. punchlist_get(id=...)     → drill into one task
5. punchlist_update(id=...)  → change state, add notes

For implementation details, see mcp/PLAN.md (architecture) and mcp/CLAUDE.md (setup guide).


Configuration

.punchlist/config.yaml supports:

  • states: list of state definitions with name, aliases, and tui_hotkey
  • ls_state_order: custom state ordering for pin ls
  • next_id: next task id (auto-managed)
  • id_width: zero padding width for filenames (default 3)
  • title_max_len: max stored title length before truncation (default 80)
  • ls_title_max_len: max title length shown in pin ls (default 80)
  • edit_start_insert: add +startinsert for vim/nvim (default true)
  • edit_goyo: add +Goyo for vim/nvim (default false)
  • browse_margin: columns of left/right margin in pin browse (default 12)
pin config          # open config in $EDITOR
pin config migrate  # backfill new config fields without overwriting yours

State config rules: name and aliases must be single-word tokens; tui_hotkey must be a single character and cannot conflict with reserved keys (n, q, j, k, J, K, space, e, 0–9).


Data layout

your-project/
  .punchlist/
    config.yaml       # project config
  tasks/
    001-write-outline.md
    002-review-plan.md
    ...
  .trash/             # deleted tasks land here (not permanently removed)

The punchlist source tree includes:

punchlist/
  cmd/                # Go CLI commands
  config/             # config loading and state catalog
  task/               # task parsing and serialization
  mcp/                # MCP server (Python, optional)
    server.py         # MCP server implementation
    pyproject.toml    # Python package config
    .venv/            # Python virtual environment (after setup)
  docs/               # extended documentation
  AGENTS.md           # machine-facing agent instructions

Tasks are plain markdown files with YAML frontmatter. Config is plain YAML. Nothing is hidden, encoded, or proprietary.


Shell integration (optional)

Get notified when you cd into a punchlist-enabled folder:

# find nearest parent with .punchlist
_punchlist_root() {
  local d="$PWD"
  while [[ "$d" != "/" ]]; do
    [[ -d "$d/.punchlist" ]] && { print -r -- "$d"; return 0 }
    d="${d:h}"
  done
  return 1
}

_punchlist_task_count() {
  local root tasks_dir
  root="$(_punchlist_root)" || return 1
  if [[ -d "$root/tasks" ]]; then
    tasks_dir="$root/tasks"
  elif [[ -d "$root/.punchlist/tasks" ]]; then
    tasks_dir="$root/.punchlist/tasks"
  else
    return 1
  fi
  local -a files
  files=("$tasks_dir"/*.md(N))
  print -r -- "${#files[@]}"
}

typeset -g _PUNCHLIST_LAST_COUNT=""
_punchlist_maybe_notice() {
  [[ -o interactive ]] || return 0
  local count
  count="$(_punchlist_task_count)" || { _PUNCHLIST_LAST_COUNT=""; return 0 }
  if [[ "$count" != "$_PUNCHLIST_LAST_COUNT" ]]; then
    local plural=""
    (( count != 1 )) && plural="s"
    print -r -- "${count} task${plural}. Use \`pin ls\` to review."
    _PUNCHLIST_LAST_COUNT="$count"
  fi
}

Development

make check    # go test ./... && go vet ./...
make build    # builds ./pin with version from VERSION file
make install  # go install with version ldflags

For command grammar details, see docs/grammar.md.


Project

Punchlist is open source software.

About

Punchlist is a markdown-native task system for humans and AI agents. Every task is a plain markdown file with YAML frontmatter — readable in nvim or Obsidian, and machine-queryable with --json, --ready, pin meta, and acceptance criteria via pin check.

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors