frame 0.1.5

A markdown task tracker with a terminal UI for humans and a CLI for agents
Documentation
# Markdown File Format

Frame uses plain markdown files as its data store. This document specifies the exact syntax recognized by the parser.

## Track Files

Each track is a single `.md` file with this structure:

```markdown
# Track Title

> Optional description line.

## Backlog

- [ ] `ID-001` Task title #tag
  - added: 2025-05-10
- [>] `ID-002` Active task

## Parked

- [~] `ID-010` Parked task

## Done

- [x] `ID-003` Completed task
  - resolved: 2025-05-14
```

### Sections

Three section headers are recognized (case-insensitive):

- `## Backlog` — todo, active, and blocked tasks
- `## Parked` — intentionally paused tasks
- `## Done` — completed tasks

Any content that isn't a section header or task is treated as literal passthrough text and preserved verbatim.

### Title and Description

The first `# Title` line sets the track's display title. An optional `> Description` line can follow.

## Task Syntax

A task line has this format:

```
INDENT- [STATE] `ID` Title text #tag1 #tag2
```

**Indentation**: 0 spaces for top-level, 2 for subtasks, 4 for sub-subtasks (3 nesting levels max).

**Checkbox states**:

| Char | State |
|------|-------|
| ` ` (space) | Todo |
| `>` | Active |
| `-` | Blocked |
| `x` | Done |
| `~` | Parked |

**ID** (optional): Enclosed in backticks after the checkbox. Format: `PREFIX-NNN` for top-level, `PREFIX-NNN.N` for subtasks, `PREFIX-NNN.N.N` for sub-subtasks.

**Tags** (optional): `#word` tokens at the end of the line. Parsed right-to-left from the end; only trailing `#word` sequences are recognized as tags.

Examples:

```markdown
- [ ] Task with no ID
- [>] `EFF-014` Implement inference #cc
  - [ ] `EFF-014.1` First subtask
    - [ ] `EFF-014.1.1` Deep subtask
```

## Metadata Lines

Metadata lines are indented under their task (task indent + 2 spaces) and start with `- key: value`:

```markdown
- [>] `EFF-014` Task title
  - added: 2025-05-10
  - dep: EFF-003, INFRA-007
  - ref: doc/design.md, src/parser.rs
  - spec: doc/spec.md#section
  - note: Short note text
```

### Metadata Types

**`added: YYYY-MM-DD`** — Creation date.

**`resolved: YYYY-MM-DD`** — Completion date.

**`dep: ID1, ID2`** — Comma-separated dependency task IDs.

**`ref: path1, path2`** — Comma-separated file paths (relative to project root).

**`spec: path#section`** — Single spec file path with optional `#anchor`.

**`note: text`** — Single-line note, or multi-line block:

```markdown
- [>] `EFF-014` Task title
  - note:
    First line of note.

    Second paragraph.

    ```rust
    fn example() {}
    ```

    Text after code block.
```

Multi-line notes: continuation lines are indented under the `note:` key. Blank lines within the note are preserved. Fenced code blocks (`` ``` ``) are tracked to avoid parsing their contents as tasks or metadata.

## Nesting

Tasks nest up to 3 levels deep. Each level adds 2 spaces of indentation:

```markdown
- [>] `N-001` Top level (depth 0)
  - added: 2025-05-10
  - [ ] `N-001.1` Subtask (depth 1)
    - note: Details here
    - [ ] `N-001.1.1` Sub-subtask (depth 2)
    - [ ] `N-001.1.2` Another sub-subtask
  - [>] `N-001.2` Second subtask
```

Metadata always comes before subtasks for a given task. Subtask IDs follow the pattern `PARENT.N`.

## Blank Lines

- Non-task lines (including blank lines) **terminate** task parsing — the track parser handles inter-section blank lines.
- Blank lines between the section header and first task are preserved.
- Blank lines after the last task in a section are preserved.
- Blank lines within multi-line notes are preserved.

## Inbox File

`inbox.md` uses a simpler format — list items with no checkboxes or IDs:

```markdown
# Inbox

- Parser crashes on empty blocks #bug
  Stack trace points to parser.rs line 142.

- Think about expression vs statement design #design
  #research
  If it's an expression, we get composability:
  ```
  let x = perform Ask() + 1
  ```
  But it makes the type system more complex.

- Quick idea for later #todo
```

### Inbox Item Structure

**Title line**: `- Title text #tag1 #tag2`

**Tag-only continuation lines**: Indented lines where every word starts with `#` are parsed as additional tags, not body text:

```markdown
- Some idea
  #design #research
```

**Body text**: Any other indented continuation lines (1+ spaces). Body text is stripped of 2 leading spaces when present.

**Item separation**: Blank lines between items.

## Selective Rewrite

Frame uses a selective rewrite strategy for round-trip preservation:

- Each parsed task stores its original source lines (`source_text`) and a `dirty` flag.
- `source_text` contains only the task's own lines (task line + metadata), **not** subtask lines.
- On serialization:
  - **Clean tasks** (not modified): emit `source_text` verbatim, preserving exact formatting.
  - **Dirty tasks** (modified): regenerate in canonical format.
  - Subtasks are always serialized independently regardless of parent's dirty state.

This means editing one task never reformats its parent, siblings, or unrelated tasks. If no mutations occur, parse-then-serialize produces byte-identical output.