diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f2c43d4..bf74849 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,12 +18,12 @@ jobs: - name: Lint Markdown files uses: DavidAnson/markdownlint-cli2-action@v14 with: - globs: "**/*.md" + globs: "**/*.md !tests/**/*.md" - name: Check spelling uses: streetsidesoftware/cspell-action@v5 with: - files: "**/*.md" + files: "**/*.md !tests/**/*.md" config: "./cspell.json" - name: Check links @@ -35,10 +35,10 @@ jobs: - name: AISD forbidden words check run: | echo "Checking for forbidden words (should/might/could/typically/usually/often)..." - # Exclude README.md, CHANGELOG.md, and AGENTS.md from this check - if find . -name "*.md" ! -name "README.md" ! -name "CHANGELOG.md" ! -name "AGENTS.md" -exec grep -l -E "\b(should|might|could|typically|usually|often)\b" {} \; | grep -q .; then + # Exclude README.md, CHANGELOG.md, AGENTS.md, and tests/ from this check + if find . -name "*.md" ! -path "./tests/*" ! -name "README.md" ! -name "CHANGELOG.md" ! -name "AGENTS.md" -exec grep -l -E "\b(should|might|could|typically|usually|often)\b" {} \; | grep -q .; then echo "❌ Found forbidden words in the following files:" - find . -name "*.md" ! -name "README.md" ! -name "CHANGELOG.md" ! -name "AGENTS.md" -exec grep -Hn -E "\b(should|might|could|typically|usually|often)\b" {} \; + find . -name "*.md" ! -path "./tests/*" ! -name "README.md" ! -name "CHANGELOG.md" ! -name "AGENTS.md" -exec grep -Hn -E "\b(should|might|could|typically|usually|often)\b" {} \; echo "" echo "Use MUST/REQUIRED/FORBIDDEN instead per AISD style guide." exit 1 @@ -47,9 +47,9 @@ jobs: - name: AISD line count check run: | - echo "Checking docs/ files for line count (max 600)..." + echo "Checking docs/ and spec/ files for line count (max 600)..." failed=0 - for file in docs/*.md docs/**/*.md; do + for file in docs/*.md docs/**/*.md spec/*.md; do [ -f "$file" ] || continue lines=$(wc -l < "$file") if [ "$lines" -gt 600 ]; then @@ -61,7 +61,7 @@ jobs: echo "Consider splitting large files." exit 1 fi - echo "✅ All docs files within line limit" + echo "✅ All docs and spec files within line limit" commitlint: name: Commit Messages diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d4ddd6e..53c71f0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,12 +17,14 @@ repos: hooks: - id: markdownlint args: ["--fix"] + exclude: ^tests/ - repo: https://github.com/streetsidesoftware/cspell-cli rev: v8.6.0 hooks: - id: cspell args: ["--no-progress", "--no-summary"] + exclude: ^tests/ # Conventional commits for semver - repo: https://github.com/compilerla/conventional-pre-commit @@ -39,10 +41,10 @@ repos: entry: bash -c 'if grep -rn -E "\b(should|might|could|typically|usually|often)\b" "$@" 2>/dev/null; then echo "❌ Found forbidden words. Use MUST/REQUIRED/FORBIDDEN instead."; exit 1; fi' -- language: system files: \.(md)$ - exclude: ^(README\.md|CHANGELOG\.md|AGENTS\.md)$ + exclude: ^(README\.md|CHANGELOG\.md|AGENTS\.md|tests/) - id: aisd-line-count name: AISD line count check (max 600) entry: bash -c 'for file in "$@"; do lines=$(wc -l < "$file"); if [ $lines -gt 600 ]; then echo "❌ $file has $lines lines (max 600). Consider splitting."; exit 1; fi; done' -- language: system - files: ^docs/.*\.md$ + files: ^(docs|spec)/.*\.md$ diff --git a/.releaserc.json b/.releaserc.json index 0eb91d8..3205641 100644 --- a/.releaserc.json +++ b/.releaserc.json @@ -6,7 +6,8 @@ { "preset": "conventionalcommits", "releaseRules": [ - { "type": "docs", "release": "minor" }, + { "type": "docs", "scope": "spec", "release": "minor" }, + { "type": "docs", "release": false }, { "type": "fix", "release": "patch" }, { "type": "refactor", "release": "patch" }, { "type": "style", "release": "patch" }, @@ -25,13 +26,13 @@ [ "@semantic-release/exec", { - "prepareCmd": "sed -i 's/\\*\\*Version:\\*\\* [0-9]*\\.[0-9]*\\.[0-9]*/\\*\\*Version:\\*\\* ${nextRelease.version}/' docs/specification.md && sed -i 's/Draft v[0-9]*\\.[0-9]*\\.[0-9]*/Draft v${nextRelease.version}/g' README.md AGENTS.md" + "prepareCmd": "sed -i 's/\\*\\*Version:\\*\\* [0-9]*\\.[0-9]*\\.[0-9]*/\\*\\*Version:\\*\\* ${nextRelease.version}/' spec/specification.md && sed -i 's/Draft v[0-9]*\\.[0-9]*\\.[0-9]*/Draft v${nextRelease.version}/g' README.md AGENTS.md" } ], [ "@semantic-release/git", { - "assets": ["CHANGELOG.md", "docs/specification.md", "README.md", "AGENTS.md"], + "assets": ["CHANGELOG.md", "spec/specification.md", "README.md", "AGENTS.md"], "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" } ], diff --git a/AGENTS.md b/AGENTS.md index 0c175e0..37b37d1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,79 +1,44 @@ # AI Agents Guide — TaskMark -AI-authored documentation for AI agents working on the TaskMark project. +Documentation for AI agents working on the TaskMark project. -**Format:** AISD-inspired -**Audience:** Claude Code, Cursor, and other AI coding assistants -**Last Updated:** 2025-12-05 +**Version:** 1.2.0 --- -## Commit Format - -Follow conventional commits pattern with type prefixes: +## Documentation Map + +| Document | Purpose | +|----------|---------| +| [specification.md](spec/specification.md) | Core format specification | +| [examples.md](spec/examples.md) | Format examples | +| [frontmatter.md](spec/frontmatter.md) | YAML configuration | +| [subtasks.md](spec/subtasks.md) | Subtask rules | +| [notes.md](spec/notes.md) | Note syntax | +| [recurrence.md](spec/recurrence.md) | Recurring tasks | +| [mutation.md](spec/mutation.md) | Task mutation rules | +| [serialization.md](spec/serialization.md) | Output formatting | +| [compatibility.md](docs/compatibility.md) | Format conversion | +| [libraries.md](docs/libraries.md) | Implementation libraries | -| Type | Use Case | Example | -|------|----------|---------| -| `docs:` | Documentation changes | `docs: update specification for recurrence fields` | -| `feat:` | New features | `feat: add blocked state [!] to task syntax` | -| `fix:` | Bug fixes | `fix: correct ISO 8601 datetime parsing` | -| `refactor:` | Code restructuring | `refactor: simplify inheritance resolution logic` | -| `test:` | Test additions/changes | `test: add recurrence calculation edge cases` | -| `chore:` | Maintenance tasks | `chore: update dependencies` | +--- -**Commit Message Structure:** +## Commit Format ``` : -Optional body with details: -- What changed -- Why it changed -- Impact on existing functionality - Co-Authored-By: Claude ``` -**Examples from AISD reference:** - -- `docs: map checkout architecture and rate limiting baseline` -- `docs: plan checkout rate limiting (20 req/min)` -- `feat: add checkout rate limiting (20 req/min)` - -**Key Rules:** - -- Use lowercase for type prefix -- Imperative mood: "add" not "added" -- Concise description (50 chars preferred, 72 max) -- Reference issue numbers when applicable: `fix: correct date parsing (#42)` - ---- - -## Project Overview - -### What is TaskMark? - -**Core Purpose:** Plain-text task management format that works everywhere - -**Key Characteristics:** - -- Markdown-based (`.md` files) -- Git-friendly version control -- Metadata inheritance through sections -- Powerful features (recurrence, subtasks, priorities) -- Simple by default, scales to complex projects - -**Status:** Draft v1.2.0 (specification complete, no parser yet) - -### Philosophy - -| Principle | Implementation | -|-----------|----------------| -| Plain text is forever | UTF-8 Markdown, human-readable | -| Simple by default | `- [ ] Task` is valid, everything else optional | -| Powerful when needed | Priorities, dates, recurrence, inheritance | -| Git-native | Treat tasks like code (branch, merge, diff) | -| DRY principle | Metadata inheritance prevents repetition | +| Type | Use Case | +|------|----------| +| `docs:` | Documentation changes | +| `feat:` | New features | +| `fix:` | Bug fixes | +| `refactor:` | Code restructuring | +| `test:` | Test additions/changes | +| `chore:` | Maintenance tasks | --- @@ -81,562 +46,110 @@ Co-Authored-By: Claude ``` taskmark-spec/ -├── README.md # User-facing introduction -├── AGENTS.md # THIS FILE - AI agent guide -├── LICENSE # MIT license -├── docs/ -│ ├── specification.md # Complete technical spec -│ └── format-comparison.md # Comparison with todo.txt, xit, etc. -└── examples/ - ├── sprint-planning.md # Sprint with subtasks - ├── team-standup.md # Team coordination - └── comprehensive.md # Full feature showcase +├── README.md # User introduction +├── AGENTS.md # This file +├── spec/ # Core specification +│ ├── specification.md # Main spec +│ └── *.md # Detailed specs +├── docs/ # Implementation docs +│ ├── format-comparison.md # Format comparison +│ ├── compatibility.md # Format conversion +│ └── libraries.md # Recommended libraries +└── tests/ # Golden test files ``` --- -## Core Syntax Reference +## Quick Syntax Reference ### Task States -| Symbol | State | Creates Recurrence | Use When | -|--------|-------|-------------------|----------| -| `[ ]` | Open | No | Task pending | -| `[x]` | Done | Yes (if `repeat:` present) | Task completed | -| `[-]` | Cancelled | No | Task abandoned/rejected | -| `[!]` | Blocked | No | Task paused/waiting | - -**Case Insensitive:** `[X]` = `[x]` - -### Complete Task Pattern - -```markdown -- [STATE] (PRIORITY) [PLANNED_DATE] [DONE_DATE] Description @assignee +project #tag due:DATE repeat:PATTERN ~estimate key:value - - [STATE] Subtask inherits parent metadata -``` - -**Component Order (Position-Based):** - -| Position | Component | Required | Pattern | -|----------|-----------|----------|---------| -| 1 | State marker | YES | `- [ ]`, `- [x]`, `- [-]`, `- [!]` | -| 2 | Priority | No | `(A)`, `(1)`, `(urgent)`, `(P1)` | -| 3 | Planned date | No | `YYYY-MM-DD` or `YYYY-MM-DDTHH:MM` or `YYYY-MM-DDTHH:MM:SS±HH:MM` | -| 4 | Done date | No | Same format (only if planned date exists) | -| 5 | Description | YES | Plain text (1-unlimited chars) | -| 6+ | In-line metadata | No | Any order | - -**In-line Metadata (Any Order):** - -| Type | Pattern | Example | Notes | -|------|---------|---------|-------| -| Assignee | `@username` | `@alice @bob` | Case-insensitive, `a-zA-Z0-9_-` | -| Project | `+name[/sub]` | `+Acme/Backend`, `+app/v1.2.3` | Hierarchical `/`, versions with `.` | -| Tag | `#tag` | `#critical #backend` | Case-insensitive | -| Due date | `due:DATETIME` | `due:2024-03-15T18:00` | ISO 8601 format | -| Estimate | `~NUM[hmd]` | `~2h`, `~30m`, `~3d` | Hours, minutes, days | -| Recurrence | `repeat:PATTERN` | `repeat:weekdays`, `repeat:"every monday"` | Uses recurrent library | -| Custom metadata | `key:value` or `key:"value"` | `type:bug ticket:ENG-4701` | Keys case-insensitive | - -### Date Formats - -**Accepted:** - -- Date only: `2024-03-10` (uses YAML front matter timezone or local) -- Date + time: `2024-03-10T09:00` -- Date + time + seconds: `2024-03-10T09:00:00` -- Date + time + timezone: `2024-03-10T09:00-05:00` (explicit timezone) - -**Invalid Date Handling:** Warn at `file:line`, ignore field, continue parsing - -### YAML Front Matter - -**Supported Fields:** - -| Field | Purpose | Example | -|-------|---------|---------| -| `locale` | Natural language date parsing + default output format | `en_US`, `fr_FR` | -| `timezone` | Default timezone for dates without explicit timezone | `America/New_York`, `UTC` | -| `date_format` | Output date format (strftime), overrides locale | `%Y-%m-%d`, `%d/%m/%Y` | - -**File Scope Rules:** - -- Front matter applies ONLY to the current file -- Linked files retain their own front matter -- No inheritance of front matter between files -- Default: system locale/timezone, ISO 8601 output (`YYYY-MM-DD`) - -### Section Headers and Inheritance - -```markdown -# TODO +GlobalProject #globalTag @team type:value - -## Section Name +SectionProject #sectionTag @alice - -- [ ] Task +TaskProject #taskTag @bob -``` - -**Inheritance Rules:** - -| Metadata Type | Behavior | Example | -|---------------|----------|---------| -| Projects (`+`) | Hierarchical join with `/` | `+A` + `+B` + `+C` = `+A/B/C` | -| Tags (`#`) | Additive (all tags included) | `#api` + `#urgent` = both | -| Assignees (`@`) | Additive (all assignees included) | `@alice` + `@bob` = both | -| Key-value | Child overrides parent | Child `type:bug` overrides parent `type:feature` | - -**Example:** - -```markdown -# TODO +Acme #work @team - -## Backend +API #critical @alice - -- [ ] Task +Database @bob -``` - -**Task inherits:** `+Acme/API/Database`, `#work`, `#critical`, `@team`, `@alice`, `@bob` - -### Subtasks - -**Rules:** +| Symbol | State | Creates Recurrence | +|--------|-------|-------------------| +| `[ ]` | Open | No | +| `[.]` | In Progress | No | +| `[x]` | Done | Yes (if `repeat:`) | +| `[-]` | Cancelled | No | +| `[!]` | Blocked | No | -- One level only (no sub-subtasks) -- Any indentation > 0 (spaces or tabs) -- Inherits ALL parent metadata + section metadata -- Independent state from parent -- Projects hierarchical join -- Tags additive -- Assignees additive +### Task Pattern ```markdown -- [ ] (A) Parent task @alice +Database #critical ~8h - - [ ] (B) Update connection pooling @alice ~2h - - [ ] (B) Add retry logic @bob ~3h - - [x] (C) Write migration @alice ~3h +- [STATE] (PRIORITY) Description @assignee +project #tag ~estimate due:DATE repeat:PATTERN key:value + - [STATE] Subtask (inherits parent metadata) + - Note text (no brackets) ``` -### File Links - -**Syntax:** `[Link Text](path/to/file.md)` on standalone line - -**Inheritance:** All tasks in linked file inherit section metadata where link appears +### Inheritance ```markdown -# TODO +Acme #work - -## Backend +Backend #critical - -[API Team](backend/api.md) -[Database Team](backend/database.md) +# Project +Acme #work @team +## Section +Backend #critical +- [ ] Task inherits: +Acme/Backend, #work, #critical, @team ``` -**Result:** All tasks in both files inherit `+Acme/Backend`, `#work`, `#critical` - ---- - -## Recurrence Behavior - -### When Task Marked `[x]` with `repeat:` - -**Old Task Changes:** - -1. State → `[x]` -2. Add done date (2nd position after planned date) -3. Remove `repeat:` field - -**New Task Created:** - -1. State → `[ ]` -2. Dates updated to next occurrence -3. Keep `repeat:` field -4. Preserve all other metadata - -### Date Calculation Algorithm - -| Step | Action | Library | -|------|--------|---------| -| 1 | Parse `repeat:` pattern | recurrent | -| 2 | Extract RRULE + DTSTART | recurrent | -| 3 | Calculate next occurrence | python-dateutil.rrule | -| 4 | Calculate offset (if both planned & due) | pendulum | -| 5 | Apply offset to new dates | pendulum | +| Type | Behavior | +|------|----------| +| Projects (`+`) | Hierarchical: `+A` + `+B` = `+A/B` | +| Tags (`#`) | Additive | +| Assignees (`@`) | Additive | +| Key-value | Child overrides parent | -**Offset Preservation:** - -```markdown -# Original -- [ ] Task planned:2024-03-10 due:2024-03-14 repeat:weekly - -# After marking [x] on 2024-03-10 -- [x] 2024-03-10 2024-03-10 Task due:2024-03-14 -- [ ] Task planned:2024-03-17 due:2024-03-21 repeat:weekly -``` - -4-day offset between planned and due is preserved. - ---- - -## Common Patterns and Conventions - -### Priority Sorting - -| Condition | Sort Method | Example | -|-----------|-------------|---------| -| All numeric | Numeric ascending | `(1)` < `(2)` < `(10)` | -| Any non-numeric | Alphanumeric ascending | `(A)` < `(A1)` < `(B)` | -| Same priority | Line number ascending | First in file comes first | - -### Standard Metadata Keys (Convention, NOT Core Spec) - -| Key | Common Values | Example | -|-----|---------------|---------| -| `type` | `bug`, `feature`, `docs`, `refactor`, `test` | `type:bug` | -| `epic` | Epic/initiative name | `epic:auth-v2` | -| `ticket` | Issue tracker ID | `ticket:ENG-4739` | -| `sprint` | Sprint number/name | `sprint:24` | -| `milestone` | Release version | `milestone:v2.0` | -| `url` | Full URL | `url:https://jira.example.com/ENG-4701` | -| `status` | Status name | `status:in-progress` | - -### File Organization Strategies - -**Single File (Small Projects):** - -```markdown -# TODO +Project - -## Feature Work -## Bug Fixes -## Documentation -``` - -**Multi-File (Large Projects):** - -```markdown -# Main: project-todo.md -# TODO +Project #tag - -## Engineering +Engineering -[Backend](team/backend.md) -[Frontend](team/frontend.md) - -## Product +Product -[Features](team/features.md) -``` - ---- - -## Development Guidelines - -### When Writing Parsers - -**MUST Requirements:** - -| Requirement | Rule | -|-------------|------| -| File encoding | UTF-8 only, fail on non-UTF-8 | -| Line endings | Accept both LF and CRLF | -| Date parsing | ISO 8601: `YYYY-MM-DD[THH:MM[:SS][±HH:MM]]` | -| Position-based dates | 1st datetime = planned, 2nd = done | -| Recurrence parsing | Use recurrent library | -| Recurrence calculation | Use python-dateutil.rrule | -| Case matching | Treat case-insensitive: `@User` = `@user` | -| State recognition | Only `[ ]`, `[x]`, `[-]`, `[!]` | -| Subtask depth | Reject > 1 level (warn + treat as 1-level) | - -**Error Handling:** - -| Error Type | Action | Log Level | -|------------|--------|-----------| -| Malformed date | Warn at `file:line`, ignore field | WARNING | -| Multiple priorities | Use first, ignore rest | INFO | -| Invalid task state | Ignore line | INFO | -| Unclosed quote | Warn, treat as unquoted | WARNING | -| Sub-subtask | Warn, treat as subtask | WARNING | - -### When Writing Documentation - -**Tone:** - -- Clear, concise, scannable -- Tables over prose when possible -- Explicit constraints (not vague) -- Use MUST/REQUIRED/SHOULD/MAY from RFC 2119 - -**Examples:** - -- Use realistic software project scenarios -- Show minimal → full-featured progression -- Include "after inheritance" results - -### When Writing Examples - -**Required Elements:** - -1. YAML front matter with locale/timezone (if using times) -2. Top-level `# TODO` header -3. Section headers for organization -4. Mix of task states (`[ ]`, `[x]`, `[-]`, `[!]`) -5. Representative metadata usage -6. Comments explaining inheritance - ---- - -## Common Pitfalls and Solutions - -### Pitfall: Forgetting Inheritance - -**Problem:** - -```markdown -# TODO +Acme - -## Backend -- [ ] Task +Acme # WRONG: Redundant project -``` - -**Solution:** - -```markdown -# TODO +Acme - -## Backend +Backend -- [ ] Task # Inherits +Acme/Backend -``` - -### Pitfall: Deep Subtask Nesting - -**Problem:** - -```markdown -- [ ] Task - - [ ] Subtask - - [ ] Sub-subtask # INVALID -``` - -**Solution:** - -```markdown -- [ ] Task - - [ ] Subtask 1 - - [ ] Subtask 2 - -- [ ] Sub-subtask as separate task -``` - -### Pitfall: Inline File Links - -**Problem:** - -```markdown -- [ ] Check [tasks](team.md) for details # Link ignored -``` - -**Solution:** - -```markdown -## Section - -[Team Tasks](team.md) - -- [ ] Check tasks for details -``` - -### Pitfall: Ambiguous Dates - -**Problem:** - -```markdown -- [ ] Task due:03-10 # Ambiguous format -``` - -**Solution:** - -```markdown -- [ ] Task due:2024-03-10 # ISO 8601 required -``` - -### Pitfall: Missing Quotes in Values - -**Problem:** - -```markdown -- [ ] Task notes:This is a long note # Stops at first space -# Parsed as: notes:This -``` - -**Solution:** - -```markdown -- [ ] Task notes:"This is a long note" -``` +See [specification.md](spec/specification.md) for complete syntax. --- ## AI Agent Workflow -### When Asked to Create Todo Files - -1. **Start Simple:** `# TODO` header + basic tasks -2. **Add Structure:** Sections with descriptive names -3. **Apply Metadata:** Projects, tags on sections (not tasks) -4. **Use Inheritance:** Let tasks inherit from sections -5. **Add Details:** Priorities, dates, estimates only where needed - -### When Asked to Update Tasks - -1. **Read First:** Never edit without reading current state -2. **Preserve Format:** Maintain existing indentation, metadata style -3. **Check Recurrence:** If `repeat:` present, understand implications -4. **Update Inheritance:** Consider section metadata changes - -### When Asked About Format - -1. **Cite Spec:** Reference [specification.md](docs/specification.md) -2. **Show Examples:** Use realistic project scenarios -3. **Explain Trade-offs:** Compare with todo.txt, xit, etc. -4. **Link Comparison:** Reference [format-comparison.md](docs/format-comparison.md) - -### When Implementing Features - -1. **Check Spec First:** [specification.md](docs/specification.md) is source of truth -2. **Follow Library Stack:** dateparser, recurrent, python-dateutil, pendulum -3. **Error Handling:** Warn at `file:line`, continue parsing -4. **Test Edge Cases:** Invalid dates, deep nesting, malformed syntax - ---- - -## Testing Scenarios - -### Valid Inputs to Support - -```markdown -# Minimal valid -- [ ] Task - -# Full featured -- [ ] (A) 2024-03-10T09:00-05:00 Task @user +proj/sub #tag1 #tag2 due:2024-03-15 repeat:weekdays ~2h type:bug +### Creating Todo Files -# Subtasks with inheritance -## Section +Proj #tag -- [ ] Parent @alice - - [ ] Child inherits @alice +Proj #tag +1. Start with `#` header + basic tasks +2. Add section headers for organization +3. Put metadata on sections (inheritance) +4. Add priorities, dates, estimates where needed -# File links -## Engineering +Eng -[Team](team.md) # All tasks in team.md inherit +Eng +### Updating Tasks -# Recurrence -- [ ] 2024-03-18T09:00 Standup repeat:"every monday at 9am" -``` +1. Read file first +2. Preserve existing formatting +3. Check for `repeat:` implications +4. Use inline metadata for dates (`planned:`, `due:`, `done:`) -### Invalid Inputs to Handle Gracefully +### Common Pitfalls -```markdown -# Malformed date (warn + skip) -- [ ] Task due:2024-13-99 +| Pitfall | Solution | +|---------|----------| +| Redundant project on task | Let task inherit from section | +| Deep subtask nesting | Flatten to 1 level | +| Inline file links | Put links on standalone lines | +| Non-ISO dates | Use `YYYY-MM-DD` format | +| Missing quotes | Use `key:"value with spaces"` | -# Deep nesting (warn + flatten) -- [ ] Task - - [ ] Subtask - - [ ] Sub-subtask # Treat as subtask - -# Invalid state (ignore line) -- [?] Unknown state - -# Unclosed quote (warn + treat as unquoted) -- [ ] Task notes:"unclosed -``` +See [examples.md](spec/examples.md) for patterns. --- -## Quick Reference - -### Minimal Task - -```markdown -- [ ] Task description -``` - -### Everything - -```markdown -- [ ] (A) 2024-03-10T09:00-05:00 Task @user +project/sub #tag created:2024-03-01T10:00 started:2024-03-10T09:00 due:2024-03-15T18:00 repeat:weekdays ~2h type:bug ticket:ENG-4739 - - [ ] Subtask -``` - -### Section with Inheritance - -```markdown -# TODO +global-project #global-tag @team - -## Section Name +section-project #section-tag @alice - -- [ ] Task inherits: +global-project/section-project, #global-tag, #section-tag, @team, @alice -``` - -### File Link with Inheritance - -```markdown -## Backend +Acme #critical @alice - -[API Team](team/api.md) - -# All tasks in api.md inherit: +Acme, #critical, @alice -``` +## Error Handling ---- +Parser MUST NOT fail unless file is unparseable. -## Resources +| Error | Action | +|-------|--------| +| Malformed date | Preserve as string | +| Invalid state | Treat as note | +| Sub-subtask | Warn, flatten | +| `+project` on subtask | Warn, ignore | +| `repeat:` on subtask | Warn, ignore | -| Document | Purpose | When to Use | -|----------|---------|-------------| -| [README.md](README.md) | User introduction | Explaining format to humans | -| [specification.md](docs/specification.md) | Complete technical spec | Implementation questions | -| [format-comparison.md](docs/format-comparison.md) | Format comparison | Understanding design decisions | -| [sprint-planning.md](examples/sprint-planning.md) | Sprint example | Learning basics | -| [team-standup.md](examples/team-standup.md) | Team coordination | Intermediate example | -| [comprehensive.md](examples/comprehensive.md) | Full feature showcase | Advanced usage | +See [specification.md](spec/specification.md) for complete error handling. --- ## Implementation Status -| Component | Status | Notes | -|-----------|--------|-------| -| Specification | ✅ Complete | Draft v1.2.0 | -| Reference Parser | ⏳ Not started | Python implementation planned | -| VSCode Extension | ⏳ Not started | Syntax highlighting, task detection | -| CLI Tool | ⏳ Not started | Query, filter, update tasks | -| Web Viewer | ⏳ Not started | Read-only task viewer | - ---- - -## Philosophy for AI Agents - -### AISD Principles Applied - -This document follows AISD (AI Small Docs) principles: - -1. **Tables over prose** — Scannable structures for quick lookup -2. **Explicit constraints** — "MUST be UTF-8" not "should use UTF-8" -3. **Small focused content** — Each section addresses one concern -4. **AI-authored, human-reviewed** — Generated by Claude, reviewed by humans - -### Working with This Project - -**When in doubt:** - -1. Read the spec: [specification.md](docs/specification.md) -2. Check examples: [examples/](examples/) -3. Compare formats: [format-comparison.md](docs/format-comparison.md) -4. Ask questions: Clarify before implementing - -**Core values:** - -- Plain text simplicity -- Git-native workflow -- Human readability -- Machine parseability -- Future-proof format +| Component | Status | +|-----------|--------| +| Specification | ✅ v1.2.0 | +| Reference Parser | ⏳ Not started | +| VSCode Extension | ⏳ Not started | +| CLI Tool | ⏳ Not started | diff --git a/README.md b/README.md index 5743379..7287a2f 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,11 @@ [![CI](https://github.com/taskmark/taskmark-spec/actions/workflows/ci.yml/badge.svg)](https://github.com/taskmark/taskmark-spec/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -**Plain-text todos that work everywhere.** Write tasks in Markdown, track them in git, and never lose your todos again. +**Plain-text todos that work everywhere.** -**Status:** Draft v1.2.0 +Write tasks in Markdown. Track them in git. Never lose your todos again. + +**Version:** 1.2.0 --- @@ -13,11 +15,11 @@ Your todos should be as simple as your code: -- **Plain text** - Works in any editor, forever -- **Git-friendly** - Version control your tasks like your code -- **Markdown-based** - Looks good everywhere (GitHub, VSCode, terminal) -- **Organized** - Sections with metadata inheritance -- **Powerful** - Recurring tasks, subtasks, priorities, estimates +- **Plain text** — Works in any editor, forever +- **Git-friendly** — Version control your tasks like your code +- **Markdown-based** — Looks good everywhere (GitHub, VSCode, terminal) +- **Organized** — Sections with metadata inheritance +- **Powerful** — Recurring tasks, subtasks, priorities, estimates, notes --- @@ -26,28 +28,29 @@ Your todos should be as simple as your code: ### The Basics ```markdown -# TODO +# My Project - [ ] Review pull request @alice -- [x] 2024-03-10 2024-03-10 Update dependencies @bob +- [x] Update dependencies @bob done:2024-03-10 - [ ] Write tests @charlie due:2024-03-15 ``` -That's it. You're done. Everything else is optional. +That's it. You're using TaskMark. Everything else is optional. ### Add Some Structure ```markdown -# TODO +Acme #work +# Acme Project +Acme #work ## Backend +Backend #critical -- [ ] (A) 2024-03-10 Fix database connection @alice due:2024-03-15 ~8h +- [ ] (A) Fix database connection @alice planned:2024-03-10 due:2024-03-15 ~8h - [ ] Update connection pooling ~2h - [ ] Add retry logic ~3h - - [x] 2024-03-09 2024-03-10 Write migration ~3h + - Blocked on DBA approval #repeat + - [x] Write migration ~3h done:2024-03-10 -- [ ] 2024-03-15T09:00 Daily standup @team repeat:weekdays ~15m +- [ ] Daily standup @team planned:2024-03-15T09:00 repeat:weekdays ~15m ## Frontend +Frontend #routine @@ -57,24 +60,27 @@ That's it. You're done. Everything else is optional. **What's happening here?** -- `# TODO +Acme #work` = Top-level metadata (all tasks inherit this!) -- `(A)` = Priority -- `2024-03-10` = Planned start date -- `2024-03-10 2024-03-10` = Planned and done dates -- `@alice` = Assigned to Alice -- `+Backend` = Section project (combines with `+Acme`) -- `#critical` = Section tag (adds to `#work`) -- `due:2024-03-15` = Deadline -- `~8h` = Time estimate -- `repeat:weekdays` = Recurring task -- Indented = Subtasks +| Element | Meaning | +|---------|---------| +| `# Acme Project +Acme #work` | Top-level metadata (all tasks inherit!) | +| `(A)` | Priority | +| `planned:2024-03-10` | Planned start date | +| `done:2024-03-10` | Completion date | +| `@alice` | Assigned to Alice | +| `+Backend` | Section project (combines with `+Acme`) | +| `#critical` | Section tag (adds to `#work`) | +| `due:2024-03-15` | Deadline | +| `~8h` | Time estimate | +| `repeat:weekdays` | Recurring task | +| Indented `- [ ]` | Subtask | +| Indented `- text` | Note (no brackets) | ### Inheritance Magic -Put metadata on ANY section (even the top one!), and all tasks inherit it: +Put metadata on ANY section, and all tasks inherit it: ```markdown -# TODO +Acme #work +# Acme Project +Acme #work ## Database Maintenance +Database #critical @@ -84,17 +90,18 @@ Put metadata on ANY section (even the top one!), and all tasks inherit it: Both tasks automatically get: `+Acme/Database`, `#work`, and `#critical` -**Every task in the file inherits from `# TODO`!** +**Every task in the file inherits from the top-level header!** --- -## Features +## Features at a Glance ### Task States | Symbol | Meaning | Example | |--------|---------|---------| -| `[ ]` | Open task | `- [ ] Fix login bug` | +| `[ ]` | Open | `- [ ] Fix login bug` | +| `[.]` | In Progress | `- [.] Working on feature` | | `[x]` | Done | `- [x] Update docs` | | `[-]` | Cancelled | `- [-] Deprecated feature` | | `[!]` | Blocked | `- [!] Awaiting API access` | @@ -107,21 +114,20 @@ Both tasks automatically get: `+Acme/Database`, `#work`, and `#critical` - [ ] (C) Nice to have ``` -Use any value: `(A)`, `(1)`, `(urgent)`, `(P1)` - whatever makes sense to you. +Use any value: `(A)`, `(1)`, `(urgent)`, `(P1)` — whatever makes sense. ### Dates and Times ```markdown -# Simple dates -- [ ] 2024-03-10 Start project -- [x] 2024-03-08 2024-03-10 Complete report +# Date fields use inline metadata +- [ ] Start project planned:2024-03-10 +- [x] Complete report planned:2024-03-08 done:2024-03-10 # With times -- [ ] 2024-03-15T09:00 Morning standup due:2024-03-15T09:30 +- [ ] Morning standup planned:2024-03-15T09:00 due:2024-03-15T09:30 # With timezones -- [ ] 2024-03-15T09:00-05:00 Team call (EST) -- [ ] 2024-03-15T15:00+01:00 European meeting +- [ ] Team call (EST) planned:2024-03-15T09:00-05:00 ``` ### Recurring Tasks @@ -134,32 +140,32 @@ Use any value: `(A)`, `(1)`, `(urgent)`, `(P1)` - whatever makes sense to you. When you mark a recurring task done, it automatically creates the next one. -### Subtasks +### Subtasks and Notes ```markdown - [ ] Launch new feature @alice - [ ] Write documentation - [ ] Create tests - [x] Design mockups + - Remember to update changelog + - Check with legal team #repeat ``` -One level only. Keep it simple. +Subtasks have state markers (`[ ]`). Notes are just text after a dash. + +Notes with `#repeat` are copied when recurring tasks generate new instances. ### Multiple Files **Main: project-todo.md** ```markdown -# TODO +Acme #work +# Acme Project +Acme #work ## Backend +Backend #critical [API Team](backend/api.md) [Database Team](backend/database.md) - -## Frontend +Frontend #routine - -[UI Team](frontend/ui-tasks.md) ``` **Linked: backend/api.md** @@ -187,8 +193,9 @@ Add whatever you need: ## Documentation | Document | What's Inside | -|----------|--------------| -| [Specification](docs/specification.md) | Complete technical specification | +|----------|---------------| +| [Specification](spec/specification.md) | Core format specification | +| [Detailed Specs](spec/) | Deep-dive on specific features | | [Format Comparison](docs/format-comparison.md) | How we compare to todo.txt, xit, etc. | --- @@ -201,7 +208,7 @@ Start with basic checkboxes. Add structure as your project grows. Use metadata i **Plain text is forever.** Your todos will outlive any app. -**Git-native.** Treat your tasks like code. Branch, merge, diff, blame - it all just works. +**Git-native.** Treat your tasks like code. Branch, merge, diff, blame — it all just works. **Markdown everywhere.** Looks good on GitHub, renders in VSCode, readable in terminal, works in any editor. @@ -209,38 +216,34 @@ Start with basic checkboxes. Add structure as your project grows. Use metadata i ## Quick Reference -### Basic Task +### Minimal Task ```markdown - [ ] Task description ``` -### Everything +### Full Featured ```markdown -- [ ] (A) 2024-03-10 Task @user +project #tag due:2024-03-15 ~2h type:bug +- [ ] (A) Task @user +project #tag planned:2024-03-10 due:2024-03-15 ~2h type:bug - [ ] Subtask + - Note with context ``` ### Section with Inheritance ```markdown -# TODO +project #tag type:value +# My Project +project #tag type:value ## Section Name +section-project - [ ] Task inherits: +project/section-project, #tag, type:value ``` -### Recurring - -```markdown -- [ ] Task repeat:weekdays due:2024-03-15 -``` - ### States - `[ ]` = Open +- `[.]` = In Progress - `[x]` = Done - `[-]` = Cancelled - `[!]` = Blocked @@ -249,9 +252,11 @@ Start with basic checkboxes. Add structure as your project grows. Use metadata i ## Implementation Status -**Specification:** ✅ Draft v1.2.0 (Complete) -**Reference Parser:** ⏳ Not yet implemented -**Editor Support:** ⏳ Not yet implemented +| Component | Status | +|-----------|--------| +| Specification | ✅ v1.2.0 | +| Reference Parser | ⏳ Not yet implemented | +| Editor Support | ⏳ Not yet implemented | Want to help? We'd love contributions for parsers, editor plugins, or CLI tools. @@ -281,16 +286,13 @@ pre-commit run --all-files ### Commit Convention -This project uses [Conventional Commits](https://www.conventionalcommits.org/) for semantic versioning: +This project uses [Conventional Commits](https://www.conventionalcommits.org/): ```bash -feat: add new feature # → minor version bump (0.1.0 → 0.2.0) -fix: fix a bug # → patch version bump (0.1.0 → 0.1.1) +feat: add new feature # → minor version bump +fix: fix a bug # → patch version bump docs: update documentation # → patch version bump chore: maintenance task # → no release - -# Breaking changes -feat!: breaking change # → major version bump (0.1.0 → 1.0.0) ``` --- @@ -299,19 +301,19 @@ feat!: breaking change # → major version bump (0.1.0 → 1.0.0) Inspired by the best parts of: -- [todo.txt](https://github.com/todotxt/todo.txt) - The OG plain-text format -- [todo.md](https://github.com/todomd/todo.md) - Markdown task lists -- [xit](https://github.com/jotaen/xit) - Minimalist text format +- [todo.txt](https://github.com/todotxt/todo.txt) — The OG plain-text format +- [todo.md](https://github.com/todomd/todo.md) — Markdown task lists +- [xit](https://github.com/jotaen/xit) — Minimalist text format Specification format influenced by: -- [AISD](https://github.com/rickgemignani/AISD) - AI Small Docs -- [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119) - Requirement levels +- [AISD](https://github.com/rickgemignani/AISD) — AI Small Docs +- [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119) — Requirement levels --- ## License -MIT License - see [LICENSE](LICENSE) file for details. +MIT License — see [LICENSE](LICENSE) file for details. **TL;DR:** Use it however you want. Build tools, sell products, fork it, whatever. No strings attached. diff --git a/cspell.json b/cspell.json index 2afbc05..6345c51 100644 --- a/cspell.json +++ b/cspell.json @@ -7,15 +7,13 @@ "subtasks", "assignee", "assignees", - "subheadings", "YYYY", "DDTHH", "backlog", "todos", - "unchecked", - "datetimes", "parseable", - "parseability", + "unparseable", + "reserialize", "AISD", "RRULE", "DTSTART", @@ -23,6 +21,9 @@ "dateutil", "rrule", "dateparser", + "chrono", + "luxon", + "teambition", "todotxt", "todomd", "jotaen", @@ -34,7 +35,8 @@ "ignorePaths": [ ".git/**", "node_modules/**", - ".github/**" + ".github/**", + "tests/**" ], "ignoreRegExpList": [ "\\(#\\d+\\)", diff --git a/docs/compatibility.md b/docs/compatibility.md new file mode 100644 index 0000000..bb03e59 --- /dev/null +++ b/docs/compatibility.md @@ -0,0 +1,51 @@ +# Format Compatibility + +Conversion rules. See [specification.md](../spec/specification.md). + +--- + +## todo.txt + +| Element | todo.txt | TaskMark | +|---------|----------|----------| +| States | `x` (done), none (open) | `[ ]`, `[.]`, `[x]`, `[-]`, `[!]` | +| Priority | `(A)` at start | `(A)` after state | +| Contexts | `@context` | `#context` | +| Projects | `+project` | `+project` | +| Assignees | None | `@user` | + +--- + +## todo.md + +| Element | todo.md | TaskMark | +|---------|---------|----------| +| States | `[ ]`, `[x]`, `[-]` | `[ ]`, `[.]`, `[x]`, `[-]`, `[!]` | +| Priority | None | `(value)` | +| Metadata | Freeform | `key:value` | +| Recurrence | None | `repeat:PATTERN` | + +--- + +## xit + +| Element | xit | TaskMark | +|---------|-----|----------| +| States | `[ ]`, `[@]`, `[x]`, `[~]`, `[?]` | `[ ]`, `[.]`, `[x]`, `[-]`, `[!]` | +| Priority | `!`, `!!`, `!!!` | `(C)`, `(B)`, `(A)` | +| Projects | None | `+name` | + +State mapping: `[@]`→`[.]`, `[~]`→`[-]`, `[?]`→`[!]` + +--- + +## Data Loss + +| Direction | Lost | +|-----------|------| +| todo.txt → TaskMark | None | +| TaskMark → todo.txt | `[.]`/`[-]`/`[!]`, sections, subtasks, notes | +| todo.md → TaskMark | None | +| TaskMark → todo.md | `[.]`/`[!]`, priority, metadata, recurrence, notes | +| xit → TaskMark | `[?]` nuance | +| TaskMark → xit | Sections, assignees, projects, subtasks, notes | diff --git a/docs/format-comparison.md b/docs/format-comparison.md index b22d208..edac9d1 100644 --- a/docs/format-comparison.md +++ b/docs/format-comparison.md @@ -19,7 +19,7 @@ This document compares existing plain-text todo formats that inspired the TaskMa | Feature | todo.txt | todo.md | xit | .todo | TaskMark | |---------|----------|---------|-----|-------|-----------| -| **Task States** | `x` (done) | `[ ]`, `[x]`, `[-]` | `[ ]`, `[x]`, `[-]` | `[ ]`, `[x]` | `[ ]`, `[x]`, `[-]`, `[!]` | +| **Task States** | `x` (done) | `[ ]`, `[x]`, `[-]` | `[ ]`, `[x]`, `[-]` | `[ ]`, `[x]` | `[ ]`, `[.]`, `[x]`, `[-]`, `[!]` | | **Priority** | `(A-Z)` | No standard | No | Variable | `(any)` | | **Assignees** | No | `@user` | No | Variable | `@user` | | **Projects** | `+project` | No standard | `project:name` | Variable | `+project` with `/` hierarchy | @@ -30,6 +30,7 @@ This document compares existing plain-text todo formats that inspired the TaskMa | **Subtasks** | No (flat list) | Yes (indented) | Yes (indented) | Yes (indented) | Yes (one level) | | **Sections** | No | Yes (headers) | Yes (dates) | Yes (headers) | Yes (headers) | | **File Links** | No | No | No | No | `[text](file.md)` (core) | +| **Notes** | No | No | No | No | Indented `- text` (no brackets) | | **Metadata** | `key:value` | Inline text | `key:value` | Separate lines | `key:value` | --- @@ -61,10 +62,10 @@ This document compares existing plain-text todo formats that inspired the TaskMa | Format | Support | Syntax | |--------|---------|--------| | **todo.txt** | No | Projects serve as implicit sections | -| **todo.md** | Yes | `# TODO`, `## Section`, `### Subsection` | +| **todo.md** | Yes | `# Name`, `## Section`, `### Subsection` | | **xit** | Yes | Date headers: `2025-11-19` | | **.todo** | Yes | Markdown headers | -| **TaskMark** | Yes | `# TODO`, `## Section` with metadata inheritance | +| **TaskMark** | Yes | `# Name`, `## Section` with metadata inheritance | --- @@ -83,7 +84,7 @@ This document compares existing plain-text todo formats that inspired the TaskMa | Aspect | todo.md | TaskMark | Rationale | |--------|---------|-----------|-----------| -| Task states | `[ ]`, `[x]`, `[-]` | `[ ]`, `[x]`, `[-]`, `[!]` | Added blocked state | +| Task states | `[ ]`, `[x]`, `[-]` | `[ ]`, `[.]`, `[x]`, `[-]`, `[!]` | Added in-progress, blocked | | Priority | Not standardized | `(value)` after state | Consistent with todo.txt | | Metadata | No standard | `key:value` + core fields | Machine-parseable structure | | Recurrence | No | `repeat:*` fields | Time-based task automation | @@ -143,7 +144,9 @@ This document compares existing plain-text todo formats that inspired the TaskMa - [ ] (A) Fix database connection pooling @alice +Database #critical due:2024-03-15 ~8h type:urgent - [ ] (B) Update connection settings @alice ~2h - [ ] (B) Add retry logic @bob ~3h - - [x] (C) 2024-03-09 2024-03-10 Write migration tests @alice ~3h + - [x] (C) Write migration tests @alice ~3h done:2024-03-10 + - Check with DBA before deploying #repeat + - Production credentials in vault ``` **After inheritance:** @@ -212,13 +215,14 @@ This document compares existing plain-text todo formats that inspired the TaskMa | Feature | Source | Improvement | |---------|--------|-------------| | Plain text simplicity | todo.txt | UTF-8 Markdown for readability | -| Task states `[x]` | xit, todo.md | Added `[!]` for blocked | +| Task states `[x]` | xit, todo.md | Added `[.]`, `[!]` | | Priority `(A)` | todo.txt | Flexible: letters, numbers, symbols | | Assignees `@user` | todo.md | Case-insensitive matching | | Projects `+proj` | todo.txt | Hierarchical with `/` | | Tags `#tag` | todo.md | Additive inheritance | | Sections | todo.md | With metadata inheritance | | Subtasks | xit, todo.md | One level, clear rules | +| Notes | New | Text annotations under tasks | | Dates `YYYY-MM-DD` | todo.txt | Core spec, not extension | | Recurrence | New | RRULE-inspired, auto-repeat | | File links | New | Markdown links with inheritance | @@ -244,7 +248,7 @@ This document compares existing plain-text todo formats that inspired the TaskMa | Element | todo.md | TaskMark | Notes | |---------|---------|-----------|-------| -| Task states | `[ ]`, `[x]`, `[-]` | `[ ]`, `[x]`, `[-]`, `[!]` | Add blocked if needed | +| Task states | `[ ]`, `[x]`, `[-]` | `[ ]`, `[.]`, `[x]`, `[-]`, `[!]` | Add in-progress, blocked | | Priority | None | `(value)` after state | Add if needed | | Metadata | Freeform | `key:value` | Standardize format | | Recurrence | None | `repeat:*` | Add if needed | @@ -255,7 +259,7 @@ This document compares existing plain-text todo formats that inspired the TaskMa | Element | xit | TaskMark | Notes | |---------|-----|-----------|-------| | Date headers | `2025-11-19` | `## Section` or `due:` | Reorganize by project | -| Task states | `[ ]`, `[x]`, `[-]` | `[ ]`, `[x]`, `[-]`, `[!]` | Compatible + blocked | +| Task states | `[ ]`, `[x]`, `[-]` | `[ ]`, `[.]`, `[x]`, `[-]`, `[!]` | Add in-progress, blocked | | Metadata | `key:value` | `key:value` + core | Extract core fields | | Priority | None | `(value)` | Add if needed | | Projects | `project:name` | `+name` | Convert syntax | diff --git a/docs/libraries.md b/docs/libraries.md new file mode 100644 index 0000000..0bae194 --- /dev/null +++ b/docs/libraries.md @@ -0,0 +1,73 @@ +# Implementation Libraries + +Recommended libraries for TaskMark implementations. See [specification.md](../spec/specification.md) for core spec. + +--- + +## Core Capabilities + +TaskMark parsers need libraries for: + +| Capability | Purpose | +|------------|---------| +| Natural language → datetime | Parse phrases like "next Friday 3pm" | +| Natural language → recurrence | Parse "every Tuesday" into RRULE | +| RRULE → dates | Generate occurrence dates from RRULE | +| Timezone handling | Timezone-aware datetime operations | + +--- + +## Processing Pipeline + +| Step | Input | Output | +|------|-------|--------| +| 1 | Natural language date | Datetime object | +| 2 | Natural language recurrence | RRULE string (RFC 5545) | +| 3 | RRULE string | List of datetime objects | +| 4 | Datetime object | Timezone-aware datetime | + +--- + +## Language-Specific Recommendations + +### Python + +| Purpose | Library | Notes | +|---------|---------|-------| +| Natural language dates | [dateparser](https://dateparser.readthedocs.io/) | Multi-language support | +| Natural language recurrence | [recurrent](https://github.com/kvh/recurrent) | Parses to RRULE | +| RRULE processing | [python-dateutil](https://dateutil.readthedocs.io/) | Full RFC 5545 support | +| Datetime wrapper | [pendulum](https://pendulum.eustace.io/) | Timezone-aware operations | + +### JavaScript/TypeScript + +| Purpose | Library | Notes | +|---------|---------|-------| +| Natural language dates | [chrono-node](https://github.com/wanasit/chrono) | Multi-language support | +| RRULE processing | [rrule](https://github.com/jakubroztocil/rrule) | Full RFC 5545 support | +| Datetime handling | [luxon](https://moment.github.io/luxon/) | Timezone-aware operations | + +### Go + +| Purpose | Library | Notes | +|---------|---------|-------| +| RRULE processing | [teambition/rrule-go](https://github.com/teambition/rrule-go) | RFC 5545 support | +| Datetime handling | Standard `time` package | Built-in timezone support | + +### Rust + +| Purpose | Library | Notes | +|---------|---------|-------| +| RRULE processing | [rrule](https://crates.io/crates/rrule) | RFC 5545 support | +| Datetime handling | [chrono](https://crates.io/crates/chrono) | Timezone-aware operations | + +--- + +## Implementation Notes + +Implementations SHOULD: + +1. Use ISO 8601 for all date parsing and serialization +2. Support RRULE (RFC 5545) for recurrence patterns +3. Handle timezone conversion correctly +4. Preserve date precision (date-only vs datetime vs with timezone) diff --git a/docs/spec/compatibility.md b/docs/spec/compatibility.md deleted file mode 100644 index f3d84f5..0000000 --- a/docs/spec/compatibility.md +++ /dev/null @@ -1,119 +0,0 @@ -# Format Compatibility - -Conversion rules between TaskMark and other task formats. See [specification.md](../specification.md) for the core spec. - ---- - -## todo.txt Conversion - -| Element | todo.txt | TaskMark | Conversion Rule | -|---------|----------|---------|----------------| -| Priority | `(A)` at start | `(A)` after state | Move after `- [ ]` | -| Contexts | `@context` | `#context` | Replace `@` with `#` | -| Projects | `+project` | `+project` | No change | -| Assignees | Not supported | `@user` | Add if needed | -| Dates | Creation date required | Optional | Remove if unwanted | -| Sections | Not supported | `## Section` | Group by project | - -### Example Conversion - -**todo.txt:** - -``` -(A) 2024-03-10 Call client +Sales @phone due:2024-03-15 -x 2024-03-05 2024-03-01 Complete report +Reports -``` - -**TaskMark:** - -```markdown -# TODO - -## Sales -- [ ] (A) 2024-03-10 Call client +Sales #phone due:2024-03-15 - -## Reports -- [x] 2024-03-01 2024-03-05 Complete report +Reports -``` - ---- - -## todo.md Conversion - -| Element | todo.md | TaskMark | Conversion Rule | -|---------|---------|---------|----------------| -| States | `[ ]`, `[x]`, `[-]` | `[ ]`, `[x]`, `[-]`, `[!]` | Add `[!]` if needed | -| Priority | Not standardized | `(value)` after state | Add if needed | -| Metadata | Freeform | `key:value` | Standardize format | -| Recurrence | Not supported | `repeat:PATTERN` | Add if needed | -| File links | Not supported | `[text](file.md)` | Add if needed | - -### Example Conversion - -**todo.md:** - -```markdown -# Tasks - -- [ ] Important task -- [x] Completed task -- [-] Cancelled task -``` - -**TaskMark:** - -```markdown -# TODO - -## Tasks -- [ ] (A) Important task -- [x] Completed task -- [-] Cancelled task -``` - ---- - -## xit Conversion - -| Element | xit | TaskMark | Conversion Rule | -|---------|-----|---------|----------------| -| Date headers | `2024-03-15` | `## Section` | Convert to sections | -| States | `[ ]`, `[x]`, `[-]` | `[ ]`, `[x]`, `[-]`, `[!]` | Add `[!]` if needed | -| Metadata | `key:value` | `key:value` | Extract core fields | -| Priority | Not supported | `(value)` | Add if needed | -| Projects | `project:name` | `+name` | Convert syntax | - -### Example Conversion - -**xit:** - -``` -2024-03-15 -[ ] Deploy to staging project:Backend -[x] Write tests project:Backend -[-] Skip documentation -``` - -**TaskMark:** - -```markdown -# TODO - -## 2024-03-15 -- [ ] Deploy to staging +Backend -- [x] Write tests +Backend -- [-] Skip documentation -``` - ---- - -## Bidirectional Conversion Notes - -| Direction | Data Loss | Notes | -|-----------|-----------|-------| -| todo.txt → TaskMark | None | Full compatibility | -| TaskMark → todo.txt | Sections, blocked state, subtasks | Use project tags for sections | -| todo.md → TaskMark | None | Full compatibility | -| TaskMark → todo.md | Priority, metadata, recurrence | Lossy conversion | -| xit → TaskMark | None | Full compatibility | -| TaskMark → xit | Sections, assignees | Use date headers | diff --git a/docs/spec/examples.md b/docs/spec/examples.md deleted file mode 100644 index 453b054..0000000 --- a/docs/spec/examples.md +++ /dev/null @@ -1,138 +0,0 @@ -# TaskMark Examples - -Detailed examples for the TaskMark format. See [specification.md](../specification.md) for the core spec. - ---- - -## Minimal - -```markdown -# TODO -- [ ] Task -``` - ---- - -## Full Featured - -```markdown -# TODO +Acme #work - -## Backend Operations +Backend #critical - -- [ ] (A) 2024-03-10 Fix database connection @alice +Database due:2024-03-15T18:00 ~8h type:urgent ticket:ENG-4739 created:2024-03-01T10:00 started:2024-03-10T09:00 - - [ ] (B) 2024-03-10 Update connection pooling @alice ~2h - - [ ] (B) 2024-03-11 Add retry logic @bob ~3h - - [x] (C) 2024-03-09 2024-03-10 Write migration @alice ~3h - -- [ ] 2024-03-15T09:00 Daily status report @alice repeat:"weekdays at 9am" due:2024-03-15T09:30 ~30m type:report -``` - -**After Inheritance:** - -- First task: `+Acme/Backend/Database`, `#work`, `#critical` -- Subtasks: Inherit parent + section + top-level metadata -- Daily report: `+Acme/Backend`, `#work`, `#critical` - ---- - -## Date and Time Examples - -```markdown -# TODO +MyProject - -## Date-only -- [ ] (A) 2024-03-10 Start project planning -- [x] (B) 2024-03-01 2024-03-05 Complete requirements - -## Time-specific -- [ ] 2024-03-15T09:00 Morning standup due:2024-03-15T09:30 -- [ ] 2024-03-15T14:00 Client presentation due:2024-03-15T15:00 - -## With timezones -- [ ] 2024-03-15T09:00-05:00 Team call (EST) -- [ ] 2024-03-15T15:00+01:00 European meeting - -## Full lifecycle -- [ ] (A) 2024-03-10T09:00 Implement OAuth2 @alice created:2024-03-01T10:00 started:2024-03-10T09:00 due:2024-03-15T18:00 - -## Recurring -- [ ] 2024-03-18T09:00 Weekly standup repeat:"every monday at 9am" -- [ ] 2024-04-01 Monthly review repeat:"first day of month" -``` - -**Result:** All tasks inherit `+MyProject` from top-level section. - ---- - -## YAML Front Matter - -```markdown ---- -locale: en_US -timezone: America/New_York ---- - -# TODO - -## Meetings -- [ ] 2024-03-18T09:00 Weekly standup repeat:"every monday at 9am" -- [ ] 2024-04-01 Monthly review repeat:"first day of month" - -## Tasks -- [ ] (A) 2024-03-10 Start development @alice due:2024-03-15T17:00 -``` - -**Result:** All datetimes without explicit timezone use `America/New_York` (EST/EDT). - ---- - -## Custom Date Format - -```markdown ---- -locale: en_GB -date_format: "%d/%m/%Y" -timezone: Europe/London ---- - -# TODO - -- [ ] 10/03/2024 Project kickoff @alice -- [x] 01/03/2024 05/03/2024 Complete requirements @bob -``` - -**Result:** Dates are output in day/month/year format. The `date_format` overrides the locale default. - ---- - -## Multi-File with Different Front Matter - -**main.md:** - -```markdown ---- -timezone: America/New_York -date_format: "%Y-%m-%d" ---- - -# TODO +Project - -## Engineering +Engineering -[UK Team](uk-team.md) -``` - -**uk-team.md:** - -```markdown ---- -timezone: Europe/London -date_format: "%d/%m/%Y" ---- - -# UK Team Tasks - -- [ ] 15/03/2024 Sprint planning @alice -``` - -**Result:** Each file uses its own front matter. The UK team file displays dates in `dd/mm/yyyy` format with London timezone, independent of the parent file's settings. diff --git a/docs/spec/frontmatter.md b/docs/spec/frontmatter.md deleted file mode 100644 index 1fc4af9..0000000 --- a/docs/spec/frontmatter.md +++ /dev/null @@ -1,128 +0,0 @@ -# YAML Front Matter - -Detailed documentation for TaskMark YAML front matter. See [specification.md](../specification.md) for the core spec. - ---- - -## Position and Syntax - -| Constraint | Rule | -|-----------|------| -| Position | MUST appear at file start | -| Delimiters | MUST be enclosed between `---` lines | -| Syntax | MUST be valid YAML | -| Invalid YAML | MUST warn, MUST NOT fail parsing | -| Unknown fields | MUST be ignored | - ---- - -## Supported Fields - -| Field | Type | Constraint | Purpose | Example Values | -|-------|------|-----------|---------|----------------| -| `locale` | String | OPTIONAL | Locale for dateparser/recurrent | `en_US`, `fr_FR`, `de_DE` | -| `timezone` | String | OPTIONAL | Default timezone for datetime fields | `America/New_York`, `Europe/Paris`, `UTC` | -| `date_format` | String | OPTIONAL | Output date format (strftime pattern) | `%Y-%m-%d`, `%d/%m/%Y`, `%B %d, %Y` | - ---- - -## Field Behavior - -| Field | Input Parsing | Output Formatting | -|-------|--------------|-------------------| -| `locale` | Used by dateparser for natural language | If set alone, uses locale's default date format | -| `timezone` | Applied to dates without explicit timezone | Applied when serializing dates | -| `date_format` | Not used for parsing | Overrides locale default when both are set | - -**Default Output Format:** ISO 8601 (`YYYY-MM-DD`) when no `locale` or `date_format` is set. - ---- - -## Processing Order - -1. Parse YAML front matter (if present) -2. Extract `locale`, `timezone`, and `date_format` values -3. Apply to all subsequent date/time parsing in THIS FILE ONLY - ---- - -## File Scope - -| Rule | Constraint | -|------|-----------| -| Scope | Front matter settings apply ONLY to the current file | -| Linked files | Imported files via `[Link](file.md)` retain their own front matter | -| No inheritance | Parent file front matter does NOT cascade to linked files | -| Default behavior | Files without front matter use system locale/timezone and ISO 8601 output | - ---- - -## Examples - -### Basic Usage - -```markdown ---- -locale: en_US -timezone: America/New_York ---- - -# TODO - -## Meetings -- [ ] 2024-03-18T09:00 Weekly standup repeat:"every monday at 9am" -- [ ] 2024-04-01 Monthly review repeat:"first day of month" - -## Tasks -- [ ] (A) 2024-03-10 Start development @alice due:2024-03-15T17:00 -``` - -**Result:** All datetimes without explicit timezone use `America/New_York` (EST/EDT). - -### Custom Date Format - -```markdown ---- -locale: en_GB -date_format: "%d/%m/%Y" -timezone: Europe/London ---- - -# TODO - -- [ ] 10/03/2024 Project kickoff @alice -- [x] 01/03/2024 05/03/2024 Complete requirements @bob -``` - -**Result:** Dates are output in day/month/year format. The `date_format` overrides the locale default. - -### Multi-File with Different Front Matter - -**main.md:** - -```markdown ---- -timezone: America/New_York -date_format: "%Y-%m-%d" ---- - -# TODO +Project - -## Engineering +Engineering -[UK Team](uk-team.md) -``` - -**uk-team.md:** - -```markdown ---- -timezone: Europe/London -date_format: "%d/%m/%Y" ---- - -# UK Team Tasks - -- [ ] 15/03/2024 Sprint planning @alice -``` - -**Result:** Each file uses its own front matter. The UK team file displays dates in `dd/mm/yyyy` format with London timezone, independent of the parent file's settings. diff --git a/docs/spec/libraries.md b/docs/spec/libraries.md deleted file mode 100644 index 2221a14..0000000 --- a/docs/spec/libraries.md +++ /dev/null @@ -1,91 +0,0 @@ -# Library Stack - -Required libraries for TaskMark implementations. See [specification.md](../specification.md) for the core spec. - ---- - -## Required Libraries - -| Purpose | Library | Version | Usage | -|---------|---------|---------|-------| -| Natural language → datetime | [dateparser](https://dateparser.readthedocs.io/) | Latest | Parse phrases like "next Friday 3pm" | -| Natural language → recurrence | [recurrent](https://github.com/kvh/recurrent) | Latest | Parse "every Tuesday" into RRULE | -| Recurrence → dates | [python-dateutil](https://dateutil.readthedocs.io/) | Latest | Generate occurrence dates from RRULE | -| Datetime wrapper | [pendulum](https://pendulum.eustace.io/) | Latest | Timezone-aware datetime operations | - ---- - -## Processing Pipeline - -| Step | Input | Library | Output | -|------|-------|---------|--------| -| 1 | Natural language date | dateparser | Datetime object | -| 2 | Natural language recurrence | recurrent | RRULE string | -| 3 | RRULE string | python-dateutil | List of datetime objects | -| 4 | Datetime object | pendulum | Timezone-aware datetime | - ---- - -## Library Details - -### dateparser - -Parses natural language date expressions into datetime objects. - -**Capabilities:** - -- Multi-language support (50+ languages) -- Relative dates: "tomorrow", "next week", "in 3 days" -- Absolute dates: "March 15, 2024", "15/03/2024" -- Time expressions: "3pm", "15:00", "noon" - -**Usage in TaskMark:** - -- Parsing natural language dates in task descriptions -- Respecting `locale` from YAML front matter - -### recurrent - -Parses natural language recurrence patterns into RRULE strings. - -**Capabilities:** - -- Pattern recognition: "every Tuesday", "daily", "weekly" -- Complex patterns: "first Monday of month", "every other week" -- Output: iCalendar RRULE format - -**Usage in TaskMark:** - -- Parsing `repeat:` field values -- Converting to RRULE for date calculation - -### python-dateutil - -Generates occurrence dates from RRULE strings. - -**Capabilities:** - -- Full RRULE support (RFC 5545) -- Date iteration: next occurrence, all occurrences in range -- Timezone handling - -**Usage in TaskMark:** - -- Calculating next occurrence when task is completed -- Generating future task instances - -### pendulum - -Timezone-aware datetime wrapper with intuitive API. - -**Capabilities:** - -- Timezone conversion -- Date arithmetic -- Formatting with locale support - -**Usage in TaskMark:** - -- Applying `timezone` from YAML front matter -- Date offset calculations for recurring tasks -- Formatting output dates diff --git a/docs/specification.md b/docs/specification.md deleted file mode 100644 index 16a3575..0000000 --- a/docs/specification.md +++ /dev/null @@ -1,510 +0,0 @@ -# TaskMark Format Specification - -**Version:** 1.2.0 -**Format:** Markdown (.md) -**Encoding:** UTF-8 (REQUIRED) -**Line Endings:** LF (`\n`) or CRLF (`\r\n`) - ---- - -## 1. File Structure - -| Element | Constraint | Example | -|---------|-----------|---------| -| File extension | MUST be `.md` | `todo.md`, `TODO.md` | -| YAML front matter | OPTIONAL, between `---` delimiters | See §1.1 | -| First header | MUST be `# TODO` (or after front matter) | `# TODO` or `# TODO +project #tag` | -| Sections | MUST use `#` headers | `## Urgent`, `### Bugs` | -| Multiple files | ALLOWED in different directories | `project/todo.md`, `team/todo.md` | - -### 1.1 YAML Front Matter - -| Constraint | Rule | -|-----------|------| -| Position | MUST appear at file start | -| Delimiters | MUST be enclosed between `---` lines | -| Syntax | MUST be valid YAML | -| Invalid YAML | MUST warn, MUST NOT fail parsing | -| Unknown fields | MUST be ignored | - -**Supported Fields:** - -| Field | Type | Purpose | -|-------|------|---------| -| `locale` | String | Locale for dateparser/recurrent (`en_US`, `fr_FR`) | -| `timezone` | String | Default timezone (`America/New_York`, `UTC`) | -| `date_format` | String | Output date format (`%Y-%m-%d`, `%d/%m/%Y`) | - -**See [spec/frontmatter.md](spec/frontmatter.md) for detailed field behavior, processing order, and file scope rules.** - ---- - -## 2. Task Syntax - -### 2.1 Task States - -| State | Symbol | Meaning | Creates Recurrence | -|-------|--------|---------|-------------------| -| Open | `[ ]` | Pending/todo | No | -| Done | `[x]` | Completed | Yes (if `repeat:` present) | -| Cancelled | `[-]` | Cancelled/rejected | No | -| Blocked | `[!]` | Blocked/paused/on-hold | No | - -**Case Sensitivity:** State markers are case-insensitive: `[X]` = `[x]` - -### 2.2 Task Components - -| Component | Pattern | Position | Required | Case Sensitive | Valid Range | -|-----------|---------|----------|----------|----------------|-------------| -| State | `- [X]` | Line start | Yes | No | `[ ]`, `[x]`, `[-]`, `[!]` | -| Priority | `(VALUE)` | After state | No | No | Any alphanumeric | -| Planned date | `YYYY-MM-DD[THH:MM[:SS][±HH:MM]]` | After priority | No | N/A | ISO 8601 datetime | -| Done date | `YYYY-MM-DD[THH:MM[:SS][±HH:MM]]` | After planned | No | N/A | ISO 8601 datetime | -| Description | Plain text | After dates/priority/state | Yes | Yes | 1-unlimited chars | -| Assignee | `@username` | In line | No | No | `a-zA-Z0-9_-` | -| Project | `+name[/sub]` | In line | No | No | `a-zA-Z0-9_-./` | -| Tag | `#tag` | In line | No | No | `a-zA-Z0-9_-` | -| Created date | `created:DATETIME` | In line | No | N/A | ISO 8601 datetime | -| Started date | `started:DATETIME` | In line | No | N/A | ISO 8601 datetime | -| Paused date | `paused:DATETIME` | In line | No | N/A | ISO 8601 datetime | -| Due date | `due:DATETIME` | In line | No | N/A | ISO 8601 datetime | -| Estimate | `~NUM[hmd]` | In line | No | N/A | Hours/minutes/days | -| Metadata | `key:value` or `key:"value"` | In line | No | Keys: No, Values: Yes | Any text | - -**Processing Rule:** Position-based components MUST be parsed left-to-right before in-line components. - -### 2.3 Date and Time Formats - -**Accepted Formats:** - -| Format | Pattern | Example | Timezone | -|--------|---------|---------|----------| -| Date only | `YYYY-MM-DD` | `2024-03-10` | Uses front matter `timezone` or local | -| Date + time | `YYYY-MM-DDTHH:MM` | `2024-03-10T09:00` | Uses front matter `timezone` or local | -| Date + time + seconds | `YYYY-MM-DDTHH:MM:SS` | `2024-03-10T09:00:00` | Uses front matter `timezone` or local | -| Date + time + timezone | `YYYY-MM-DDTHH:MM±HH:MM` | `2024-03-10T09:00-05:00` | Explicit timezone overrides default | -| Date + time + seconds + timezone | `YYYY-MM-DDTHH:MM:SS±HH:MM` | `2024-03-10T09:00:00-05:00` | Explicit timezone overrides default | - -**Constraints:** - -| Field | Min Value | Max Value | Rule | -|-------|-----------|-----------|------| -| Year | 0001 | 9999 | MUST be 4 digits | -| Month | 01 | 12 | MUST be 2 digits | -| Day | 01 | 31 | MUST be valid for month/year | -| Hour | 00 | 23 | MUST be 2 digits (24-hour format) | -| Minute | 00 | 59 | MUST be 2 digits | -| Second | 00 | 59 | MUST be 2 digits | -| Timezone offset | -12:00 | +14:00 | MUST match `±HH:MM` pattern | - -**Invalid Date Handling:** - -| Error | Action | Example | -|-------|--------|---------| -| Malformed date | Warn at `file:line`, ignore field | `due:2024-13-99` → warning + skip | -| Invalid day | Warn at `file:line`, ignore field | `2024-02-30` → warning + skip | -| Missing required component | Warn at `file:line`, ignore field | `2024-03` → warning + skip | - -### 2.4 Character Sets - -| Token Type | Pattern | Valid Characters | Notes | -|------------|---------|------------------|-------| -| Assignee | `@NAME` | `a-zA-Z0-9_-` | MUST follow whitespace | -| Project | `+NAME` or `+NAME/SUB` | `a-zA-Z0-9_-./` | `/` for hierarchy, `.` for versions | -| Tag | `#NAME` | `a-zA-Z0-9_-` | MUST NOT be section header | -| Metadata key | `KEY:` | `a-zA-Z0-9_-` | Before colon | -| Metadata value (unquoted) | `:VALUE` | Any non-whitespace | Until whitespace | -| Metadata value (quoted) | `:"VALUE"` or `:'VALUE'` | Any characters | Until closing quote | - -### 2.5 Escaping - -| Character | Escape Sequence | Result | -|-----------|----------------|--------| -| `@` | `\@` | Literal @ (not assignee) | -| `+` | `\+` | Literal + (not project) | -| `#` | `\#` | Literal # (not tag) | -| `:` | `\:` | Literal : (not key:value) | -| `\` | `\\` | Literal backslash | -| `"` | `\"` | Quote inside double-quoted value | -| `'` | `\'` | Quote inside single-quoted value | - -### 2.6 Quoted Values - -| Format | Pattern | Parsed Value | Whitespace Allowed | -|--------|---------|--------------|-------------------| -| Unquoted | `key:value` | `value` | No (stops at space) | -| Double quotes | `key:"value with spaces"` | `value with spaces` | Yes | -| Single quotes | `key:'value with spaces'` | `value with spaces` | Yes | - -**Rules:** - -| Constraint | Rule | -|-----------|------| -| Opening quote position | MUST immediately follow colon | -| Invalid spacing | `key: "value"` is INVALID | -| Closing quote | Terminates value | -| Quote stripping | Quotes MUST NOT be part of parsed value | -| Unclosed quote | Warn at `file:line`, treat as unquoted | - ---- - -## 3. Priority - -### 3.1 Priority Sorting - -| Condition | Sort Method | Example | -|-----------|-------------|---------| -| All priorities numeric | Numeric ascending | `(1)` < `(2)` < `(10)` | -| Any priority non-numeric | Alphanumeric ascending | `(A)` < `(A1)` < `(B)` | -| Same priority | Line number ascending | First in file → first in list | -| Multiple priorities | Use first only | `(A) (B)` → priority `(A)` | - -### 3.2 Priority Values - -| Type | Examples | Sort Order | -|------|----------|------------| -| Letters | `(A)`, `(B)`, `(Z)` | Alphanumeric | -| Numbers | `(1)`, `(2)`, `(10)`, `(100)` | Numeric | -| Mixed | `(A1)`, `(P1)`, `(H)` | Alphanumeric | -| Case | `(a)` = `(A)` | Case-insensitive | - ---- - -## 4. Recurrence - -### 4.1 Recurrence Field - -| Field | Pattern | Library | Example | -|-------|---------|---------|---------| -| Repeat pattern | `repeat:PATTERN` | [recurrent](https://github.com/kvh/recurrent) | `repeat:daily`, `repeat:weekdays` | - -**Common Patterns:** - -| Pattern | Syntax | Description | RRULE Equivalent | -|---------|--------|-------------|------------------| -| Daily | `repeat:daily` | Every day | `FREQ=DAILY` | -| Weekly | `repeat:weekly` | Every week | `FREQ=WEEKLY` | -| Bi-weekly | `repeat:every-2-weeks` | Every 2 weeks | `FREQ=WEEKLY;INTERVAL=2` | -| Monthly | `repeat:monthly` | Every month | `FREQ=MONTHLY` | -| Yearly | `repeat:yearly` | Every year | `FREQ=YEARLY` | -| Weekdays | `repeat:weekdays` | Monday-Friday | `FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR` | -| Specific day | `repeat:every-tuesday` | Every Tuesday | `FREQ=WEEKLY;BYDAY=TU` | -| First of month | `repeat:first-monday-of-month` | First Monday each month | `FREQ=MONTHLY;BYDAY=1MO` | -| Last of month | `repeat:last-friday-of-month` | Last Friday each month | `FREQ=MONTHLY;BYDAY=-1FR` | - -### 4.2 Recurrence Behavior - -| Action | Old Task | New Task | Notes | -|--------|----------|----------|-------| -| Mark `[x]` with `repeat:` | State → `[x]`, add done date (2nd position), remove `repeat:` | State → `[ ]`, dates updated, keep `repeat:` | Creates new instance | -| Mark `[-]` with `repeat:` | State → `[-]`, keep `repeat:` | None | No new instance | -| Mark `[!]` with `repeat:` | State → `[!]`, keep `repeat:` | None | No new instance | -| Change `[x]` to `[ ]` | State → `[ ]`, keep done date | None | Manual cleanup required | - -### 4.3 Date Calculation Algorithm - -**Processing Steps:** - -| Step | Action | Library | -|------|--------|---------| -| 1 | Parse `repeat:` pattern | recurrent | -| 2 | Extract RRULE + DTSTART | recurrent | -| 3 | Calculate next occurrence | python-dateutil.rrule | -| 4 | Calculate offset (if both planned & due exist) | pendulum | -| 5 | Apply offset to new dates | pendulum | - -**Offset Preservation:** - -| Scenario | Calculation | Example | -|----------|-------------|---------| -| Planned date only | `new_planned = next_occurrence` | Planned: 2024-03-18 | -| `due:` only | `new_due = next_occurrence` | `due:2024-03-18` | -| Both dates | `offset = due - planned`
`new_planned = next_occurrence`
`new_due = new_planned + offset` | 4-day offset preserved | - ---- - -## 5. Sections and Inheritance - -### 5.1 Section Headers - -| Header Level | Pattern | Example | -|--------------|---------|---------| -| Top level | `# TODO` (required) | `# TODO +Acme #work @team` | -| Section | `## Section Name` | `## Backend +API #critical @alice` | -| Subsection | `### Subsection Name` | `### Database +Maintenance @bob` | - -**All headers MAY include metadata (projects, tags, assignees, key-value pairs).** - -### 5.2 Inheritance Rules - -| Metadata Type | Behavior | Example | -|---------------|----------|---------| -| Projects (`+`) | Hierarchical (join with `/`) | `+A` + `+B` = `+A/B` | -| Tags (`#`) | Additive | `#api` + `#urgent` = both tags | -| Assignees (`@`) | Additive | `@alice` + `@bob` = both assignees | -| Key-value | Override | `type:bug` overrides `type:feature` | - -**Example:** - -```markdown -# TODO +Acme #work @team - -## Backend +API #critical @alice - -- [ ] Task +Database @bob -``` - -**Task inherits:** `+Acme/API/Database`, `#work`, `#critical`, `@team`, `@alice`, `@bob` - ---- - -## 6. Subtasks - -| Property | Constraint | Notes | -|----------|-----------|-------| -| Syntax | Indented task line | Any spaces/tabs | -| Nesting level | MUST be 1 level only | Tasks → subtasks, no deeper | -| Indentation | MUST be > 0 spaces/tabs | Any amount > 0 | -| Inheritance | Inherits ALL parent metadata | Task + section metadata | -| States | Independent of parent | Can differ from parent state | -| Projects | Hierarchical join | Parent `+A`, subtask `+B` → `+A/B` | -| Tags | Additive | Parent `#api`, subtask `#urgent` → both | -| Assignees | Additive | Parent `@alice`, subtask `@bob` → both | -| Key-value | Override | Child overrides parent | - -**Invalid Nesting:** - -```markdown -- [ ] Task - - [ ] Subtask (VALID) - - [ ] Sub-subtask (INVALID - ignored or warned) -``` - ---- - -## 7. File Links - -### 7.1 Link Syntax - -| Element | Constraint | Example | -|---------|-----------|---------| -| Format | Standard Markdown link | `[Link Text](path/to/file.md)` | -| Location | MUST be standalone line | NOT inside task description | -| Link text | REQUIRED | MUST have descriptive text | -| Path type | Relative or absolute | See §7.2 | -| File extension | MUST be `.md` | Other extensions ignored | - -### 7.2 Path Resolution - -| Path Type | Pattern | Resolves To | Example | -|-----------|---------|-------------|---------| -| Relative | `file.md` | Same directory | `[Tasks](todo.md)` | -| Relative subdirectory | `dir/file.md` | Subdirectory from current | `[Team](team/tasks.md)` | -| Parent directory | `../file.md` | Parent directory | `[Main](../todo.md)` | -| Absolute | `/path/file.md` | From project root | `[Root](/project/todo.md)` | - -### 7.3 Inheritance with Links - -| Context | Rule | Example | -|---------|------|---------| -| Link in section | All linked tasks inherit section metadata | See example below | -| Link in subsection | All linked tasks inherit section + subsection | Hierarchical inheritance | -| Multiple links in section | Each link inherits same metadata | Applies to all | - -**Example:** - -```markdown -## Engineering +Enterprise #engineering @geordi type:maintenance - -[Warp Core](team/warp.md) -[Transporters](team/transport.md) -``` - -**Result:** All tasks from both files inherit: `+Enterprise`, `#engineering`, `@geordi`, `type:maintenance` - ---- - -## 8. Parsing Rules - -### 8.1 Detection Rules - -| Element | Detection Pattern | Constraint | -|---------|------------------|-----------| -| Task | Line starts with `- [ ]`, `- [x]`, `- [-]`, `- [!]` | Case-insensitive state | -| Section | Line starts with `#` + space | MUST have space after `#` | -| Priority | First `(content)` after state | Rest ignored | -| Planned date | First ISO 8601 datetime after priority/state | Before description | -| Done date | Second ISO 8601 datetime | Only if first date exists | -| Assignee | `@` + word chars | Case-insensitive | -| Project | `+` + word chars (may include `/`) | Case-insensitive | -| Tag | `#` + word chars | Case-insensitive, NOT section | -| Created date | `created:DATETIME` | ISO 8601 format | -| Started date | `started:DATETIME` | ISO 8601 format | -| Paused date | `paused:DATETIME` | ISO 8601 format | -| Due date | `due:DATETIME` | ISO 8601 format | -| Estimate | `~` + digits + `h`/`m`/`d` | No spaces | -| Metadata (unquoted) | `key:value` | Until whitespace | -| Metadata (quoted) | `key:"value"` or `key:'value'` | Until closing quote | -| File link | `[text](path.md)` on standalone line | Not inline | - -### 8.2 Error Handling - -| Error Type | Action | Log Level | Example | -|------------|--------|-----------|---------| -| Malformed date | Warn at `file:line`, ignore field | WARNING | `due:2024-13-99` | -| Multiple priorities | Use first, ignore rest | INFO | `(A) (B)` → `(A)` | -| Invalid task state | Ignore line | INFO | `- [?] Task` | -| Empty task | Parse as valid | INFO | `- [ ]` | -| Leading/trailing spaces | Trim from description | NONE | `Task` → `Task` | -| Unclosed quote | Warn at `file:line`, treat as unquoted | WARNING | `desc:"unclosed` | -| Escaped quote | Remove backslash | NONE | `\"` → `"` | -| Invalid timezone | Warn at `file:line`, use default | WARNING | `T09:00+99:00` | -| Sub-subtask | Warn at `file:line`, treat as subtask | WARNING | 3+ level nesting | - -### 8.3 Whitespace Handling - -| Context | Rule | Example | -|---------|------|---------| -| Task description | Trim leading/trailing | `Task` → `Task` | -| Between tokens | Collapse to single space | `Task @user` → `Task @user` | -| Indentation | Any amount = subtask | Spaces or tabs | -| Empty lines | Ignored | Not parsed | - ---- - -## 9. Complete Syntax Pattern - -``` -- [STATE] (PRIORITY) [PLANNED] [DONE] Description @assignee +project #tag created:DT started:DT paused:DT due:DT repeat:PATTERN ~estimate key:value key:"quoted value" - - [STATE] Subtask inherits all parent metadata -``` - -**Component Order:** - -| Position | Component | Required | Pattern | -|----------|-----------|----------|---------| -| 1 | State marker | Yes | `- [ ]`, `- [x]`, `- [-]`, `- [!]` | -| 2 | Priority | No | `(VALUE)` | -| 3 | Planned date | No | ISO 8601 datetime | -| 4 | Done date | No | ISO 8601 datetime (requires planned) | -| 5 | Description | Yes | Plain text | -| 6+ | In-line metadata | No | Any order | - -**In-line Metadata (any order):** - -| Type | Examples | -|------|----------| -| Assignees | `@user1 @user2` | -| Projects | `+proj1 +proj2/sub +app/v1.2.3` | -| Tags | `#tag1 #tag2` | -| Dates | `created:`, `started:`, `paused:`, `due:` | -| Recurrence | `repeat:PATTERN` or `repeat:"natural language"` | -| Estimate | `~2h`, `~30m`, `~3d` | -| Custom | `key:value`, `key:"quoted value"` | - ---- - -## 10. Examples - -### 10.1 Minimal - -```markdown -# TODO -- [ ] Task -``` - -### 10.2 Full Featured - -```markdown -# TODO +Acme #work - -## Backend Operations +Backend #critical - -- [ ] (A) 2024-03-10 Fix database connection @alice +Database due:2024-03-15T18:00 ~8h type:urgent - - [ ] (B) 2024-03-10 Update connection pooling @alice ~2h - - [x] (C) 2024-03-09 2024-03-10 Write migration @alice ~3h - -- [ ] 2024-03-15T09:00 Daily status report @alice repeat:"weekdays at 9am" ~30m -``` - -**After Inheritance:** - -- First task: `+Acme/Backend/Database`, `#work`, `#critical` -- Subtasks: Inherit parent + section + top-level metadata - -**See [spec/examples.md](spec/examples.md) for date/time examples, YAML front matter examples, and multi-file examples.** - ---- - -## 11. Library Stack - -| Purpose | Library | Usage | -|---------|---------|-------| -| Natural language → datetime | [dateparser](https://dateparser.readthedocs.io/) | Parse "next Friday 3pm" | -| Natural language → recurrence | [recurrent](https://github.com/kvh/recurrent) | Parse "every Tuesday" into RRULE | -| Recurrence → dates | [python-dateutil](https://dateutil.readthedocs.io/) | Generate occurrence dates | -| Datetime wrapper | [pendulum](https://pendulum.eustace.io/) | Timezone-aware operations | - -**See [spec/libraries.md](spec/libraries.md) for detailed library usage and processing pipeline.** - ---- - -## 12. Common Conventions (NOT Core Spec) - -| Key | Common Values | Type | Example | -|-----|---------------|------|---------| -| `type` | `bug`, `feature`, `docs`, `refactor`, `test` | Enum | `type:bug` | -| `epic` | Epic/initiative name | String | `epic:auth-v2` | -| `ticket` | Issue tracker ID | String | `ticket:PROJ-123` | -| `sprint` | Sprint number/name | String/Number | `sprint:24` | -| `milestone` | Release version | String | `milestone:v2.0` | -| `url` | Full URL | URL | `url:https://example.com/issue/123` | -| `version` | Version number | Semver | `version:1.2.3` | -| `priority` | Priority name | String | `priority:high` | -| `status` | Status name | String | `status:in-progress` | - -**Note:** These are conventions only. Parsers MAY ignore or handle differently. - ---- - -## 13. Implementation Requirements - -| Requirement | Constraint | Error Handling | -|-------------|-----------|----------------| -| File encoding | MUST be UTF-8 | Fail on non-UTF-8 | -| Line endings | MUST accept LF and CRLF | Auto-detect | -| YAML front matter | MUST detect `---` delimiters | Warn on invalid YAML, continue | -| Locale handling | MUST use front matter `locale` if present | Default to system locale | -| Timezone handling | MUST use front matter `timezone` as default | Default to system timezone | -| Date parsing | MUST parse ISO 8601: `YYYY-MM-DD[THH:MM[:SS][±HH:MM]]` | Warn + skip invalid | -| Position-based dates | MUST recognize: 1st datetime = planned, 2nd = done | Parse left-to-right | -| Natural language dates | SHOULD use dateparser | Optional feature | -| Recurrence parsing | MUST use recurrent library | Fail if library unavailable | -| Recurrence calculation | MUST use python-dateutil.rrule | Fail if library unavailable | -| Datetime wrapper | SHOULD use pendulum | Optional but recommended | -| Malformed dates | MUST warn with `file:line:column` | Continue parsing | -| Case matching | MUST treat case-insensitive: `@User` = `@user` | Normalize to lowercase | -| Case preservation | MUST preserve original case for display | Store original | -| Priority parsing | MUST use first priority only | Ignore rest | -| Subtask depth | MUST reject > 1 level | Warn + treat as 1-level | -| State recognition | MUST recognize only: `[ ]`, `[x]`, `[-]`, `[!]` | Ignore other states | -| Quoted values | MUST support both `"` and `'` | Warn on unclosed | -| Quote escaping | MUST support `\"` and `\'` | Remove backslash | - ---- - -## 14. Format Compatibility - -TaskMark supports conversion to/from other task formats: - -| Format | Full Compatibility | Notes | -|--------|-------------------|-------| -| todo.txt | → TaskMark: Yes | Contexts become tags | -| todo.md | → TaskMark: Yes | Add priority, metadata | -| xit | → TaskMark: Yes | Date headers become sections | - -**See [spec/compatibility.md](spec/compatibility.md) for detailed conversion rules and examples.** - ---- - -**End of Specification** diff --git a/spec/examples.md b/spec/examples.md new file mode 100644 index 0000000..cba0d71 --- /dev/null +++ b/spec/examples.md @@ -0,0 +1,132 @@ +# Examples + +Format examples. See [specification.md](specification.md). + +--- + +## Minimal + +```markdown +# Tasks +- [ ] Task +``` + +--- + +## States + +```markdown +- [ ] Open task +- [.] In progress @alice +- [x] Done done:2024-03-10 +- [-] Cancelled +- [!] Blocked +``` + +--- + +## Full Task + +```markdown +- [ ] (A) Task +Project @alice #critical ~8h planned:2024-03-10 due:2024-03-15 type:feature + - [ ] (B) Subtask @bob ~2h + - [ ] Subtask 2 ~3h + - Note for context + - Another note #repeat +``` + +--- + +## Inheritance + +```markdown +# Project +Acme #work @team + +## Backend +API #critical @alice + +- [ ] Task +# Inherits: +Acme/API, #work #critical, @team @alice +``` + +--- + +## Recurring + +```markdown +- [ ] Weekly review repeat:weekly + - [ ] Check metrics #repeat + - Personal notes + +# After completion: +- [ ] Weekly review repeat:weekly planned:2024-03-22 + - [ ] Check metrics #repeat +- [x] Weekly review done:2024-03-15T14:30 + - [ ] Check metrics #repeat + - Personal notes +``` + +--- + +## Dates + +```markdown +- [ ] Task planned:2024-03-10 +- [ ] Meeting planned:2024-03-15T09:00 due:2024-03-15T09:30 +- [ ] Call planned:2024-03-15T09:00-05:00 +``` + +--- + +## Frontmatter + +```markdown +--- +timezone: America/New_York +datetime_format: "%d/%m/%Y[ %H:%M]" +--- + +# Tasks +- [ ] Task planned:15/03/2024 +``` + +--- + +## Multi-File + +**main.md:** + +```markdown +# Project +Project + +## Engineering +[Backend](team/backend.md) +``` + +**team/backend.md:** + +```markdown +# Backend Tasks +- [ ] API design @alice +``` + +Backend inherits `+Project` from parent section. + +--- + +## Custom Metadata + +```markdown +- [ ] Task type:bug priority:high ticket:ENG-123 sprint:24 +``` + +--- + +## Notes + +```markdown +- [ ] Task + - [ ] Subtask + - Note text + continuation line + - Another note #repeat +``` diff --git a/spec/frontmatter.md b/spec/frontmatter.md new file mode 100644 index 0000000..f093312 --- /dev/null +++ b/spec/frontmatter.md @@ -0,0 +1,72 @@ +# YAML Front Matter + +Configuration for TaskMark files. See [specification.md](specification.md). + +--- + +## Syntax + +| Constraint | Rule | +|-----------|------| +| Position | File start | +| Delimiters | `---` lines | +| Invalid YAML | Warn, continue parsing | +| Unknown fields | Ignore | + +--- + +## Fields + +| Field | Type | Example | +|-------|------|---------| +| `locale` | String | `en_US`, `en_GB` | +| `timezone` | String | `America/New_York`, `UTC` | +| `datetime_format` | String | `%d/%m/%Y[ %H:%M]` | + +--- + +## `datetime_format` Syntax + +Strftime pattern with optional bracket syntax. + +| Pattern | Output | +|---------|--------| +| `%d/%m/%Y` | `15/03/2024` | +| `%d/%m/%Y %H:%M` | `15/03/2024 09:00` | +| `%d/%m/%Y[ %H:%M]` | `15/03/2024` or `15/03/2024 09:00` | + +**Brackets:** Content inside `[...]` included only if time component exists. + +--- + +## Date Parsing Precedence + +| Priority | Source | +|----------|--------| +| 1 | `datetime_format` from frontmatter | +| 2 | `locale` default format | +| 3 | ISO 8601 | +| 4 | System locale | +| 5 | Keep as string (emit WARNING) | + +Mixed formats allowed per file. Unparseable dates preserved as strings. + +--- + +## Serialization Output + +| Condition | Format | +|-----------|--------| +| `datetime_format` set | Custom pattern | +| `locale` set | Locale default | +| No frontmatter | ISO 8601 | + +--- + +## Scope + +| Rule | Constraint | +|------|-----------| +| Applies to | Current file only | +| Linked files | Own frontmatter | +| Inheritance | None | diff --git a/spec/mutation.md b/spec/mutation.md new file mode 100644 index 0000000..22492d6 --- /dev/null +++ b/spec/mutation.md @@ -0,0 +1,95 @@ +# Mutation Rules + +Task update behavior. See [specification.md](specification.md). + +--- + +## Allowed Actions + +| Action | Allowed | +|--------|---------| +| Change state | Yes | +| Add/update dates | Yes | +| Add/remove subtasks, notes | Yes | +| Change priority | Yes | +| Add/remove assignees, tags | Yes | + +--- + +## Forbidden Actions + +| Action | Error | +|--------|-------| +| Change inherited metadata | `CannotMutateInheritedProperty` | +| Move task between sections | `CannotMoveTask` | +| Modify section headers | N/A | +| Modify linked file frontmatter | N/A | + +--- + +## State Change Effects + +| Transition | Auto-set Field | +|------------|----------------| +| → `[.]` | `started:` (if null) | +| → `[!]` | `paused:` (always) | +| → `[x]` | `done:`, `planned:` (if null) | +| → `[-]` | None | + +All state transitions are valid. + +--- + +## Recurring Task Completion + +When task with `repeat:` marked `[x]`: + +**New task (above):** + +| Property | Value | +|----------|-------| +| State | `[ ]` | +| Dates | Calculated from pattern | +| `repeat:` | Preserved | +| `#repeat` items | Copied | + +**Completed task:** + +| Property | Value | +|----------|-------| +| State | `[x]` | +| `done:` | Current datetime | +| `repeat:` | Removed | +| All items | Kept | + +--- + +## Date Normalization + +On mutation, dates normalize to frontmatter format: + +| Setting | Output | +|---------|--------| +| `datetime_format` | Custom pattern | +| `locale` only | Locale default | +| None | ISO 8601 | + +--- + +## Processing Order + +| Step | Action | +|------|--------| +| 1 | Process BOTTOM to TOP | +| 2 | Reserialize in place | + +--- + +## Multi-File + +| File | Behavior | +|------|----------| +| Parent | Own frontmatter | +| Linked | Own frontmatter | +| Inheritance | Metadata flows down | +| Frontmatter | Does NOT flow | diff --git a/spec/notes.md b/spec/notes.md new file mode 100644 index 0000000..581c7c9 --- /dev/null +++ b/spec/notes.md @@ -0,0 +1,95 @@ +# Notes + +Plain text annotations under tasks. See [specification.md](specification.md) for core spec. + +--- + +## Definition + +| Property | Value | +|----------|-------| +| Content | Plain text (no metadata parsing) | +| State | None | +| Attachment | Parent task only (not subtasks) | +| Special | `#repeat` tag (word boundary match) | + +--- + +## Detection + +A note is an indented line starting with `-` that is NOT a valid subtask. + +| Pattern | Result | +|---------|--------| +| `- [ ] text` | Subtask | +| `- [.] text` | Subtask | +| `- [x] text` | Subtask | +| `- [-] text` | Subtask | +| `- [!] text` | Subtask | +| `- text` | Note | +| `- [z] text` | Note (invalid state) | + +**Indentation:** Any whitespace > 0 under a task. + +**Invalid states:** `- [anything]` where `anything` is not ``, `.`, `x`, `-`, `!` is treated as a note. Preserved verbatim on serialization. + +--- + +## Multiline Notes + +```markdown +- [ ] Task + - Note line one + continuation (more indented, no dash) + more continuation + - New note (dash at note indent) +``` + +| Rule | Behavior | +|------|----------| +| Continuation | More indented than note, no `-` prefix | +| Ends at | Same/less indent, or line starting with `-` | +| Blank lines | End the note | + +--- + +## The `#repeat` Tag + +Notes containing `#repeat` are copied when a recurring task completes. + +**Matching:** Word boundary required. Matches `#repeat` but not `#repeated` or `foo#repeat`. + +```markdown +# Before: +- [ ] Weekly review repeat:weekly + - Check metrics #repeat + - Personal reminder + +# After marking [x]: +- [ ] Weekly review repeat:weekly planned:2024-03-22 + - Check metrics #repeat +- [x] Weekly review done:2024-03-15T14:30 + - Check metrics #repeat + - Personal reminder +``` + +--- + +## Serialization + +| Order | Content | +|-------|---------| +| 1 | Subtasks (by priority, then original order) | +| 2 | Notes (original order) | + +Notes reorder after subtasks but preserve relative order among themselves. + +--- + +## Error Handling + +| Condition | Action | +|-----------|--------| +| Empty note (`-`) | Ignore | +| No parent task | Treat as markdown text | +| Under subtask | Attach to parent task instead | diff --git a/spec/recurrence.md b/spec/recurrence.md new file mode 100644 index 0000000..00f3ec8 --- /dev/null +++ b/spec/recurrence.md @@ -0,0 +1,73 @@ +# Recurrence + +Recurring task patterns. See [specification.md](specification.md). + +--- + +## Syntax + +`repeat:PATTERN` or `repeat:"complex pattern"` + +--- + +## Patterns + +| Pattern | RRULE | +|---------|-------| +| `daily` | `FREQ=DAILY` | +| `weekly` | `FREQ=WEEKLY` | +| `every-2-weeks` | `FREQ=WEEKLY;INTERVAL=2` | +| `monthly` | `FREQ=MONTHLY` | +| `yearly` | `FREQ=YEARLY` | +| `weekdays` | `FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR` | +| `every-tuesday` | `FREQ=WEEKLY;BYDAY=TU` | +| `first-monday-of-month` | `FREQ=MONTHLY;BYDAY=1MO` | +| `last-friday-of-month` | `FREQ=MONTHLY;BYDAY=-1FR` | + +--- + +## Completion Behavior + +| Action | Old Task | New Task | +|--------|----------|----------| +| Mark `[x]` | Done, remove `repeat:` | Created above, keep `repeat:` | +| Mark `[-]` | Cancelled, remove `repeat:` | None | +| Mark `[!]` | Blocked, keep `repeat:` | None | + +--- + +## Date Calculation + +| Step | Action | +|------|--------| +| 1 | Parse pattern to RRULE | +| 2 | Calculate next occurrence | +| 3 | Preserve offset (due - planned) | + +--- + +## Offset Preservation + +| Dates | Calculation | +|-------|-------------| +| Planned only | `new_planned = next` | +| Due only | `new_due = next` | +| Both | `offset = due - planned`, apply to new | + +--- + +## `#repeat` Items + +| Element | With `#repeat` | Without | +|---------|----------------|---------| +| Subtask | Copied | Not copied | +| Note | Copied | Not copied | + +--- + +## Errors + +| Case | Action | +|------|--------| +| Invalid pattern | Warn, treat as non-recurring | +| No DTSTART | Use `planned:` or current date | diff --git a/spec/serialization.md b/spec/serialization.md new file mode 100644 index 0000000..5e41525 --- /dev/null +++ b/spec/serialization.md @@ -0,0 +1,69 @@ +# Serialization + +Task output format. See [specification.md](specification.md). + +--- + +## Task Order + +``` +- [STATE] (PRIORITY) Description +projects @assignees #tags ~estimate dates repeat: custom: + - Subtasks (sorted) + - Notes (original order) +``` + +--- + +## Metadata Order + +| Position | Component | +|----------|-----------| +| 1 | `+projects` | +| 2 | `@assignees` | +| 3 | `#tags` | +| 4 | `~estimate` | +| 5 | Dates: `created:`, `planned:`, `started:`, `paused:`, `due:`, `done:` | +| 6 | `repeat:` | +| 7 | Custom keys (alphabetical) | + +--- + +## Sorting + +| Element | Order | +|---------|-------| +| Subtasks | Priority ascending, then line number | +| Notes | After subtasks, original order | +| Custom keys | Alphabetical | + +--- + +## Date Output + +| Scenario | Format | +|----------|--------| +| No frontmatter TZ | Preserve explicit TZ | +| Frontmatter TZ set | Convert, strip TZ | +| Date only | `YYYY-MM-DD` | +| Datetime | `YYYY-MM-DDTHH:MM` | + +--- + +## Values + +| Condition | Format | +|-----------|--------| +| Has spaces | `key:"value with spaces"` | +| Simple | `key:value` | +| Multiple | Space-separated | + +--- + +## Whitespace + +| Element | Rule | +|---------|------| +| Description | Trim | +| Between tokens | Single space | +| Indentation | 2 spaces or preserve | +| Empty notes | Skip | diff --git a/spec/specification.md b/spec/specification.md new file mode 100644 index 0000000..a19927b --- /dev/null +++ b/spec/specification.md @@ -0,0 +1,301 @@ +# TaskMark Format Specification + +**Version:** 1.2.0 +**Encoding:** UTF-8 +**Extension:** `.md` + +--- + +## File Structure + +| Element | Rule | +|---------|------| +| YAML front matter | OPTIONAL, between `---` | +| Headers | `#`, `##`, `###` for sections | +| File links | `[text](file.md)` on standalone line | + +See [frontmatter.md](frontmatter.md). + +--- + +## Task States + +| Symbol | State | On Completion | +|--------|-------|---------------| +| `[ ]` | Open | — | +| `[.]` | In Progress | — | +| `[x]` | Done | Creates recurrence if `repeat:` | +| `[-]` | Cancelled | — | +| `[!]` | Blocked | — | + +`[X]` = `[x]` (case-insensitive). + +--- + +## Task Pattern + +``` +- [STATE] (PRIORITY) Description +project @assignee #tag ~estimate dates repeat: custom: +``` + +| Component | Pattern | Required | +|-----------|---------|----------| +| State | `- [X]` | Yes | +| Priority | `(VALUE)` | No | +| Description | Plain text | Yes | + +--- + +## Metadata + +| Type | Pattern | Example | +|------|---------|---------| +| Project | `+name[/sub]` | `+Acme/Backend` | +| Assignee | `@username` | `@alice` | +| Tag | `#tag` | `#critical` | +| Estimate | `~NUM[UNIT]` | `~2h`, `~30min` | +| Created | `created:DATETIME` | `created:2024-03-01` | +| Planned | `planned:DATETIME` | `planned:2024-03-10` | +| Started | `started:DATETIME` | `started:2024-03-10` | +| Paused | `paused:DATETIME` | `paused:2024-03-11` | +| Due | `due:DATETIME` | `due:2024-03-15` | +| Done | `done:DATETIME` | `done:2024-03-12` | +| Repeat | `repeat:PATTERN` | `repeat:weekly` | +| Custom | `key:value` | `type:bug` | + +All tokens case-insensitive except values. + +--- + +## Character Sets + +| Token | Valid Characters | +|-------|------------------| +| `@assignee` | `a-zA-Z0-9_-` | +| `+project` | `a-zA-Z0-9_-./` | +| `#tag` | `a-zA-Z0-9_-` | +| Metadata key | `a-zA-Z0-9_-` | +| Unquoted value | Non-whitespace | +| Quoted value | Any (`key:"value"`) | + +--- + +## Estimate Units + +| Unit | Aliases | +|------|---------| +| Hours | `h`, `hour`, `hours` | +| Minutes | `m`, `min`, `minute`, `minutes` | +| Days | `d`, `day`, `days` | + +Decimals allowed (`~1.5h`). Serialize with shortest alias. + +--- + +## Escaping + +| Escape | Result | +|--------|--------| +| `\@` | Literal @ | +| `\+` | Literal + | +| `\#` | Literal # | +| `\:` | Literal : | +| `\\` | Literal \ | +| `\"` | Quote in double-quoted value | + +--- + +## Dates + +| Format | Example | +|--------|---------| +| ISO 8601 date | `2024-03-10` | +| ISO 8601 datetime | `2024-03-10T09:00` | +| With seconds | `2024-03-10T09:00:00` | +| With timezone | `2024-03-10T09:00-05:00` | +| Custom | Per `datetime_format` | + +### Parsing Precedence + +| Priority | Source | +|----------|--------| +| 1 | `datetime_format` from frontmatter | +| 2 | `locale` from frontmatter | +| 3 | ISO 8601 | +| 4 | System locale | +| 5 | Keep as string (WARNING) | + +### Serialization + +| Condition | Output | +|-----------|--------| +| `datetime_format` set | Custom format | +| `locale` set | Locale default | +| No frontmatter | ISO 8601 | + +See [frontmatter.md](frontmatter.md). + +--- + +## Subtasks + +| Rule | Constraint | +|------|------------| +| Detection | Indented `- [STATE]` under task | +| Nesting | 1 level only | +| `+project` | FORBIDDEN | +| `repeat:` | FORBIDDEN | +| `@assignee`, `#tag` | Propagates to parent | +| `#repeat` | RESERVED for recurrence | + +See [subtasks.md](subtasks.md). + +--- + +## Notes + +| Pattern | Result | +|---------|--------| +| `- [ ] text` | Subtask | +| `- text` | Note | +| `more text` | Continuation | + +| Rule | Constraint | +|------|------------| +| Metadata | None (plain text) | +| `#repeat` | RESERVED for recurrence | + +See [notes.md](notes.md). + +--- + +## Inheritance + +| Type | Behavior | +|------|----------| +| `+project` | Hierarchical join (`+A/B/C`) | +| `#tag` | Additive | +| `@assignee` | Additive | +| `key:value` | Child overrides parent | + +Flows: `#` → `##` → `###` → task → subtask. + +--- + +## File Links + +| Rule | Constraint | +|------|------------| +| Syntax | `[text](path/to/file.md)` | +| Location | Standalone line only | +| Extension | MUST be `.md` | +| Inheritance | Linked tasks inherit section metadata | + +--- + +## Recurrence + +| Pattern | RRULE | +|---------|-------| +| `daily` | FREQ=DAILY | +| `weekly` | FREQ=WEEKLY | +| `weekdays` | BYDAY=MO,TU,WE,TH,FR | +| `monthly` | FREQ=MONTHLY | +| `"first monday"` | Natural language | + +### On `[x]` Completion + +| Old Task | New Task | +|----------|----------| +| State → `[x]` | State → `[ ]` | +| Add `done:` | Calculate dates | +| Remove `repeat:` | Keep `repeat:` | + +See [recurrence.md](recurrence.md). + +--- + +## Priority + +| Condition | Sort | +|-----------|------| +| All numeric | `(1)` < `(2)` < `(10)` | +| Any non-numeric | `(A)` < `(B)` < `(C)` | +| Same priority | Line number | +| Multiple | Use first | + +--- + +## Mutation + +| Action | Allowed | +|--------|---------| +| Change state | Yes | +| Add/update dates | Yes | +| Add/remove subtasks, notes | Yes | +| Change inherited metadata | No | +| Move between sections | No | + +See [mutation.md](mutation.md). + +--- + +## Serialization Order + +| Position | Component | +|----------|-----------| +| 1 | `+projects` | +| 2 | `@assignees` | +| 3 | `#tags` | +| 4 | `~estimate` | +| 5 | Dates: `created:`, `planned:`, `started:`, `paused:`, `due:`, `done:` | +| 6 | `repeat:` | +| 7 | Custom keys (alphabetical) | + +Subtasks: priority ascending, then line number. +Notes: after subtasks, original order. + +See [serialization.md](serialization.md). + +--- + +## Error Handling + +Parser MUST NOT fail unless file is unparseable. + +| Error | Action | Level | +|-------|--------|-------| +| Malformed date | Preserve as string | WARNING | +| Invalid state | Treat as note | INFO | +| Multiple priorities | Use first | INFO | +| Unclosed quote | Treat as unquoted | WARNING | +| Sub-subtask | Flatten to 1 level | WARNING | +| Invalid YAML | Continue parsing | WARNING | +| `+project` on subtask | Ignore | WARNING | +| `repeat:` on subtask | Ignore | WARNING | + +--- + +## Implementation + +| Requirement | Constraint | +|-------------|------------| +| Encoding | UTF-8 only | +| Line endings | LF or CRLF | +| Case matching | Insensitive, preserve original | +| State symbols | `[ ]`, `[.]`, `[x]`, `[-]`, `[!]` only | + +--- + +## Related Specs + +| Spec | Content | +|------|---------| +| [frontmatter.md](frontmatter.md) | YAML, timezone, locale | +| [subtasks.md](subtasks.md) | Propagation, validation | +| [notes.md](notes.md) | Detection, `#repeat` | +| [mutation.md](mutation.md) | State changes, recurrence | +| [serialization.md](serialization.md) | Output format | +| [recurrence.md](recurrence.md) | RRULE, date calculation | +| [examples.md](examples.md) | Format examples | +| [compatibility.md](../docs/compatibility.md) | todo.txt, xit conversion | diff --git a/spec/subtasks.md b/spec/subtasks.md new file mode 100644 index 0000000..991308b --- /dev/null +++ b/spec/subtasks.md @@ -0,0 +1,107 @@ +# Subtasks + +Indented tasks under a parent. See [specification.md](specification.md). + +--- + +## Definition + +| Property | Rule | +|----------|------| +| Nesting | 1 level only | +| `+project` | FORBIDDEN | +| `repeat:` | FORBIDDEN | +| Identity | Component of parent task | + +--- + +## Detection + +| Pattern | Result | +|---------|--------| +| Indented `- [ ]` | Subtask (open) | +| Indented `- [.]` | Subtask (in progress) | +| Indented `- [x]` | Subtask (done) | +| Indented `- [-]` | Subtask (cancelled) | +| Indented `- [!]` | Subtask (blocked) | +| Indented `- text` | Note | + +**Indentation:** Whitespace > 0 + +--- + +## Properties + +| Property | Behavior | +|----------|----------| +| `@assignee` | Propagates to parent | +| `#tag` | Propagates to parent | +| `#repeat` | RESERVED (copied on recurrence) | +| `~estimate` | Validated against parent | +| Dates | Independent | +| Priority | Sorting only | + +--- + +## Propagation + +Subtask `@assignee` and `#tag` add to parent during parsing. + +```markdown +- [ ] Task @alice + - [ ] Subtask @bob #urgent +# Parent has: @alice @bob #urgent +``` + +--- + +## Inheritance + +Subtasks inherit section + parent metadata: + +| Type | Behavior | +|------|----------| +| Projects | Hierarchical join | +| Tags | Additive | +| Assignees | Additive | +| Estimate | NOT inherited | + +--- + +## Validation + +| Condition | Level | +|-----------|-------| +| Subtask estimates > parent | WARNING | +| Subtask `planned` < parent `planned` | WARNING | +| Subtask `due` > parent `due` | WARNING | +| Parent no estimate, subtasks have | INFO | + +Values kept as-is after warning. + +--- + +## Completion + +| Event | Behavior | +|-------|----------| +| Parent marked `[x]` | Subtasks unchanged | +| All subtasks marked `[x]` | Parent unchanged | +| Subtask marked `[x]` | Sets `done:` datetime | + +--- + +## Serialization + +| Order | Sort | +|-------|------| +| 1 | Priority ascending (A < B < none) | +| 2 | Original line number | + +--- + +## Invalid Nesting + +| Error | Action | +|-------|--------| +| Sub-subtask | Warn, flatten to 1 level | diff --git a/tests/CATALOG.md b/tests/CATALOG.md new file mode 100644 index 0000000..021b3e6 --- /dev/null +++ b/tests/CATALOG.md @@ -0,0 +1,440 @@ +# Golden File Test Catalog + +Conformance tests for TaskMark parsers and mutators. + +--- + +## Test Structure + +``` +tests/ +├── CATALOG.md # This file +├── TESTING.md # How golden tests work +└── T{NN}_{name}/ + ├── input.md # Root file to parse + ├── input_{linked}.md # Optional linked files + ├── parsed.yaml # Expected parse output + ├── mutation.yaml # Mutations to apply + ├── mutated.md # Expected output after mutation + └── mutated_{linked}.md # Optional mutated linked files +``` + +Each test is a complete multi-stage flow: **parse → verify → mutate → serialize → verify**. + +--- + +## Quick Reference + +| Test | Purpose | +|------|---------| +| T01_minimal | Simplest valid file | +| T02_all_states | All 5 states + transitions | +| T03_metadata_full | All metadata types | +| T04_inheritance | Section hierarchy | +| T05_subtasks_notes | Subtasks and notes | +| T06_frontmatter | Multi-file with locales/timezones | +| T07_recurrence | Recurring tasks | +| T08_multi_file | File links + cross-file inheritance | +| T09_escaping | Escape sequences | +| T10_edge_cases | Parser resilience | +| T11_locales | 8 language date formats | +| T12_team_standup | Real-world team standup example | +| T13_sprint_planning | Sprint planning with recurrence | +| T14_custom_date_format | UK locale with custom datetime format | +| T15_comprehensive | Full feature coverage example | + +--- + +## Detailed Coverage + +### T01: Minimal + +**Purpose:** Absolute minimum valid TaskMark file. + +| Feature | Tested | Details | +|---------|--------|---------| +| Parse single task | ✅ | No metadata | +| Markdown header | ✅ | `# Tasks` section | +| State `[ ]` open | ✅ | Default state | +| Mutation: state change | ✅ | `open` → `done` | +| Auto-add `done:` date | ✅ | On completion | + +--- + +### T02: All States + +**Purpose:** All 5 task states with transitions. + +| Feature | Tested | Details | +|---------|--------|---------| +| State `[ ]` open | ✅ | | +| State `[.]` in_progress | ✅ | With `started:` | +| State `[x]` done | ✅ | Lowercase | +| State `[X]` done | ✅ | Uppercase (case insensitive) | +| State `[-]` cancelled | ✅ | | +| State `[!]` blocked | ✅ | With `paused:` | +| Transition: open → in_progress | ✅ | Adds `started:` | +| Transition: in_progress → blocked | ✅ | Adds `paused:`, keeps `started:` | +| Transition: blocked → done | ✅ | Adds `done:`, keeps `paused:` | +| Transition: cancelled → open | ✅ | Reopens | +| Transition: done → open | ✅ | Clears `done:` | +| Transition: in_progress → open | ✅ | Clears `started:` | +| Transition: blocked → open | ✅ | Clears `paused:` | + +--- + +### T03: Metadata Full + +**Purpose:** All metadata types on tasks. + +| Feature | Tested | Details | +|---------|--------|---------| +| Priority `(A)` letter | ✅ | | +| Priority `(1)` numeric | ✅ | | +| Project `+Name` | ✅ | | +| Project hierarchy `+A/B` | ✅ | | +| Assignee `@name` | ✅ | Multiple | +| Tag `#name` | ✅ | Multiple | +| Estimate `~Nh` hours | ✅ | | +| Estimate `~Nm` minutes | ✅ | | +| Estimate `~Nd` days | ✅ | | +| Date `created:` | ✅ | Parse only | +| Date `planned:` | ✅ | | +| Date `started:` | ✅ | | +| Date `paused:` | ✅ | | +| Date `due:` | ✅ | | +| Date `done:` | ✅ | | +| Custom field `key:value` | ✅ | | +| Custom field `key:"quoted"` | ✅ | | +| Mutation: change priority | ✅ | | +| Mutation: change assignees | ✅ | Add/remove | +| Mutation: change tags | ✅ | Add/remove | +| Mutation: change estimate | ✅ | | +| Mutation: change custom fields | ✅ | Add/modify | +| Mutation: remove all assignees | ✅ | Set to `[]` | +| Mutation: remove all tags | ✅ | Set to `[]` | +| Mutation: change project | ✅ | | +| Mutation: change dates | ❌ | NOT TESTED (only auto-set) | + +--- + +### T04: Inheritance + +**Purpose:** Metadata inheritance through section hierarchy. + +| Feature | Tested | Details | +|---------|--------|---------| +| Header `#` level 1 | ✅ | | +| Header `##` level 2 | ✅ | | +| Header `###` level 3 | ✅ | | +| Project hierarchical join | ✅ | `+A` + `+B` = `A/B` | +| Tag additive | ✅ | Parent + child merged | +| Tag deduplication | ✅ | No duplicates | +| Assignee additive | ✅ | Parent + child merged | +| Assignee deduplication | ✅ | No duplicates | +| Custom field override | ✅ | Child replaces parent | +| Explicit vs inherited tracking | ✅ | Separate fields | +| Priority inheritance | ❌ | NOT TESTED | +| Estimate inheritance | ❌ | NOT TESTED | + +--- + +### T05: Subtasks and Notes + +**Purpose:** Subtask and note parsing rules. + +| Feature | Tested | Details | +|---------|--------|---------| +| Subtask detection | ✅ | Indented `- [STATE]` | +| Subtask all 5 states | ✅ | | +| Note detection | ✅ | Indented `- text` (no brackets) | +| Invalid state as note | ✅ | `[invalid]` → note | +| Subtask @assignee → parent | ✅ | Propagates up | +| Subtask #tag → parent | ✅ | Propagates up | +| `#repeat` on subtask | ✅ | NOT propagated to parent | +| `#repeat` on note | ✅ | `has_repeat_tag: true` | +| Subtask completion | ✅ | Sets `done:` | +| Parent completion | ✅ | Doesn't auto-complete subtasks | +| Subtask priority → parent | ❌ | NOT TESTED | +| Multi-level subtasks | ✅ | Subtask of subtask | + +--- + +### T06: Frontmatter (Multi-File) + +**Purpose:** Multi-file frontmatter with different locales and timezones. + +| Feature | Tested | Details | +|---------|--------|---------| +| Frontmatter parsing | ✅ | YAML in `---` | +| `locale` setting | ✅ | en_US, en_GB, ja_JP | +| `timezone` setting | ✅ | America/New_York, Europe/London, Asia/Tokyo | +| `date_format` setting | ✅ | Various strftime formats | +| No frontmatter (ISO default) | ✅ | Root file | +| Locale-specific date parsing | ✅ | | +| Locale-specific date serialization | ✅ | | +| UTC date conversion | ✅ | `Z` suffix | +| Explicit TZ date conversion | ✅ | `-05:00` suffix | +| Time component in dates | ✅ | Optional `[ %H:%M]` | + +--- + +### T07: Recurrence + +**Purpose:** Recurring task behavior on state changes. + +| Feature | Tested | Details | +|---------|--------|---------| +| `repeat:daily` | ✅ | Parse only | +| `repeat:weekly` | ✅ | Full test | +| `repeat:monthly` | ✅ | Advances by 1 month | +| `repeat:yearly` | ✅ | Advances by 1 year | +| `repeat:every-N-days` | ❌ | NOT TESTED | +| Completion creates new task | ✅ | Above completed | +| Completion removes `repeat:` | ✅ | From completed task | +| `#repeat` subtasks copied | ✅ | | +| `#repeat` notes copied | ✅ | | +| Non-`#repeat` stays with completed | ✅ | | +| Date advancement | ✅ | `planned:`, `due:` | +| Cancel removes `repeat:` | ✅ | No new task | +| Block keeps `repeat:` | ✅ | No new task, adds `paused:` | + +--- + +### T08: Multi-File + +**Purpose:** File link parsing and cross-file inheritance. + +| Feature | Tested | Details | +|---------|--------|---------| +| File link `[[file.md]]` | ✅ | | +| Linked file inheritance | ✅ | From link location section | +| Linked file own frontmatter | ✅ | | +| Cross-file mutation | ✅ | Saves to correct file | +| Cross-file date format | ✅ | Each file uses own | +| Broken file link | ✅ | Error E005 | +| Circular file links | ❌ | NOT TESTED | + +--- + +### T09: Escaping + +**Purpose:** All escape sequences. + +| Feature | Tested | Details | +|---------|--------|---------| +| `\@` literal @ | ✅ | Not assignee | +| `\+` literal + | ✅ | Not project | +| `\#` literal # | ✅ | Not tag | +| `\~` literal ~ | ✅ | Not estimate | +| `\:` literal : | ✅ | Not key:value | +| Mixed escaped + real | ✅ | `+Project \@literal` | +| Unicode preserved | ✅ | café, résumé | +| Emoji preserved | ✅ | 🎯 📝 ✅ | +| HTML entities preserved | ✅ | `<>&"'` | +| Backticks preserved | ✅ | `` `code` `` | +| Escape round-trip | ✅ | Parse → serialize | +| Double backslash `\\` | ✅ | `\\+` = literal `\+` | + +--- + +### T10: Edge Cases + +**Purpose:** Parser resilience and boundary conditions. + +| Feature | Tested | Details | +|---------|--------|---------| +| Extra whitespace in title | ✅ | Trimmed | +| Tab in title | ✅ | Preserved | +| Empty title `- [ ]` | ⚠️ | Undefined behavior | +| Single char title | ✅ | `A`, `1` | +| Malformed `- []` | ✅ | No space → preserved | +| Malformed `- [y]` | ✅ | Invalid state → preserved | +| Malformed `- [ ]` | ✅ | Double space → preserved | +| Orphan subtask | ✅ | Becomes top-level | +| Duplicate project | ✅ | Last wins | +| Duplicate date | ✅ | Last wins | +| Duplicate assignee | ✅ | Deduplicated | +| Duplicate tag | ✅ | Deduplicated | +| Deep nesting (5 levels) | ✅ | | +| Deep nesting (10+ levels) | ❌ | NOT TESTED | +| Long content | ✅ | 200+ chars | +| Mixed indentation | ✅ | Tabs + spaces | +| Error: task not found | ✅ | Mutation error | +| Error: ambiguous target | ✅ | Multiple tasks same title | +| Error: invalid transition | ❌ | NOT TESTED | + +--- + +### T11: Locales (Multi-Language) + +**Purpose:** Locale-specific date parsing across 8 languages. + +| Feature | Tested | Details | +|---------|--------|---------| +| English (US) | ✅ | `March 15, 2024` | +| English (UK) | ✅ | `15 March 2024` | +| Spanish | ✅ | `15 de marzo de 2024` | +| German | ✅ | `15. März 2024` | +| Portuguese (BR) | ✅ | `15 de março de 2024` | +| Dutch | ✅ | `15 maart 2024` | +| Russian | ✅ | `15 марта 2024` | +| Chinese | ✅ | `2024年03月15日` | +| French | ❌ | NOT TESTED | +| Italian | ❌ | NOT TESTED | +| Japanese (kanji months) | ❌ | NOT TESTED | +| Timezone conversion | ✅ | All files | +| Date+time serialization | ✅ | Optional time | + +--- + +### T12: Team Standup (Example) + +**Purpose:** Real-world team standup scenario. + +| Feature | Tested | Details | +|---------|--------|---------| +| Section inheritance | ✅ | @backend-team, @frontend-team, @ops | +| Project inheritance | ✅ | +work from header | +| Tag inheritance | ✅ | #standup from header | +| All 5 task states | ✅ | Real-world usage | +| Subtask completion | ✅ | Complete blocked subtask | +| Priority letters | ✅ | (A), (B) | +| Multiple assignees | ✅ | Section + explicit | +| Estimate formats | ✅ | ~4h, ~8h, ~16h | + +--- + +### T13: Sprint Planning (Example) + +**Purpose:** Sprint planning with recurrence scenario. + +| Feature | Tested | Details | +|---------|--------|---------| +| Recurrence completion | ✅ | Weekly task spawns new | +| Custom metadata | ✅ | desc:"quoted value" | +| Nested subtasks | ✅ | 3-level deep | +| Project hierarchy | ✅ | +engineering/backend | +| Tag combinations | ✅ | #q4, #security, #bugfix | +| Multiple sections | ✅ | Backend, Frontend, Documentation | + +--- + +### T14: Custom Date Format (Example) + +**Purpose:** UK locale with frontmatter datetime_format scenario. + +| Feature | Tested | Details | +|---------|--------|---------| +| Frontmatter locale | ✅ | en_GB | +| Frontmatter datetime_format | ✅ | %d/%m/%Y[ %H:%M] | +| Frontmatter timezone | ✅ | Europe/London | +| DD/MM/YYYY parsing | ✅ | UK date format | +| Time component | ✅ | Optional HH:MM | +| State: in_progress | ✅ | Subtask started | +| Notes with #repeat | ✅ | Preserved correctly | + +--- + +### T15: Comprehensive (Example) + +**Purpose:** Full feature coverage scenario. + +| Feature | Tested | Details | +|---------|--------|---------| +| Deep section nesting | ✅ | ### level 3 headers | +| Custom metadata | ✅ | status:active, env:production | +| Escaped characters | ✅ | \@mention, \#hashtag | +| Multiple projects | ✅ | +alpha/sprint1/api | +| Long titles | ✅ | 80+ character title | +| Minute estimates | ✅ | ~15m, ~30m | +| Day estimates | ✅ | ~2d | +| Multiple assignees | ✅ | @user1 @user2 @user3 | +| Multiple tags | ✅ | #tag1 #tag2 #tag3 | +| Priority override | ✅ | Explicit vs inherited | +| Project override | ✅ | +explicit-project | +| Standalone sections | ✅ | No project inheritance | + +--- + +## Coverage Gaps Summary + +### Remaining Gaps + +| Gap | Severity | Recommendation | +|-----|----------|----------------| +| `repeat:every-N-days` | MEDIUM | Add to T07 | +| Circular file links | MEDIUM | Add to T08 | +| Deep nesting 10+ levels | LOW | Add to T10 | +| Error: invalid transition | LOW | Add to T10 | +| More locales (FR, IT, JA) | LOW | Add to T11 | +| Priority/estimate inheritance | LOW | Add to T04 | +| Subtask priority → parent | LOW | Add to T05 | +| Empty title behavior | LOW | Define spec first | +| Mutation: change dates | LOW | Add to T03 | + +### Recently Added Coverage + +- ✅ T02: Reopen transitions (done→open, in_progress→open, blocked→open) +- ✅ T03: Remove all assignees/tags, change project +- ✅ T05: Multi-level subtasks (subtask of subtask) +- ✅ T07: Monthly and yearly recurrence patterns +- ✅ T08: Broken file link error +- ✅ T09: Double backslash escape +- ✅ T10: Ambiguous target error +- ✅ T12-T15: Real-world scenario tests (team standup, sprint planning, custom date format, comprehensive) + +--- + +## Serialization Rules + +### Metadata Ordering + +- **Tags**: Serialized in alphabetical order +- **Assignees**: Serialized in alphabetical order +- **Custom fields**: Serialized in alphabetical order by key + +### Value Quoting + +Values containing spaces must be quoted: + +- `planned:"March 15, 2024 09:00"` (has space) +- `planned:2024-03-15` (no space, no quotes needed) +- `ticket:"ENG-123"` (contains special characters) + +### Indentation + +- 2 spaces = 1 indent level +- 1 tab = 1 indent level +- Mixed indentation is supported but not recommended + +--- + +## Special Tag Behavior + +### #repeat Tag + +The `#repeat` tag has special handling: + +- On subtasks: NOT propagated to parent (unlike other tags) +- On notes: Marks note for copying to new recurring instance +- Purpose: Control what gets copied when recurring task completes + +--- + +## Date Field Preservation + +- `paused:` is preserved when a blocked task is completed +- `started:` is preserved through state transitions +- Only `done:` is cleared when reopening a done task + +--- + +## Implementation Notes + +1. **Test isolation:** Each test is independent; no shared state +2. **Deterministic output:** All mutations use fixed `today` date +3. **Multi-stage:** Each test covers parse + mutate + serialize +4. **Inheritance tracking:** Distinguish explicit vs inherited metadata +5. **File identity:** Track which file each task belongs to +6. **Idempotency:** Parse → serialize → parse produces identical result diff --git a/tests/T01_minimal/input.md b/tests/T01_minimal/input.md new file mode 100644 index 0000000..1aecb34 --- /dev/null +++ b/tests/T01_minimal/input.md @@ -0,0 +1,4 @@ +# Tasks + +- [ ] Simple task +- [ ] Another task diff --git a/tests/T01_minimal/mutated.md b/tests/T01_minimal/mutated.md new file mode 100644 index 0000000..2f9b8e2 --- /dev/null +++ b/tests/T01_minimal/mutated.md @@ -0,0 +1,4 @@ +# Tasks + +- [x] Simple task done:2024-03-15 +- [ ] Another task diff --git a/tests/T01_minimal/mutation.yaml b/tests/T01_minimal/mutation.yaml new file mode 100644 index 0000000..361a4fe --- /dev/null +++ b/tests/T01_minimal/mutation.yaml @@ -0,0 +1,8 @@ +target: + title: "Simple task" +changes: + state: done +expected_result: + status: success +options: + today: "2024-03-15" diff --git a/tests/T01_minimal/parsed.yaml b/tests/T01_minimal/parsed.yaml new file mode 100644 index 0000000..3b8a0f1 --- /dev/null +++ b/tests/T01_minimal/parsed.yaml @@ -0,0 +1,14 @@ +# Minimal parsing test - verifies basic task detection + +tasks: + - title: Simple task + state: open + file: input.md + line: 3 + indent: 0 + + - title: Another task + state: open + file: input.md + line: 4 + indent: 0 diff --git a/tests/T02_all_states/input.md b/tests/T02_all_states/input.md new file mode 100644 index 0000000..30a2580 --- /dev/null +++ b/tests/T02_all_states/input.md @@ -0,0 +1,14 @@ +# All States + +- [ ] Open task +- [.] In progress task started:2024-03-01 +- [x] Done task done:2024-03-05 +- [X] Done uppercase done:2024-03-05 +- [-] Cancelled task +- [!] Blocked task paused:2024-03-08 + +## Reopen Tests + +- [x] Reopen done task done:2024-03-05 +- [.] Reopen in progress task started:2024-03-01 +- [!] Reopen blocked task paused:2024-03-08 diff --git a/tests/T02_all_states/mutated.md b/tests/T02_all_states/mutated.md new file mode 100644 index 0000000..4916bc0 --- /dev/null +++ b/tests/T02_all_states/mutated.md @@ -0,0 +1,14 @@ +# All States + +- [.] Open task started:2024-03-15 +- [!] In progress task started:2024-03-01 paused:2024-03-15 +- [x] Done task done:2024-03-05 +- [X] Done uppercase done:2024-03-05 +- [ ] Cancelled task +- [x] Blocked task paused:2024-03-08 done:2024-03-15 + +## Reopen Tests + +- [ ] Reopen done task +- [ ] Reopen in progress task +- [ ] Reopen blocked task diff --git a/tests/T02_all_states/mutation.yaml b/tests/T02_all_states/mutation.yaml new file mode 100644 index 0000000..af6722a --- /dev/null +++ b/tests/T02_all_states/mutation.yaml @@ -0,0 +1,58 @@ +# Test state transitions and auto-date setting +# Includes reopen transitions: done→open, in_progress→open, blocked→open +mutations: + - target: + title: "Open task" + changes: + state: in_progress + expected_result: + status: success + + - target: + title: "In progress task" + changes: + state: blocked + expected_result: + status: success + + - target: + title: "Blocked task" + changes: + state: done + expected_result: + status: success + + - target: + title: "Cancelled task" + changes: + state: open + expected_result: + status: success + + # Reopen transitions - these clear the associated date fields + - target: + title: "Reopen done task" + changes: + state: open + expected_result: + status: success + # done: should be cleared + + - target: + title: "Reopen in progress task" + changes: + state: open + expected_result: + status: success + # started: should be cleared + + - target: + title: "Reopen blocked task" + changes: + state: open + expected_result: + status: success + # paused: should be cleared + +options: + today: "2024-03-15" diff --git a/tests/T02_all_states/parsed.yaml b/tests/T02_all_states/parsed.yaml new file mode 100644 index 0000000..a15bce8 --- /dev/null +++ b/tests/T02_all_states/parsed.yaml @@ -0,0 +1,65 @@ +# All 5 task states with case insensitivity for [x]/[X] +# Plus reopen transitions (done→open, in_progress→open, blocked→open) + +tasks: + - title: Open task + state: open + file: input.md + line: 3 + indent: 0 + + - title: In progress task + state: in_progress + started_date: '2024-03-01' + file: input.md + line: 4 + indent: 0 + + - title: Done task + state: done + done_date: '2024-03-05' + file: input.md + line: 5 + indent: 0 + + - title: Done uppercase + state: done + done_date: '2024-03-05' + file: input.md + line: 6 + indent: 0 + + - title: Cancelled task + state: cancelled + file: input.md + line: 7 + indent: 0 + + - title: Blocked task + state: blocked + paused_date: '2024-03-08' + file: input.md + line: 8 + indent: 0 + + # Reopen test tasks + - title: Reopen done task + state: done + done_date: '2024-03-05' + file: input.md + line: 12 + indent: 0 + + - title: Reopen in progress task + state: in_progress + started_date: '2024-03-01' + file: input.md + line: 13 + indent: 0 + + - title: Reopen blocked task + state: blocked + paused_date: '2024-03-08' + file: input.md + line: 14 + indent: 0 diff --git a/tests/T03_metadata_full/input.md b/tests/T03_metadata_full/input.md new file mode 100644 index 0000000..d122ed8 --- /dev/null +++ b/tests/T03_metadata_full/input.md @@ -0,0 +1,14 @@ +# Full Metadata + +- [ ] (A) Priority task +Project @alice @bob #urgent #backend ~4h due:2024-03-20 +- [ ] (1) Numeric priority +Project/Sub ~30m planned:2024-03-10T09:00Z +- [ ] All dates created:2024-03-01 planned:2024-03-10 started:2024-03-10 paused:2024-03-11 due:2024-03-15 done:2024-03-12 +- [ ] Custom fields type:bug ticket:"ENG-123" url: +- [ ] Day estimate ~2d +- [ ] Minutes estimate ~90min + +## Removal and Change Tests + +- [ ] Remove all assignees @alice @bob #keep +- [ ] Remove all tags @keep #tag1 #tag2 +- [ ] Change project +OldProject @alice diff --git a/tests/T03_metadata_full/mutated.md b/tests/T03_metadata_full/mutated.md new file mode 100644 index 0000000..030ead3 --- /dev/null +++ b/tests/T03_metadata_full/mutated.md @@ -0,0 +1,14 @@ +# Full Metadata + +- [ ] (B) Priority task +Project @alice @charlie #critical #urgent ~8h due:2024-03-20 +- [ ] (1) Numeric priority +Project/Sub ~30m planned:2024-03-10T09:00Z +- [ ] All dates created:2024-03-01 planned:2024-03-10 started:2024-03-10 paused:2024-03-11 due:2024-03-15 done:2024-03-12 +- [ ] Custom fields sprint:24 ticket:"ENG-123" type:feature url: +- [ ] Day estimate ~2d +- [ ] Minutes estimate ~90min + +## Removal and Change Tests + +- [ ] Remove all assignees #keep +- [ ] Remove all tags @keep +- [ ] Change project +NewProject @alice diff --git a/tests/T03_metadata_full/mutation.yaml b/tests/T03_metadata_full/mutation.yaml new file mode 100644 index 0000000..5b17e81 --- /dev/null +++ b/tests/T03_metadata_full/mutation.yaml @@ -0,0 +1,52 @@ +# Test metadata modifications +# Includes: change metadata, remove all assignees/tags, change project +mutations: + - target: + title: "Priority task" + changes: + priority: B + assignees: + - alice + - charlie + tags: + - urgent + - critical + estimate_minutes: 480 + expected_result: + status: success + + - target: + title: "Custom fields" + changes: + custom_fields: + type: feature + sprint: "24" + expected_result: + status: success + + # Remove all assignees (set to empty array) + - target: + title: "Remove all assignees" + changes: + assignees: [] + expected_result: + status: success + + # Remove all tags (set to empty array) + - target: + title: "Remove all tags" + changes: + tags: [] + expected_result: + status: success + + # Change project + - target: + title: "Change project" + changes: + project_path: NewProject + expected_result: + status: success + +options: + today: "2024-03-15" diff --git a/tests/T03_metadata_full/parsed.yaml b/tests/T03_metadata_full/parsed.yaml new file mode 100644 index 0000000..c2c5012 --- /dev/null +++ b/tests/T03_metadata_full/parsed.yaml @@ -0,0 +1,114 @@ +# All metadata types: priority, project, assignee, tag, estimate, dates, custom fields + +tasks: + - title: Priority task + state: open + priority: A + project_path: Project + assignees: + - alice + - bob + tags: + - backend + - urgent + estimate_minutes: 240 + due_date: '2024-03-20' + explicit_assignees: + - alice + - bob + explicit_tags: + - backend + - urgent + file: input.md + line: 3 + indent: 0 + + - title: Numeric priority + state: open + priority: '1' + project_path: Project/Sub + estimate_minutes: 30 + planned_date: '2024-03-10T09:00Z' + file: input.md + line: 4 + indent: 0 + + - title: All dates + state: open + created_date: '2024-03-01' + planned_date: '2024-03-10' + started_date: '2024-03-10' + paused_date: '2024-03-11' + due_date: '2024-03-15' + done_date: '2024-03-12' + file: input.md + line: 5 + indent: 0 + + - title: Custom fields + state: open + custom_fields: + ticket: ENG-123 + type: bug + url: https://example.com/path + file: input.md + line: 6 + indent: 0 + + - title: Day estimate + state: open + estimate_minutes: 2880 + file: input.md + line: 7 + indent: 0 + + - title: Minutes estimate + state: open + estimate_minutes: 90 + file: input.md + line: 8 + indent: 0 + + # Removal and change test tasks + - title: Remove all assignees + state: open + assignees: + - alice + - bob + tags: + - keep + explicit_assignees: + - alice + - bob + explicit_tags: + - keep + file: input.md + line: 12 + indent: 0 + + - title: Remove all tags + state: open + assignees: + - keep + tags: + - tag1 + - tag2 + explicit_assignees: + - keep + explicit_tags: + - tag1 + - tag2 + file: input.md + line: 13 + indent: 0 + + - title: Change project + state: open + project_path: OldProject + assignees: + - alice + explicit_assignees: + - alice + file: input.md + line: 14 + indent: 0 diff --git a/tests/T04_inheritance/input.md b/tests/T04_inheritance/input.md new file mode 100644 index 0000000..2f0b608 --- /dev/null +++ b/tests/T04_inheritance/input.md @@ -0,0 +1,12 @@ +# Project +Acme #work @team priority:low + +## Backend +API #critical @alice priority:high + +### Database +DB + +- [ ] Task inherits all +- [ ] Task with explicit +Extra @bob #mytag priority:override + +## Frontend +UI + +- [ ] Frontend task diff --git a/tests/T04_inheritance/mutated.md b/tests/T04_inheritance/mutated.md new file mode 100644 index 0000000..759388f --- /dev/null +++ b/tests/T04_inheritance/mutated.md @@ -0,0 +1,12 @@ +# Project +Acme #work @team priority:low + +## Backend +API #critical @alice priority:high + +### Database +DB + +- [ ] Task inherits all +- [ ] Task with explicit +Extra @bob #mytag #newtag priority:override + +## Frontend +UI + +- [x] Frontend task done:2024-03-15 diff --git a/tests/T04_inheritance/mutation.yaml b/tests/T04_inheritance/mutation.yaml new file mode 100644 index 0000000..7d19f99 --- /dev/null +++ b/tests/T04_inheritance/mutation.yaml @@ -0,0 +1,22 @@ +# Test that explicit metadata can be changed but inherited cannot be removed +mutations: + - target: + title: "Task with explicit" + changes: + tags: + - critical + - mytag + - newtag + - work + expected_result: + status: success + + - target: + title: "Frontend task" + changes: + state: done + expected_result: + status: success + +options: + today: "2024-03-15" diff --git a/tests/T04_inheritance/parsed.yaml b/tests/T04_inheritance/parsed.yaml new file mode 100644 index 0000000..8e3d217 --- /dev/null +++ b/tests/T04_inheritance/parsed.yaml @@ -0,0 +1,78 @@ +# Section hierarchy inheritance: +# - Projects: hierarchical join (+A + +B = +A/B) +# - Tags: additive +# - Assignees: additive +# - Custom fields: child overrides parent + +tasks: + - title: Task inherits all + state: open + project_path: Acme/API/DB + tags: + - critical + - work + assignees: + - alice + - team + custom_fields: + priority: high + inherited_project_path: Acme/API/DB + inherited_tags: + - critical + - work + inherited_assignees: + - alice + - team + inherited_custom_fields: + priority: high + file: input.md + line: 7 + indent: 0 + + - title: Task with explicit + state: open + project_path: Acme/API/DB/Extra + tags: + - critical + - mytag + - work + assignees: + - alice + - bob + - team + custom_fields: + priority: override + inherited_project_path: Acme/API/DB + inherited_tags: + - critical + - work + inherited_assignees: + - alice + - team + inherited_custom_fields: + priority: high + explicit_tags: + - mytag + explicit_assignees: + - bob + explicit_custom_fields: + priority: override + file: input.md + line: 8 + indent: 0 + + - title: Frontend task + state: open + project_path: Acme/UI + tags: + - work + assignees: + - team + inherited_project_path: Acme/UI + inherited_tags: + - work + inherited_assignees: + - team + file: input.md + line: 12 + indent: 0 diff --git a/tests/T05_subtasks_notes/input.md b/tests/T05_subtasks_notes/input.md new file mode 100644 index 0000000..c7c013a --- /dev/null +++ b/tests/T05_subtasks_notes/input.md @@ -0,0 +1,17 @@ +# Subtasks and Notes + +- [ ] Parent task @alice ~8h due:2024-03-20 + - [ ] (A) High priority subtask @bob #urgent + - [.] In progress subtask started:2024-03-10 + - [x] Done subtask done:2024-03-08 + - [ ] Regular subtask #repeat + - Note without brackets + - Another note #repeat + - [invalid] Treated as note + +## Multi-Level Subtasks + +- [ ] Top level task + - [ ] Level 1 subtask @lead + - [ ] Level 2 subtask #deep + - Note on level 1 diff --git a/tests/T05_subtasks_notes/mutated.md b/tests/T05_subtasks_notes/mutated.md new file mode 100644 index 0000000..245cc25 --- /dev/null +++ b/tests/T05_subtasks_notes/mutated.md @@ -0,0 +1,17 @@ +# Subtasks and Notes + +- [.] Parent task @alice ~8h started:2024-03-15 due:2024-03-20 + - [ ] (A) High priority subtask @bob #urgent + - [x] In progress subtask started:2024-03-10 done:2024-03-15 + - [x] Done subtask done:2024-03-08 + - [ ] Regular subtask #repeat + - Note without brackets + - Another note #repeat + - [invalid] Treated as note + +## Multi-Level Subtasks + +- [ ] Top level task + - [ ] Level 1 subtask @lead + - [x] Level 2 subtask #deep done:2024-03-15 + - Note on level 1 diff --git a/tests/T05_subtasks_notes/mutation.yaml b/tests/T05_subtasks_notes/mutation.yaml new file mode 100644 index 0000000..38b2ac7 --- /dev/null +++ b/tests/T05_subtasks_notes/mutation.yaml @@ -0,0 +1,27 @@ +# Test subtask mutations - independent of parent +# Includes multi-level subtask mutation +mutations: + - target: + title: "In progress subtask" + changes: + state: done + expected_result: + status: success + + - target: + title: "Parent task" + changes: + state: in_progress + expected_result: + status: success + + # Mutate a level 2 subtask (subtask of subtask) + - target: + title: "Level 2 subtask" + changes: + state: done + expected_result: + status: success + +options: + today: "2024-03-15" diff --git a/tests/T05_subtasks_notes/parsed.yaml b/tests/T05_subtasks_notes/parsed.yaml new file mode 100644 index 0000000..cedc6ee --- /dev/null +++ b/tests/T05_subtasks_notes/parsed.yaml @@ -0,0 +1,111 @@ +# Subtasks and notes: +# - Subtask @assignee and #tag propagate to parent +# - [invalid] state treated as note +# - #repeat tag preserved for recurrence +# - Notes are plain text (not parsed for metadata) + +tasks: + - title: Parent task + state: open + assignees: + - alice + - bob + tags: + - urgent + estimate_minutes: 480 + due_date: '2024-03-20' + explicit_assignees: + - alice + file: input.md + line: 3 + indent: 0 + subtasks: + - title: High priority subtask + state: open + priority: A + assignees: + - bob + tags: + - urgent + explicit_assignees: + - bob + explicit_tags: + - urgent + file: input.md + line: 4 + indent: 2 + + - title: In progress subtask + state: in_progress + started_date: '2024-03-10' + file: input.md + line: 5 + indent: 2 + + - title: Done subtask + state: done + done_date: '2024-03-08' + file: input.md + line: 6 + indent: 2 + + - title: Regular subtask + state: open + tags: + - repeat + explicit_tags: + - repeat + file: input.md + line: 7 + indent: 2 + + notes: + - text: Note without brackets + file: input.md + line: 8 + + - text: Another note #repeat + has_repeat_tag: true + file: input.md + line: 9 + + - text: "[invalid] Treated as note" + file: input.md + line: 10 + + # Multi-level subtasks - subtask of subtask + - title: Top level task + state: open + assignees: + - lead + tags: + - deep + file: input.md + line: 14 + indent: 0 + subtasks: + - title: Level 1 subtask + state: open + assignees: + - lead + tags: + - deep + explicit_assignees: + - lead + file: input.md + line: 15 + indent: 2 + subtasks: + - title: Level 2 subtask + state: open + tags: + - deep + explicit_tags: + - deep + file: input.md + line: 16 + indent: 4 + notes: + - text: Note on level 1 + file: input.md + line: 17 diff --git a/tests/T06_frontmatter/input.md b/tests/T06_frontmatter/input.md new file mode 100644 index 0000000..b86a974 --- /dev/null +++ b/tests/T06_frontmatter/input.md @@ -0,0 +1,17 @@ +# Multi-File Frontmatter Test + +## US Office (New York) + +[[us_office.md]] + +## UK Office (London) + +[[uk_office.md]] + +## Japan Office (Tokyo) + +[[japan_office.md]] + +## Cross-timezone task + +- [ ] Global sync meeting planned:2024-03-15T14:00Z due:2024-03-15T15:00Z diff --git a/tests/T06_frontmatter/input_japan_office.md b/tests/T06_frontmatter/input_japan_office.md new file mode 100644 index 0000000..855077b --- /dev/null +++ b/tests/T06_frontmatter/input_japan_office.md @@ -0,0 +1,12 @@ +--- +taskmark: + locale: ja_JP + timezone: Asia/Tokyo + date_format: "%Y年%m月%d日" +--- + +# Japan Tasks + +- [ ] Sprint planning planned:2024年03月15日 +- [ ] Release deadline due:2024年03月31日 +- [ ] Call New York planned:2024-03-15T23:00+09:00 diff --git a/tests/T06_frontmatter/input_uk_office.md b/tests/T06_frontmatter/input_uk_office.md new file mode 100644 index 0000000..d8d5a1c --- /dev/null +++ b/tests/T06_frontmatter/input_uk_office.md @@ -0,0 +1,12 @@ +--- +taskmark: + locale: en_GB + timezone: Europe/London + date_format: "%d %b %Y" +--- + +# UK Tasks + +- [ ] Board meeting planned:15 Mar 2024 +- [ ] Financial close due:31 Mar 2024 +- [ ] Call Tokyo office planned:2024-03-15T14:00+00:00 diff --git a/tests/T06_frontmatter/input_us_office.md b/tests/T06_frontmatter/input_us_office.md new file mode 100644 index 0000000..2e80314 --- /dev/null +++ b/tests/T06_frontmatter/input_us_office.md @@ -0,0 +1,12 @@ +--- +taskmark: + locale: en_US + timezone: America/New_York + date_format: "%B %d, %Y" +--- + +# US Tasks + +- [ ] Quarterly review planned:March 15, 2024 +- [ ] Submit report due:March 20, 2024 +- [ ] Call London office planned:2024-03-15T09:00-05:00 diff --git a/tests/T06_frontmatter/mutated.md b/tests/T06_frontmatter/mutated.md new file mode 100644 index 0000000..f9321be --- /dev/null +++ b/tests/T06_frontmatter/mutated.md @@ -0,0 +1,17 @@ +# Multi-File Frontmatter Test + +## US Office (New York) + +[[us_office.md]] + +## UK Office (London) + +[[uk_office.md]] + +## Japan Office (Tokyo) + +[[japan_office.md]] + +## Cross-timezone task + +- [.] Global sync meeting planned:2024-03-15T14:00Z due:2024-03-15T15:00Z started:2024-03-15 diff --git a/tests/T06_frontmatter/mutated_japan_office.md b/tests/T06_frontmatter/mutated_japan_office.md new file mode 100644 index 0000000..de92309 --- /dev/null +++ b/tests/T06_frontmatter/mutated_japan_office.md @@ -0,0 +1,12 @@ +--- +taskmark: + locale: ja_JP + timezone: Asia/Tokyo + date_format: "%Y年%m月%d日" +--- + +# Japan Tasks + +- [x] Sprint planning planned:2024年03月15日 done:2024年03月15日 +- [ ] Release deadline due:2024年03月31日 +- [ ] Call New York planned:2024年03月15日 diff --git a/tests/T06_frontmatter/mutated_uk_office.md b/tests/T06_frontmatter/mutated_uk_office.md new file mode 100644 index 0000000..6d7f887 --- /dev/null +++ b/tests/T06_frontmatter/mutated_uk_office.md @@ -0,0 +1,12 @@ +--- +taskmark: + locale: en_GB + timezone: Europe/London + date_format: "%d %b %Y" +--- + +# UK Tasks + +- [x] Board meeting planned:15 Mar 2024 done:15 Mar 2024 +- [ ] Financial close due:31 Mar 2024 +- [ ] Call Tokyo office planned:15 Mar 2024 diff --git a/tests/T06_frontmatter/mutated_us_office.md b/tests/T06_frontmatter/mutated_us_office.md new file mode 100644 index 0000000..ada61a9 --- /dev/null +++ b/tests/T06_frontmatter/mutated_us_office.md @@ -0,0 +1,12 @@ +--- +taskmark: + locale: en_US + timezone: America/New_York + date_format: "%B %d, %Y" +--- + +# US Tasks + +- [x] Quarterly review planned:March 15, 2024 done:March 15, 2024 +- [ ] Submit report due:March 20, 2024 +- [ ] Call London office planned:March 15, 2024 diff --git a/tests/T06_frontmatter/mutation.yaml b/tests/T06_frontmatter/mutation.yaml new file mode 100644 index 0000000..ee6f984 --- /dev/null +++ b/tests/T06_frontmatter/mutation.yaml @@ -0,0 +1,43 @@ +# Test timezone conversion and locale-specific date serialization: +# - Completing tasks in different timezones +# - done: dates serialized in file's format/timezone +# - Explicit TZ dates converted to file's timezone + +mutations: + - target: + file: us_office.md + title: "Quarterly review" + changes: + state: done + expected_result: + status: success + # done: should be "March 15, 2024" in US format + + - target: + file: uk_office.md + title: "Board meeting" + changes: + state: done + expected_result: + status: success + # done: should be "15 Mar 2024" in UK format + + - target: + file: japan_office.md + title: "Sprint planning" + changes: + state: done + expected_result: + status: success + # done: should be "2024年03月15日" in Japanese format + + - target: + title: "Global sync meeting" + changes: + state: in_progress + expected_result: + status: success + # Root file has no frontmatter, uses ISO format + +options: + today: "2024-03-15" diff --git a/tests/T06_frontmatter/parsed.yaml b/tests/T06_frontmatter/parsed.yaml new file mode 100644 index 0000000..c5050f1 --- /dev/null +++ b/tests/T06_frontmatter/parsed.yaml @@ -0,0 +1,115 @@ +# Multi-file frontmatter test: +# - Different locales per file (en_US, en_GB, ja_JP) +# - Different timezones per file (America/New_York, Europe/London, Asia/Tokyo) +# - Different date formats (long month, short month, Japanese) +# - Explicit timezone dates converted to file's timezone +# - UTC dates (Z suffix) converted to file's timezone + +tasks: + # Root file (no frontmatter, uses ISO) + - title: Global sync meeting + state: open + planned_date: '2024-03-15T14:00Z' + due_date: '2024-03-15T15:00Z' + file: input.md + line: 13 + indent: 0 + + # US Office (en_US, America/New_York, "%B %d, %Y") + - title: Quarterly review + state: open + planned_date: '2024-03-15' + file: us_office.md + line: 9 + indent: 0 + + - title: Submit report + state: open + due_date: '2024-03-20' + file: us_office.md + line: 10 + indent: 0 + + - title: Call London office + state: open + planned_date: '2024-03-15T09:00-05:00' + # Explicit TZ preserved in parsed, converted on output + file: us_office.md + line: 11 + indent: 0 + + # UK Office (en_GB, Europe/London, "%d %b %Y") + - title: Board meeting + state: open + planned_date: '2024-03-15' + file: uk_office.md + line: 9 + indent: 0 + + - title: Financial close + state: open + due_date: '2024-03-31' + file: uk_office.md + line: 10 + indent: 0 + + - title: Call Tokyo office + state: open + planned_date: '2024-03-15T14:00+00:00' + file: uk_office.md + line: 11 + indent: 0 + + # Japan Office (ja_JP, Asia/Tokyo, "%Y年%m月%d日") + - title: Sprint planning + state: open + planned_date: '2024-03-15' + file: japan_office.md + line: 9 + indent: 0 + + - title: Release deadline + state: open + due_date: '2024-03-31' + file: japan_office.md + line: 10 + indent: 0 + + - title: Call New York + state: open + planned_date: '2024-03-15T23:00+09:00' + file: japan_office.md + line: 11 + indent: 0 + +file_links: + - source: input.md + target: us_office.md + section: "US Office (New York)" + line: 4 + + - source: input.md + target: uk_office.md + section: "UK Office (London)" + line: 7 + + - source: input.md + target: japan_office.md + section: "Japan Office (Tokyo)" + line: 10 + +frontmatter: + us_office.md: + locale: en_US + timezone: America/New_York + date_format: "%B %d, %Y" + + uk_office.md: + locale: en_GB + timezone: Europe/London + date_format: "%d %b %Y" + + japan_office.md: + locale: ja_JP + timezone: Asia/Tokyo + date_format: "%Y年%m月%d日" diff --git a/tests/T07_recurrence/input.md b/tests/T07_recurrence/input.md new file mode 100644 index 0000000..04377e5 --- /dev/null +++ b/tests/T07_recurrence/input.md @@ -0,0 +1,18 @@ +# Recurring Tasks + +- [ ] Weekly review repeat:weekly planned:2024-03-10 due:2024-03-12 + - [ ] Check metrics #repeat + - [ ] One-time setup + - Recurring note #repeat + - One-time note + +- [ ] Daily standup repeat:daily planned:2024-03-10 + +- [ ] To cancel repeat:weekly planned:2024-03-10 + +- [ ] To block repeat:weekly planned:2024-03-10 + +## Additional Patterns + +- [ ] Monthly report repeat:monthly planned:2024-03-15 +- [ ] Yearly review repeat:yearly planned:2024-03-20 diff --git a/tests/T07_recurrence/mutated.md b/tests/T07_recurrence/mutated.md new file mode 100644 index 0000000..713ee87 --- /dev/null +++ b/tests/T07_recurrence/mutated.md @@ -0,0 +1,23 @@ +# Recurring Tasks + +- [ ] Weekly review repeat:weekly planned:2024-03-17 due:2024-03-19 + - [ ] Check metrics #repeat + - Recurring note #repeat +- [x] Weekly review planned:2024-03-10 due:2024-03-12 done:2024-03-10 + - [ ] Check metrics #repeat + - [ ] One-time setup + - Recurring note #repeat + - One-time note + +- [ ] Daily standup repeat:daily planned:2024-03-10 + +- [-] To cancel planned:2024-03-10 + +- [!] To block repeat:weekly planned:2024-03-10 paused:2024-03-10 + +## Additional Patterns + +- [ ] Monthly report repeat:monthly planned:2024-04-15 +- [x] Monthly report planned:2024-03-15 done:2024-03-10 +- [ ] Yearly review repeat:yearly planned:2025-03-20 +- [x] Yearly review planned:2024-03-20 done:2024-03-10 diff --git a/tests/T07_recurrence/mutation.yaml b/tests/T07_recurrence/mutation.yaml new file mode 100644 index 0000000..757525f --- /dev/null +++ b/tests/T07_recurrence/mutation.yaml @@ -0,0 +1,46 @@ +# Test recurrence behavior: +# - [x] completion: creates new task, removes repeat: from completed +# - [-] cancel: removes repeat:, no new task +# - [!] block: keeps repeat:, no new task +# - monthly: advances by 1 month +# - yearly: advances by 1 year +mutations: + - target: + title: "Weekly review" + changes: + state: done + expected_result: + status: success + + - target: + title: "To cancel" + changes: + state: cancelled + expected_result: + status: success + + - target: + title: "To block" + changes: + state: blocked + expected_result: + status: success + + # Monthly recurrence - advances by 1 month + - target: + title: "Monthly report" + changes: + state: done + expected_result: + status: success + + # Yearly recurrence - advances by 1 year + - target: + title: "Yearly review" + changes: + state: done + expected_result: + status: success + +options: + today: "2024-03-10" diff --git a/tests/T07_recurrence/parsed.yaml b/tests/T07_recurrence/parsed.yaml new file mode 100644 index 0000000..a367ad0 --- /dev/null +++ b/tests/T07_recurrence/parsed.yaml @@ -0,0 +1,81 @@ +# Recurrence patterns: +# - daily, weekly, monthly, yearly +# - every-N-weeks, every-monday, first-monday-of-month +# - #repeat tag on subtasks/notes copied to new instance + +tasks: + - title: Weekly review + state: open + recurrence: weekly + planned_date: '2024-03-10' + due_date: '2024-03-12' + file: input.md + line: 3 + indent: 0 + subtasks: + - title: Check metrics + state: open + tags: + - repeat + explicit_tags: + - repeat + file: input.md + line: 4 + indent: 2 + + - title: One-time setup + state: open + file: input.md + line: 5 + indent: 2 + + notes: + - text: Recurring note #repeat + has_repeat_tag: true + file: input.md + line: 6 + + - text: One-time note + file: input.md + line: 7 + + - title: Daily standup + state: open + recurrence: daily + planned_date: '2024-03-10' + file: input.md + line: 9 + indent: 0 + + - title: To cancel + state: open + recurrence: weekly + planned_date: '2024-03-10' + file: input.md + line: 11 + indent: 0 + + - title: To block + state: open + recurrence: weekly + planned_date: '2024-03-10' + file: input.md + line: 13 + indent: 0 + + # Additional recurrence patterns + - title: Monthly report + state: open + recurrence: monthly + planned_date: '2024-03-15' + file: input.md + line: 17 + indent: 0 + + - title: Yearly review + state: open + recurrence: yearly + planned_date: '2024-03-20' + file: input.md + line: 18 + indent: 0 diff --git a/tests/T08_multi_file/input.md b/tests/T08_multi_file/input.md new file mode 100644 index 0000000..aa9e885 --- /dev/null +++ b/tests/T08_multi_file/input.md @@ -0,0 +1,15 @@ +# Project Dashboard +MainProject @lead #dashboard + +## Backend + +[[backend.md]] + +## Frontend + +[[frontend.md]] + +- [ ] Integration testing planned:2024-03-15 + +## Broken Link (Error Case) + +[[nonexistent.md]] diff --git a/tests/T08_multi_file/input_backend.md b/tests/T08_multi_file/input_backend.md new file mode 100644 index 0000000..b0a799f --- /dev/null +++ b/tests/T08_multi_file/input_backend.md @@ -0,0 +1,4 @@ +# Backend Tasks + +- [ ] Setup database +Backend @alice +- [.] Build API endpoints started:2024-03-01 diff --git a/tests/T08_multi_file/input_frontend.md b/tests/T08_multi_file/input_frontend.md new file mode 100644 index 0000000..24c96be --- /dev/null +++ b/tests/T08_multi_file/input_frontend.md @@ -0,0 +1,9 @@ +--- +taskmark: + locale: en-GB + date_format: "%d/%m/%Y" +--- +# Frontend Tasks #react + +- [ ] Build login form @bob planned:15/03/2024 +- [ ] Create dashboard +UI diff --git a/tests/T08_multi_file/mutated.md b/tests/T08_multi_file/mutated.md new file mode 100644 index 0000000..aa9e885 --- /dev/null +++ b/tests/T08_multi_file/mutated.md @@ -0,0 +1,15 @@ +# Project Dashboard +MainProject @lead #dashboard + +## Backend + +[[backend.md]] + +## Frontend + +[[frontend.md]] + +- [ ] Integration testing planned:2024-03-15 + +## Broken Link (Error Case) + +[[nonexistent.md]] diff --git a/tests/T08_multi_file/mutated_backend.md b/tests/T08_multi_file/mutated_backend.md new file mode 100644 index 0000000..56b5a05 --- /dev/null +++ b/tests/T08_multi_file/mutated_backend.md @@ -0,0 +1,4 @@ +# Backend Tasks + +- [x] Setup database +Backend @alice done:2024-03-10 +- [.] Build API endpoints started:2024-03-01 diff --git a/tests/T08_multi_file/mutated_frontend.md b/tests/T08_multi_file/mutated_frontend.md new file mode 100644 index 0000000..ef76c99 --- /dev/null +++ b/tests/T08_multi_file/mutated_frontend.md @@ -0,0 +1,9 @@ +--- +taskmark: + locale: en-GB + date_format: "%d/%m/%Y" +--- +# Frontend Tasks #react + +- [.] Build login form @bob planned:15/03/2024 started:10/03/2024 +- [ ] Create dashboard +UI diff --git a/tests/T08_multi_file/mutation.yaml b/tests/T08_multi_file/mutation.yaml new file mode 100644 index 0000000..d30257d --- /dev/null +++ b/tests/T08_multi_file/mutation.yaml @@ -0,0 +1,23 @@ +# Test mutations in linked files: +# - Can mutate tasks in linked files +# - Date formatting respects linked file's frontmatter + +mutations: + - target: + file: backend.md + title: "Setup database" + changes: + state: done + expected_result: + status: success + + - target: + file: frontend.md + title: "Build login form" + changes: + state: in_progress + expected_result: + status: success + +options: + today: "2024-03-10" diff --git a/tests/T08_multi_file/parsed.yaml b/tests/T08_multi_file/parsed.yaml new file mode 100644 index 0000000..278d309 --- /dev/null +++ b/tests/T08_multi_file/parsed.yaml @@ -0,0 +1,137 @@ +# Multi-file tests: +# - File links with [[filename.md]] syntax +# - Inheritance flows from parent to linked files +# - Linked files can have own frontmatter +# - Project paths join hierarchically + +tasks: + # From input.md (root file) + - title: Integration testing + state: open + project_path: MainProject + assignees: + - lead + tags: + - dashboard + explicit_tags: [] + inherited_project: MainProject + inherited_assignees: + - lead + inherited_tags: + - dashboard + planned_date: '2024-03-15' + file: input.md + line: 9 + indent: 0 + + # From backend.md (inherits from Backend section in parent) + - title: Setup database + state: open + project_path: MainProject/Backend + assignees: + - lead + - alice + tags: + - dashboard + explicit_project: Backend + explicit_assignees: + - alice + explicit_tags: [] + inherited_project: MainProject + inherited_assignees: + - lead + inherited_tags: + - dashboard + file: backend.md + line: 3 + indent: 0 + + - title: Build API endpoints + state: in_progress + project_path: MainProject + assignees: + - lead + tags: + - dashboard + explicit_tags: [] + inherited_project: MainProject + inherited_assignees: + - lead + inherited_tags: + - dashboard + started_date: '2024-03-01' + file: backend.md + line: 4 + indent: 0 + + # From frontend.md (has own frontmatter, inherits from Frontend section) + - title: Build login form + state: open + project_path: MainProject + assignees: + - lead + - bob + tags: + - dashboard + - react + explicit_assignees: + - bob + explicit_tags: [] + inherited_project: MainProject + inherited_assignees: + - lead + inherited_tags: + - dashboard + - react + planned_date: '2024-03-15' + file: frontend.md + line: 8 + indent: 0 + + - title: Create dashboard + state: open + project_path: MainProject/UI + assignees: + - lead + tags: + - dashboard + - react + explicit_project: UI + explicit_tags: [] + inherited_project: MainProject + inherited_assignees: + - lead + inherited_tags: + - dashboard + - react + file: frontend.md + line: 9 + indent: 0 + +file_links: + - source: input.md + target: backend.md + section: Backend + line: 4 + + - source: input.md + target: frontend.md + section: Frontend + line: 7 + + # Broken link - file does not exist + - source: input.md + target: nonexistent.md + section: "Broken Link (Error Case)" + line: 12 + +frontmatter: + frontend.md: + locale: en-GB + date_format: "%d/%m/%Y" + +errors: + - file: input.md + line: 12 + code: "E005" + message: "Linked file not found: nonexistent.md" diff --git a/tests/T09_escaping/input.md b/tests/T09_escaping/input.md new file mode 100644 index 0000000..7d77443 --- /dev/null +++ b/tests/T09_escaping/input.md @@ -0,0 +1,13 @@ +# Escaping Tests + +- [ ] Task with \+literal plus +- [ ] Task with \@literal at sign +- [ ] Task with \#literal hash +- [ ] Task with \~literal tilde +- [ ] Has real +Project but \@escaped at +- [ ] Date with colon due:2024-03-15 and text\:with\:colons +- [ ] Unicode café résumé naïve +- [ ] Emoji task 🎯 📝 ✅ +- [ ] Special chars: <>&"' +- [ ] Backticks `code` and ```blocks``` +- [ ] Double backslash \\+NotProject \\@NotAssignee diff --git a/tests/T09_escaping/mutated.md b/tests/T09_escaping/mutated.md new file mode 100644 index 0000000..3761955 --- /dev/null +++ b/tests/T09_escaping/mutated.md @@ -0,0 +1,13 @@ +# Escaping Tests + +- [x] Task with \+literal plus done:2024-03-10 +- [ ] Task with \@literal at sign +- [ ] Task with \#literal hash +- [ ] Task with \~literal tilde +- [ ] Has real +Project but \@escaped at +- [ ] Date with colon due:2024-03-15 and text\:with\:colons +- [.] Unicode café résumé naïve started:2024-03-10 +- [x] Emoji task 🎯 📝 ✅ done:2024-03-10 +- [ ] Special chars: <>&"' +- [ ] Backticks `code` and ```blocks``` +- [ ] Double backslash \\+NotProject \\@NotAssignee diff --git a/tests/T09_escaping/mutation.yaml b/tests/T09_escaping/mutation.yaml new file mode 100644 index 0000000..c99aeef --- /dev/null +++ b/tests/T09_escaping/mutation.yaml @@ -0,0 +1,29 @@ +# Test that escaping is preserved through mutation: +# - Escaped characters remain escaped in output +# - Unicode preserved +# - Emoji preserved + +mutations: + - target: + title: "Task with +literal plus" + changes: + state: done + expected_result: + status: success + + - target: + title: "Unicode café résumé naïve" + changes: + state: in_progress + expected_result: + status: success + + - target: + title: "Emoji task 🎯 📝 ✅" + changes: + state: done + expected_result: + status: success + +options: + today: "2024-03-10" diff --git a/tests/T09_escaping/parsed.yaml b/tests/T09_escaping/parsed.yaml new file mode 100644 index 0000000..3976e32 --- /dev/null +++ b/tests/T09_escaping/parsed.yaml @@ -0,0 +1,84 @@ +# Escape sequence tests: +# - Backslash escapes: \+ \@ \# \~ \: +# - Unicode characters preserved +# - Emoji preserved +# - HTML entities preserved as-is +# - Markdown formatting preserved + +tasks: + - title: "Task with +literal plus" + state: open + file: input.md + line: 3 + indent: 0 + # Note: \+ means literal +, not project prefix + + - title: "Task with @literal at sign" + state: open + file: input.md + line: 4 + indent: 0 + # Note: \@ means literal @, not assignee prefix + + - title: "Task with #literal hash" + state: open + file: input.md + line: 5 + indent: 0 + # Note: \# means literal #, not tag prefix + + - title: "Task with ~literal tilde" + state: open + file: input.md + line: 6 + indent: 0 + # Note: \~ means literal ~, not estimate prefix + + - title: "Has real +Project but @escaped at" + state: open + project_path: Project + explicit_project: Project + file: input.md + line: 7 + indent: 0 + # Note: Real project extracted, escaped @ is literal + + - title: "Date with colon and text:with:colons" + state: open + due_date: '2024-03-15' + file: input.md + line: 8 + indent: 0 + # Note: \: in text preserved as :, not parsed as key:value + + - title: "Unicode café résumé naïve" + state: open + file: input.md + line: 9 + indent: 0 + + - title: "Emoji task 🎯 📝 ✅" + state: open + file: input.md + line: 10 + indent: 0 + + - title: "Special chars: <>&\"'" + state: open + file: input.md + line: 11 + indent: 0 + + - title: "Backticks `code` and ```blocks```" + state: open + file: input.md + line: 12 + indent: 0 + + # Double backslash: \\ becomes literal \, so \\+ becomes \+ (not escaped, just literal backslash-plus) + - title: "Double backslash \\+NotProject \\@NotAssignee" + state: open + file: input.md + line: 13 + indent: 0 + # Note: \\ is literal backslash, the + and @ are NOT special because they follow the backslash diff --git a/tests/T10_edge_cases/input.md b/tests/T10_edge_cases/input.md new file mode 100644 index 0000000..68b4f49 --- /dev/null +++ b/tests/T10_edge_cases/input.md @@ -0,0 +1,50 @@ +# Edge Cases + +## Whitespace Handling + +- [ ] Extra spaces in title +- [ ] Tab in title +- [ ] Normal task + +## Empty and Minimal + +- [ ] +- [ ] A +- [ ] 1 + +## Malformed (should be preserved as non-tasks) + +- [] Missing space after bracket +- [y] Invalid state character +- [ ] Double space in checkbox +This is not a task + - [ ] Orphan subtask (no parent task) + +## Duplicate Metadata + +- [ ] Task +Project1 +Project2 due:2024-03-10 due:2024-03-15 +- [ ] Task @alice @bob @alice #tag #tag + +## Deeply Nested + +- [ ] Level 1 + - [ ] Level 2 + - [ ] Level 3 + - [ ] Level 4 + - [ ] Level 5 + +## Long Content + +- [ ] This is a very long task title that goes on and on and on and contains lots of words to test how the parser handles extremely long lines that might wrap in editors or cause buffer issues in naive implementations + +## Mixed Indentation + +- [ ] Parent with spaces + - [ ] Child with tab + - [ ] Child with 2 spaces + - [ ] Child with 4 spaces + +## Ambiguous Target (Error Case) + +- [ ] Duplicate title task +- [ ] Duplicate title task diff --git a/tests/T10_edge_cases/mutated.md b/tests/T10_edge_cases/mutated.md new file mode 100644 index 0000000..253d695 --- /dev/null +++ b/tests/T10_edge_cases/mutated.md @@ -0,0 +1,50 @@ +# Edge Cases + +## Whitespace Handling + +- [ ] Extra spaces in title +- [ ] Tab in title +- [ ] Normal task + +## Empty and Minimal + +- [ ] +- [ ] A +- [ ] 1 + +## Malformed (should be preserved as non-tasks) + +- [] Missing space after bracket +- [y] Invalid state character +- [ ] Double space in checkbox +This is not a task + - [ ] Orphan subtask (no parent task) + +## Duplicate Metadata + +- [ ] Task +Project1 +Project2 due:2024-03-10 due:2024-03-15 +- [ ] Task @alice @bob @alice #tag #tag + +## Deeply Nested + +- [ ] Level 1 + - [ ] Level 2 + - [x] Level 3 done:2024-03-10 + - [ ] Level 4 + - [ ] Level 5 + +## Long Content + +- [.] This is a very long task title that goes on and on and on and contains lots of words to test how the parser handles extremely long lines that might wrap in editors or cause buffer issues in naive implementations started:2024-03-10 + +## Mixed Indentation + +- [ ] Parent with spaces + - [ ] Child with tab + - [ ] Child with 2 spaces + - [ ] Child with 4 spaces + +## Ambiguous Target (Error Case) + +- [ ] Duplicate title task +- [ ] Duplicate title task diff --git a/tests/T10_edge_cases/mutation.yaml b/tests/T10_edge_cases/mutation.yaml new file mode 100644 index 0000000..02b4409 --- /dev/null +++ b/tests/T10_edge_cases/mutation.yaml @@ -0,0 +1,39 @@ +# Test mutations on edge cases: +# - Deep nesting state change +# - Long content preservation +# - Invalid mutation targets (not found, ambiguous) + +mutations: + - target: + title: "Level 3" + changes: + state: done + expected_result: + status: success + + - target: + title: "This is a very long task title that goes on and on and on and contains lots of words to test how the parser handles extremely long lines that might wrap in editors or cause buffer issues in naive implementations" + changes: + state: in_progress + expected_result: + status: success + + - target: + title: "Nonexistent task" + changes: + state: done + expected_result: + status: error + error: "Task not found" + + # Ambiguous target - two tasks have same title, mutation should error + - target: + title: "Duplicate title task" + changes: + state: done + expected_result: + status: error + error: "Ambiguous target: multiple tasks match" + +options: + today: "2024-03-10" diff --git a/tests/T10_edge_cases/parsed.yaml b/tests/T10_edge_cases/parsed.yaml new file mode 100644 index 0000000..873f19b --- /dev/null +++ b/tests/T10_edge_cases/parsed.yaml @@ -0,0 +1,174 @@ +# Edge case tests: +# - Whitespace normalization +# - Empty/minimal tasks +# - Malformed lines preserved as-is +# - Duplicate metadata handling +# - Deep nesting +# - Long content +# - Mixed indentation + +tasks: + # Whitespace handling section + - title: "Extra spaces in title" + state: open + file: input.md + line: 4 + indent: 0 + # Trailing/leading whitespace in title trimmed + + - title: "Tab in title" + state: open + file: input.md + line: 5 + indent: 0 + + - title: "Normal task" + state: open + file: input.md + line: 6 + indent: 0 + + # Empty/minimal - empty title task may be skipped or error + # Implementations may vary on whether "- [ ] " with no title is valid + - title: "A" + state: open + file: input.md + line: 10 + indent: 0 + + - title: "1" + state: open + file: input.md + line: 11 + indent: 0 + + # Malformed lines - NOT parsed as tasks, preserved as plain text + # "- [] Missing space" - not a valid checkbox + # "- [y] Invalid" - 'y' is not a valid state + # "[ ]" - double space not valid + # Orphan subtask with no parent - treated as top-level or error + + - title: "Orphan subtask (no parent task)" + state: open + file: input.md + line: 18 + indent: 2 + # Note: orphan subtask becomes top-level task + + # Duplicate metadata - last wins for single-value, merged for multi-value + - title: "Task" + state: open + project_path: Project2 + explicit_project: Project2 + due_date: '2024-03-15' + file: input.md + line: 20 + indent: 0 + # Note: +Project2 overrides +Project1, due:2024-03-15 overrides earlier + + - title: "Task" + state: open + assignees: + - alice + - bob + tags: + - tag + explicit_assignees: + - alice + - bob + explicit_tags: + - tag + file: input.md + line: 21 + indent: 0 + # Note: duplicate @alice deduplicated, duplicate #tag deduplicated + + # Deep nesting + - title: "Level 1" + state: open + file: input.md + line: 24 + indent: 0 + subtasks: + - title: "Level 2" + state: open + file: input.md + line: 25 + indent: 2 + subtasks: + - title: "Level 3" + state: open + file: input.md + line: 26 + indent: 4 + subtasks: + - title: "Level 4" + state: open + file: input.md + line: 27 + indent: 6 + subtasks: + - title: "Level 5" + state: open + file: input.md + line: 28 + indent: 8 + + # Long content + - title: "This is a very long task title that goes on and on and on and contains lots of words to test how the parser handles extremely long lines that might wrap in editors or cause buffer issues in naive implementations" + state: open + file: input.md + line: 31 + indent: 0 + + # Mixed indentation - treated as flattened or normalized + - title: "Parent with spaces" + state: open + file: input.md + line: 34 + indent: 0 + subtasks: + - title: "Child with tab" + state: open + file: input.md + line: 35 + indent: 1 + # Tab = 1 indent level + + - title: "Child with 2 spaces" + state: open + file: input.md + line: 36 + indent: 2 + + - title: "Child with 4 spaces" + state: open + file: input.md + line: 37 + indent: 4 + + # Ambiguous target - two tasks with same title (for error testing) + - title: "Duplicate title task" + state: open + file: input.md + line: 41 + indent: 0 + + - title: "Duplicate title task" + state: open + file: input.md + line: 42 + indent: 0 + +malformed_lines: + - line: 14 + content: "- [] Missing space after bracket" + reason: "No space after opening bracket" + + - line: 15 + content: "- [y] Invalid state character" + reason: "Invalid state character 'y'" + + - line: 16 + content: "- [ ] Double space in checkbox" + reason: "Double space in checkbox" diff --git a/tests/T11_locales/input.md b/tests/T11_locales/input.md new file mode 100644 index 0000000..4a0580a --- /dev/null +++ b/tests/T11_locales/input.md @@ -0,0 +1,33 @@ +# Multi-Locale Date Parsing Test + +## English (US) + +[[en_us.md]] + +## English (UK) + +[[en_gb.md]] + +## Spanish + +[[es.md]] + +## German + +[[de.md]] + +## Portuguese (Brazil) + +[[pt_br.md]] + +## Dutch + +[[nl.md]] + +## Russian + +[[ru.md]] + +## Chinese (Simplified) + +[[zh_cn.md]] diff --git a/tests/T11_locales/input_de.md b/tests/T11_locales/input_de.md new file mode 100644 index 0000000..44d2a38 --- /dev/null +++ b/tests/T11_locales/input_de.md @@ -0,0 +1,13 @@ +--- +taskmark: + locale: de_DE + timezone: Europe/Berlin + date_format: "%d. %B %Y[ %H:%M]" +--- + +# Deutsche Aufgaben + +- [ ] März-Besprechung planned:15. März 2024 +- [ ] Dezember-Frist due:31. Dezember 2024 +- [ ] Morgen-Standup planned:"15. März 2024 09:30" +- [ ] Tokyo-Anruf planned:2024-03-15T18:00+09:00 diff --git a/tests/T11_locales/input_en_gb.md b/tests/T11_locales/input_en_gb.md new file mode 100644 index 0000000..bf1fb43 --- /dev/null +++ b/tests/T11_locales/input_en_gb.md @@ -0,0 +1,13 @@ +--- +taskmark: + locale: en_GB + timezone: Europe/London + date_format: "%d %B %Y[ %H:%M]" +--- + +# English (UK) Tasks + +- [ ] March meeting planned:15 March 2024 +- [ ] December deadline due:31 December 2024 +- [ ] Afternoon call planned:"15 March 2024 14:30" +- [ ] NYC event planned:2024-03-15T10:00-04:00 diff --git a/tests/T11_locales/input_en_us.md b/tests/T11_locales/input_en_us.md new file mode 100644 index 0000000..1e185aa --- /dev/null +++ b/tests/T11_locales/input_en_us.md @@ -0,0 +1,13 @@ +--- +taskmark: + locale: en_US + timezone: America/New_York + date_format: "%B %d, %Y[ %H:%M]" +--- + +# English (US) Tasks + +- [ ] March meeting planned:March 15, 2024 +- [ ] December deadline due:December 31, 2024 +- [ ] Morning standup planned:"March 15, 2024 09:00" +- [ ] UTC event planned:2024-03-15T14:00Z diff --git a/tests/T11_locales/input_es.md b/tests/T11_locales/input_es.md new file mode 100644 index 0000000..c226a1a --- /dev/null +++ b/tests/T11_locales/input_es.md @@ -0,0 +1,13 @@ +--- +taskmark: + locale: es_ES + timezone: Europe/Madrid + date_format: "%d de %B de %Y[ %H:%M]" +--- + +# Tareas en Español + +- [ ] Reunión de marzo planned:15 de marzo de 2024 +- [ ] Fecha límite diciembre due:31 de diciembre de 2024 +- [ ] Llamada matutina planned:"15 de marzo de 2024 10:00" +- [ ] Evento Londres planned:2024-03-15T09:00+00:00 diff --git a/tests/T11_locales/input_nl.md b/tests/T11_locales/input_nl.md new file mode 100644 index 0000000..1e8a56c --- /dev/null +++ b/tests/T11_locales/input_nl.md @@ -0,0 +1,13 @@ +--- +taskmark: + locale: nl_NL + timezone: Europe/Amsterdam + date_format: "%d %B %Y[ %H:%M]" +--- + +# Nederlandse Taken + +- [ ] Maart vergadering planned:15 maart 2024 +- [ ] December deadline due:31 december 2024 +- [ ] Ochtend standup planned:"15 maart 2024 09:00" +- [ ] Sydney event planned:2024-03-15T20:00+11:00 diff --git a/tests/T11_locales/input_pt_br.md b/tests/T11_locales/input_pt_br.md new file mode 100644 index 0000000..d8f1022 --- /dev/null +++ b/tests/T11_locales/input_pt_br.md @@ -0,0 +1,13 @@ +--- +taskmark: + locale: pt_BR + timezone: America/Sao_Paulo + date_format: "%d de %B de %Y[ %H:%M]" +--- + +# Tarefas em Português + +- [ ] Reunião de março planned:15 de março de 2024 +- [ ] Prazo dezembro due:31 de dezembro de 2024 +- [ ] Chamada matinal planned:"15 de março de 2024 08:00" +- [ ] Evento Berlin planned:2024-03-15T14:00+01:00 diff --git a/tests/T11_locales/input_ru.md b/tests/T11_locales/input_ru.md new file mode 100644 index 0000000..bc565ba --- /dev/null +++ b/tests/T11_locales/input_ru.md @@ -0,0 +1,13 @@ +--- +taskmark: + locale: ru_RU + timezone: Europe/Moscow + date_format: "%d %B %Y[ %H:%M]" +--- + +# Русские Задачи + +- [ ] Мартовская встреча planned:15 марта 2024 +- [ ] Декабрьский дедлайн due:31 декабря 2024 +- [ ] Утренний стендап planned:"15 марта 2024 10:00" +- [ ] Событие в Нью-Йорке planned:2024-03-15T08:00-04:00 diff --git a/tests/T11_locales/input_zh_cn.md b/tests/T11_locales/input_zh_cn.md new file mode 100644 index 0000000..2528faf --- /dev/null +++ b/tests/T11_locales/input_zh_cn.md @@ -0,0 +1,13 @@ +--- +taskmark: + locale: zh_CN + timezone: Asia/Shanghai + date_format: "%Y年%m月%d日[ %H:%M]" +--- + +# 中文任务 + +- [ ] 三月会议 planned:2024年03月15日 +- [ ] 十二月截止 due:2024年12月31日 +- [ ] 早间站会 planned:"2024年03月15日 09:00" +- [ ] 伦敦活动 planned:2024-03-15T10:00+00:00 diff --git a/tests/T11_locales/mutated.md b/tests/T11_locales/mutated.md new file mode 100644 index 0000000..4a0580a --- /dev/null +++ b/tests/T11_locales/mutated.md @@ -0,0 +1,33 @@ +# Multi-Locale Date Parsing Test + +## English (US) + +[[en_us.md]] + +## English (UK) + +[[en_gb.md]] + +## Spanish + +[[es.md]] + +## German + +[[de.md]] + +## Portuguese (Brazil) + +[[pt_br.md]] + +## Dutch + +[[nl.md]] + +## Russian + +[[ru.md]] + +## Chinese (Simplified) + +[[zh_cn.md]] diff --git a/tests/T11_locales/mutated_de.md b/tests/T11_locales/mutated_de.md new file mode 100644 index 0000000..2d151a8 --- /dev/null +++ b/tests/T11_locales/mutated_de.md @@ -0,0 +1,13 @@ +--- +taskmark: + locale: de_DE + timezone: Europe/Berlin + date_format: "%d. %B %Y[ %H:%M]" +--- + +# Deutsche Aufgaben + +- [x] März-Besprechung planned:15. März 2024 done:15. März 2024 +- [ ] Dezember-Frist due:31. Dezember 2024 +- [ ] Morgen-Standup planned:"15. März 2024 09:30" +- [ ] Tokyo-Anruf planned:"15. März 2024 10:00" diff --git a/tests/T11_locales/mutated_en_gb.md b/tests/T11_locales/mutated_en_gb.md new file mode 100644 index 0000000..3b6a007 --- /dev/null +++ b/tests/T11_locales/mutated_en_gb.md @@ -0,0 +1,13 @@ +--- +taskmark: + locale: en_GB + timezone: Europe/London + date_format: "%d %B %Y[ %H:%M]" +--- + +# English (UK) Tasks + +- [x] March meeting planned:15 March 2024 done:15 March 2024 +- [ ] December deadline due:31 December 2024 +- [ ] Afternoon call planned:"15 March 2024 14:30" +- [ ] NYC event planned:"15 March 2024 14:00" diff --git a/tests/T11_locales/mutated_en_us.md b/tests/T11_locales/mutated_en_us.md new file mode 100644 index 0000000..5b7bdb4 --- /dev/null +++ b/tests/T11_locales/mutated_en_us.md @@ -0,0 +1,13 @@ +--- +taskmark: + locale: en_US + timezone: America/New_York + date_format: "%B %d, %Y[ %H:%M]" +--- + +# English (US) Tasks + +- [x] March meeting planned:March 15, 2024 done:March 15, 2024 +- [ ] December deadline due:December 31, 2024 +- [ ] Morning standup planned:"March 15, 2024 09:00" +- [ ] UTC event planned:"March 15, 2024 10:00" diff --git a/tests/T11_locales/mutated_es.md b/tests/T11_locales/mutated_es.md new file mode 100644 index 0000000..5ce8e23 --- /dev/null +++ b/tests/T11_locales/mutated_es.md @@ -0,0 +1,13 @@ +--- +taskmark: + locale: es_ES + timezone: Europe/Madrid + date_format: "%d de %B de %Y[ %H:%M]" +--- + +# Tareas en Español + +- [x] Reunión de marzo planned:15 de marzo de 2024 done:15 de marzo de 2024 +- [ ] Fecha límite diciembre due:31 de diciembre de 2024 +- [ ] Llamada matutina planned:"15 de marzo de 2024 10:00" +- [ ] Evento Londres planned:"15 de marzo de 2024 10:00" diff --git a/tests/T11_locales/mutated_nl.md b/tests/T11_locales/mutated_nl.md new file mode 100644 index 0000000..82e1744 --- /dev/null +++ b/tests/T11_locales/mutated_nl.md @@ -0,0 +1,13 @@ +--- +taskmark: + locale: nl_NL + timezone: Europe/Amsterdam + date_format: "%d %B %Y[ %H:%M]" +--- + +# Nederlandse Taken + +- [x] Maart vergadering planned:15 maart 2024 done:15 maart 2024 +- [ ] December deadline due:31 december 2024 +- [ ] Ochtend standup planned:"15 maart 2024 09:00" +- [ ] Sydney event planned:"15 maart 2024 10:00" diff --git a/tests/T11_locales/mutated_pt_br.md b/tests/T11_locales/mutated_pt_br.md new file mode 100644 index 0000000..3396548 --- /dev/null +++ b/tests/T11_locales/mutated_pt_br.md @@ -0,0 +1,13 @@ +--- +taskmark: + locale: pt_BR + timezone: America/Sao_Paulo + date_format: "%d de %B de %Y[ %H:%M]" +--- + +# Tarefas em Português + +- [x] Reunião de março planned:15 de março de 2024 done:15 de março de 2024 +- [ ] Prazo dezembro due:31 de dezembro de 2024 +- [ ] Chamada matinal planned:"15 de março de 2024 08:00" +- [ ] Evento Berlin planned:"15 de março de 2024 10:00" diff --git a/tests/T11_locales/mutated_ru.md b/tests/T11_locales/mutated_ru.md new file mode 100644 index 0000000..ce533f9 --- /dev/null +++ b/tests/T11_locales/mutated_ru.md @@ -0,0 +1,13 @@ +--- +taskmark: + locale: ru_RU + timezone: Europe/Moscow + date_format: "%d %B %Y[ %H:%M]" +--- + +# Русские Задачи + +- [x] Мартовская встреча planned:15 марта 2024 done:15 марта 2024 +- [ ] Декабрьский дедлайн due:31 декабря 2024 +- [ ] Утренний стендап planned:"15 марта 2024 10:00" +- [ ] Событие в Нью-Йорке planned:"15 марта 2024 15:00" diff --git a/tests/T11_locales/mutated_zh_cn.md b/tests/T11_locales/mutated_zh_cn.md new file mode 100644 index 0000000..bbd7145 --- /dev/null +++ b/tests/T11_locales/mutated_zh_cn.md @@ -0,0 +1,13 @@ +--- +taskmark: + locale: zh_CN + timezone: Asia/Shanghai + date_format: "%Y年%m月%d日[ %H:%M]" +--- + +# 中文任务 + +- [x] 三月会议 planned:2024年03月15日 done:2024年03月15日 +- [ ] 十二月截止 due:2024年12月31日 +- [ ] 早间站会 planned:"2024年03月15日 09:00" +- [ ] 伦敦活动 planned:"2024年03月15日 18:00" diff --git a/tests/T11_locales/mutation.yaml b/tests/T11_locales/mutation.yaml new file mode 100644 index 0000000..63d3472 --- /dev/null +++ b/tests/T11_locales/mutation.yaml @@ -0,0 +1,79 @@ +# Test locale-specific date serialization: +# - Complete one task in each locale +# - done: dates should be serialized in locale's format + +mutations: + - target: + file: en_us.md + title: "March meeting" + changes: + state: done + expected_result: + status: success + # done: "March 15, 2024" + + - target: + file: en_gb.md + title: "March meeting" + changes: + state: done + expected_result: + status: success + # done: "15 March 2024" + + - target: + file: es.md + title: "Reunión de marzo" + changes: + state: done + expected_result: + status: success + # done: "15 de marzo de 2024" + + - target: + file: de.md + title: "März-Besprechung" + changes: + state: done + expected_result: + status: success + # done: "15. März 2024" + + - target: + file: pt_br.md + title: "Reunião de março" + changes: + state: done + expected_result: + status: success + # done: "15 de março de 2024" + + - target: + file: nl.md + title: "Maart vergadering" + changes: + state: done + expected_result: + status: success + # done: "15 maart 2024" + + - target: + file: ru.md + title: "Мартовская встреча" + changes: + state: done + expected_result: + status: success + # done: "15 марта 2024" + + - target: + file: zh_cn.md + title: "三月会议" + changes: + state: done + expected_result: + status: success + # done: "2024年03月15日" + +options: + today: "2024-03-15" diff --git a/tests/T11_locales/parsed.yaml b/tests/T11_locales/parsed.yaml new file mode 100644 index 0000000..5f9964a --- /dev/null +++ b/tests/T11_locales/parsed.yaml @@ -0,0 +1,321 @@ +# Multi-locale date parsing test: +# - Tests parsing dates with locale-specific month names +# - Languages: English (US/UK), Spanish, German, Portuguese, Dutch, Russian, Chinese +# - Each file has its own timezone +# - Tests datetime with explicit TZ converted to file's timezone +# - All dates normalize to ISO format internally + +tasks: + # English (US) - en_US, America/New_York + - title: March meeting + state: open + planned_date: '2024-03-15' + file: en_us.md + line: 10 + indent: 0 + + - title: December deadline + state: open + due_date: '2024-12-31' + file: en_us.md + line: 11 + indent: 0 + + - title: Morning standup + state: open + planned_date: '2024-03-15T09:00-04:00' + file: en_us.md + line: 12 + indent: 0 + + - title: UTC event + state: open + planned_date: '2024-03-15T14:00Z' + file: en_us.md + line: 13 + indent: 0 + + # English (UK) - en_GB, Europe/London + - title: March meeting + state: open + planned_date: '2024-03-15' + file: en_gb.md + line: 10 + indent: 0 + + - title: December deadline + state: open + due_date: '2024-12-31' + file: en_gb.md + line: 11 + indent: 0 + + - title: Afternoon call + state: open + planned_date: '2024-03-15T14:30+00:00' + file: en_gb.md + line: 12 + indent: 0 + + - title: NYC event + state: open + planned_date: '2024-03-15T10:00-04:00' + file: en_gb.md + line: 13 + indent: 0 + + # Spanish - es_ES, Europe/Madrid + - title: Reunión de marzo + state: open + planned_date: '2024-03-15' + file: es.md + line: 10 + indent: 0 + + - title: Fecha límite diciembre + state: open + due_date: '2024-12-31' + file: es.md + line: 11 + indent: 0 + + - title: Llamada matutina + state: open + planned_date: '2024-03-15T10:00+01:00' + file: es.md + line: 12 + indent: 0 + + - title: Evento Londres + state: open + planned_date: '2024-03-15T09:00+00:00' + file: es.md + line: 13 + indent: 0 + + # German - de_DE, Europe/Berlin + - title: März-Besprechung + state: open + planned_date: '2024-03-15' + file: de.md + line: 10 + indent: 0 + + - title: Dezember-Frist + state: open + due_date: '2024-12-31' + file: de.md + line: 11 + indent: 0 + + - title: Morgen-Standup + state: open + planned_date: '2024-03-15T09:30+01:00' + file: de.md + line: 12 + indent: 0 + + - title: Tokyo-Anruf + state: open + planned_date: '2024-03-15T18:00+09:00' + file: de.md + line: 13 + indent: 0 + + # Portuguese (Brazil) - pt_BR, America/Sao_Paulo + - title: Reunião de março + state: open + planned_date: '2024-03-15' + file: pt_br.md + line: 10 + indent: 0 + + - title: Prazo dezembro + state: open + due_date: '2024-12-31' + file: pt_br.md + line: 11 + indent: 0 + + - title: Chamada matinal + state: open + planned_date: '2024-03-15T08:00-03:00' + file: pt_br.md + line: 12 + indent: 0 + + - title: Evento Berlin + state: open + planned_date: '2024-03-15T14:00+01:00' + file: pt_br.md + line: 13 + indent: 0 + + # Dutch - nl_NL, Europe/Amsterdam + - title: Maart vergadering + state: open + planned_date: '2024-03-15' + file: nl.md + line: 10 + indent: 0 + + - title: December deadline + state: open + due_date: '2024-12-31' + file: nl.md + line: 11 + indent: 0 + + - title: Ochtend standup + state: open + planned_date: '2024-03-15T09:00+01:00' + file: nl.md + line: 12 + indent: 0 + + - title: Sydney event + state: open + planned_date: '2024-03-15T20:00+11:00' + file: nl.md + line: 13 + indent: 0 + + # Russian - ru_RU, Europe/Moscow + - title: Мартовская встреча + state: open + planned_date: '2024-03-15' + file: ru.md + line: 10 + indent: 0 + + - title: Декабрьский дедлайн + state: open + due_date: '2024-12-31' + file: ru.md + line: 11 + indent: 0 + + - title: Утренний стендап + state: open + planned_date: '2024-03-15T10:00+03:00' + file: ru.md + line: 12 + indent: 0 + + - title: Событие в Нью-Йорке + state: open + planned_date: '2024-03-15T08:00-04:00' + file: ru.md + line: 13 + indent: 0 + + # Chinese (Simplified) - zh_CN, Asia/Shanghai + - title: 三月会议 + state: open + planned_date: '2024-03-15' + file: zh_cn.md + line: 10 + indent: 0 + + - title: 十二月截止 + state: open + due_date: '2024-12-31' + file: zh_cn.md + line: 11 + indent: 0 + + - title: 早间站会 + state: open + planned_date: '2024-03-15T09:00+08:00' + file: zh_cn.md + line: 12 + indent: 0 + + - title: 伦敦活动 + state: open + planned_date: '2024-03-15T10:00+00:00' + file: zh_cn.md + line: 13 + indent: 0 + +file_links: + - source: input.md + target: en_us.md + section: "English (US)" + line: 4 + + - source: input.md + target: en_gb.md + section: "English (UK)" + line: 7 + + - source: input.md + target: es.md + section: "Spanish" + line: 10 + + - source: input.md + target: de.md + section: "German" + line: 13 + + - source: input.md + target: pt_br.md + section: "Portuguese (Brazil)" + line: 16 + + - source: input.md + target: nl.md + section: "Dutch" + line: 19 + + - source: input.md + target: ru.md + section: "Russian" + line: 22 + + - source: input.md + target: zh_cn.md + section: "Chinese (Simplified)" + line: 25 + +frontmatter: + en_us.md: + locale: en_US + timezone: America/New_York + date_format: "%B %d, %Y[ %H:%M]" + + en_gb.md: + locale: en_GB + timezone: Europe/London + date_format: "%d %B %Y[ %H:%M]" + + es.md: + locale: es_ES + timezone: Europe/Madrid + date_format: "%d de %B de %Y[ %H:%M]" + + de.md: + locale: de_DE + timezone: Europe/Berlin + date_format: "%d. %B %Y[ %H:%M]" + + pt_br.md: + locale: pt_BR + timezone: America/Sao_Paulo + date_format: "%d de %B de %Y[ %H:%M]" + + nl.md: + locale: nl_NL + timezone: Europe/Amsterdam + date_format: "%d %B %Y[ %H:%M]" + + ru.md: + locale: ru_RU + timezone: Europe/Moscow + date_format: "%d %B %Y[ %H:%M]" + + zh_cn.md: + locale: zh_CN + timezone: Asia/Shanghai + date_format: "%Y年%m月%d日[ %H:%M]" diff --git a/examples/team-standup.md b/tests/T12_team_standup/input.md similarity index 76% rename from examples/team-standup.md rename to tests/T12_team_standup/input.md index d81aff9..29ebed4 100644 --- a/examples/team-standup.md +++ b/tests/T12_team_standup/input.md @@ -2,7 +2,7 @@ ## Backend @backend-team -- [x] 2024-11-01 2024-11-02 API rate limiting ~4h #security +- [x] API rate limiting ~4h #security planned:2024-11-01 done:2024-11-02 - [!] Database migration blocked by ops ~8h @alice due:2024-11-15 - [ ] Write migration script ~2h - [ ] Test on staging ~3h @@ -18,4 +18,4 @@ ## DevOps @ops - [ ] (A) CI/CD pipeline optimization ~6h #infra -- [x] 2024-10-28 2024-10-30 SSL certificate renewal ~1h +- [x] SSL certificate renewal ~1h planned:2024-10-28 done:2024-10-30 diff --git a/tests/T12_team_standup/mutated.md b/tests/T12_team_standup/mutated.md new file mode 100644 index 0000000..b039311 --- /dev/null +++ b/tests/T12_team_standup/mutated.md @@ -0,0 +1,21 @@ +# Team Standup +work #standup + +## Backend @backend-team + +- [x] API rate limiting ~4h #security planned:2024-11-01 done:2024-11-02 +- [!] Database migration blocked by ops ~8h @alice due:2024-11-15 + - [x] Write migration script ~2h done:2024-11-10 + - [ ] Test on staging ~3h + +## Frontend @frontend-team + +- [ ] (B) User dashboard redesign ~16h #ux + - [x] Mockups approved ~4h @designer + - [ ] Component implementation ~8h @bob + - [ ] Integration testing ~4h +- [-] Legacy widget removal + +## DevOps @ops + +- [ ] (A) CI/CD pipeline optimization ~6h #infra +- [x] SSL certificate renewal ~1h planned:2024-10-28 done:2024-10-30 diff --git a/tests/T12_team_standup/mutation.yaml b/tests/T12_team_standup/mutation.yaml new file mode 100644 index 0000000..0154c4b --- /dev/null +++ b/tests/T12_team_standup/mutation.yaml @@ -0,0 +1,9 @@ +# Complete a blocked subtask and start the parent task +target: + title: "Write migration script" +changes: + state: done +expected_result: + status: success +options: + today: "2024-11-10" diff --git a/tests/T12_team_standup/parsed.yaml b/tests/T12_team_standup/parsed.yaml new file mode 100644 index 0000000..ca6ceaa --- /dev/null +++ b/tests/T12_team_standup/parsed.yaml @@ -0,0 +1,101 @@ +# Team standup example - tests section inheritance, subtasks, all states + +tasks: + # Backend section + - title: API rate limiting + state: done + estimate: 4h + tags: [security] + planned: "2024-11-01" + done: "2024-11-02" + projects: [work] + assignees: [backend-team] + section: "Backend" + file: input.md + line: 5 + indent: 0 + + - title: Database migration blocked by ops + state: blocked + estimate: 8h + assignees: [backend-team, alice] + due: "2024-11-15" + projects: [work] + section: "Backend" + file: input.md + line: 6 + indent: 0 + subtasks: + - title: Write migration script + state: open + estimate: 2h + indent: 2 + + - title: Test on staging + state: open + estimate: 3h + indent: 2 + + # Frontend section + - title: User dashboard redesign + state: open + priority: B + estimate: 16h + tags: [ux] + projects: [work] + assignees: [frontend-team, designer, bob] + section: "Frontend" + file: input.md + line: 12 + indent: 0 + subtasks: + - title: Mockups approved + state: done + estimate: 4h + assignees: [designer] + indent: 2 + + - title: Component implementation + state: open + estimate: 8h + assignees: [bob] + indent: 2 + + - title: Integration testing + state: open + estimate: 4h + indent: 2 + + - title: Legacy widget removal + state: cancelled + projects: [work] + assignees: [frontend-team] + section: "Frontend" + file: input.md + line: 16 + indent: 0 + + # DevOps section + - title: CI/CD pipeline optimization + state: open + priority: A + estimate: 6h + tags: [infra] + projects: [work] + assignees: [ops] + section: "DevOps" + file: input.md + line: 20 + indent: 0 + + - title: SSL certificate renewal + state: done + estimate: 1h + planned: "2024-10-28" + done: "2024-10-30" + projects: [work] + assignees: [ops] + section: "DevOps" + file: input.md + line: 21 + indent: 0 diff --git a/examples/sprint-planning.md b/tests/T13_sprint_planning/input.md similarity index 87% rename from examples/sprint-planning.md rename to tests/T13_sprint_planning/input.md index d32339c..e930f42 100644 --- a/examples/sprint-planning.md +++ b/tests/T13_sprint_planning/input.md @@ -7,7 +7,7 @@ - [ ] Research rate limiting libraries ~30m - [ ] Implement middleware ~1h - [ ] Write tests ~30m -- [x] 2024-12-01 2024-12-03 Fix database connection pooling #bugfix +- [x] Fix database connection pooling #bugfix planned:2024-12-01 done:2024-12-03 ## Frontend +frontend diff --git a/tests/T13_sprint_planning/mutated.md b/tests/T13_sprint_planning/mutated.md new file mode 100644 index 0000000..7ca98a4 --- /dev/null +++ b/tests/T13_sprint_planning/mutated.md @@ -0,0 +1,22 @@ +# Sprint 23 +engineering #q4 + +## Backend +backend + +- [ ] (A) Implement user authentication ~4h @alice due:2024-12-15 +- [ ] Add rate limiting to API endpoints ~2h @bob #security + - [ ] Research rate limiting libraries ~30m + - [ ] Implement middleware ~1h + - [ ] Write tests ~30m +- [x] Fix database connection pooling #bugfix planned:2024-12-01 done:2024-12-03 + +## Frontend +frontend + +- [ ] (B) Redesign dashboard ~8h @carol #ux +- [!] Blocked by API changes @dave +- [-] Dark mode toggle cancelled + +## Documentation +docs + +- [ ] Update API docs desc:"Include rate limiting section" ~1h +- [ ] Write deployment guide repeat:weekly planned:2024-12-17 +- [x] Write deployment guide done:2024-12-10 diff --git a/tests/T13_sprint_planning/mutation.yaml b/tests/T13_sprint_planning/mutation.yaml new file mode 100644 index 0000000..c3848d4 --- /dev/null +++ b/tests/T13_sprint_planning/mutation.yaml @@ -0,0 +1,9 @@ +# Complete a recurring task - tests recurrence spawning +target: + title: "Write deployment guide" +changes: + state: done +expected_result: + status: success +options: + today: "2024-12-10" diff --git a/tests/T13_sprint_planning/parsed.yaml b/tests/T13_sprint_planning/parsed.yaml new file mode 100644 index 0000000..3547ab1 --- /dev/null +++ b/tests/T13_sprint_planning/parsed.yaml @@ -0,0 +1,112 @@ +# Sprint planning example - tests priorities, subtasks, custom metadata, recurrence + +tasks: + # Backend section + - title: Implement user authentication + state: open + priority: A + estimate: 4h + assignees: [dev, alice] + due: "2024-12-15" + projects: [engineering, backend] + tags: [q4] + section: "Backend" + file: input.md + line: 5 + indent: 0 + + - title: Add rate limiting to API endpoints + state: open + estimate: 2h + assignees: [dev, bob] + tags: [q4, security] + projects: [engineering, backend] + section: "Backend" + file: input.md + line: 6 + indent: 0 + subtasks: + - title: Research rate limiting libraries + state: open + estimate: 30m + indent: 2 + + - title: Implement middleware + state: open + estimate: 1h + indent: 2 + + - title: Write tests + state: open + estimate: 30m + indent: 2 + + - title: Fix database connection pooling + state: done + tags: [q4, bugfix] + planned: "2024-12-01" + done: "2024-12-03" + projects: [engineering, backend] + assignees: [dev] + section: "Backend" + file: input.md + line: 10 + indent: 0 + + # Frontend section + - title: Redesign dashboard + state: open + priority: B + estimate: 8h + assignees: [dev, carol] + tags: [q4, ux] + projects: [engineering, frontend] + section: "Frontend" + file: input.md + line: 14 + indent: 0 + + - title: Blocked by API changes + state: blocked + assignees: [dev, dave] + projects: [engineering, frontend] + tags: [q4] + section: "Frontend" + file: input.md + line: 15 + indent: 0 + + - title: Dark mode toggle cancelled + state: cancelled + projects: [engineering, frontend] + assignees: [dev] + tags: [q4] + section: "Frontend" + file: input.md + line: 16 + indent: 0 + + # Documentation section + - title: Update API docs + state: open + estimate: 1h + custom: + desc: "Include rate limiting section" + projects: [engineering, docs] + assignees: [dev] + tags: [q4] + section: "Documentation" + file: input.md + line: 20 + indent: 0 + + - title: Write deployment guide + state: open + repeat: weekly + projects: [engineering, docs] + assignees: [dev] + tags: [q4] + section: "Documentation" + file: input.md + line: 21 + indent: 0 diff --git a/tests/T14_custom_date_format/input.md b/tests/T14_custom_date_format/input.md new file mode 100644 index 0000000..ef7c5a3 --- /dev/null +++ b/tests/T14_custom_date_format/input.md @@ -0,0 +1,29 @@ +--- +locale: en_GB +datetime_format: "%d/%m/%Y[ %H:%M]" +timezone: Europe/London +--- + +# UK Project Tasks +uk-project #team + +## Sprint 1 +sprint1 + +- [ ] (A) Project kickoff meeting @alice planned:15/03/2024 due:15/03/2024 +- [x] Complete requirements document @bob planned:01/03/2024 done:05/03/2024 ~4h +- [ ] (B) Design system architecture @charlie planned:"10/03/2024 09:00" ~8h + - [ ] Database schema design ~2h + - [ ] API contract definition ~3h + - [x] Infrastructure planning @alice planned:08/03/2024 done:09/03/2024 ~3h + - Review with tech lead before finalizing + - Consider scalability requirements #repeat + +## Sprint 2 +sprint2 + +- [ ] Implementation phase @team planned:18/03/2024 due:01/04/2024 +- [ ] Weekly sync @team planned:"25/03/2024 10:00" repeat:weekly ~1h +- [!] External API integration blocked @bob planned:20/03/2024 + +## Documentation +docs + +- [ ] Write user guide @charlie planned:25/03/2024 ~6h +- [-] Legacy docs migration cancelled diff --git a/tests/T14_custom_date_format/mutated.md b/tests/T14_custom_date_format/mutated.md new file mode 100644 index 0000000..c5afc1e --- /dev/null +++ b/tests/T14_custom_date_format/mutated.md @@ -0,0 +1,29 @@ +--- +locale: en_GB +datetime_format: "%d/%m/%Y[ %H:%M]" +timezone: Europe/London +--- + +# UK Project Tasks +uk-project #team + +## Sprint 1 +sprint1 + +- [ ] (A) Project kickoff meeting @alice planned:15/03/2024 due:15/03/2024 +- [x] Complete requirements document @bob planned:01/03/2024 done:05/03/2024 ~4h +- [ ] (B) Design system architecture @charlie planned:"10/03/2024 09:00" ~8h + - [.] Database schema design ~2h started:12/03/2024 + - [ ] API contract definition ~3h + - [x] Infrastructure planning @alice planned:08/03/2024 done:09/03/2024 ~3h + - Review with tech lead before finalizing + - Consider scalability requirements #repeat + +## Sprint 2 +sprint2 + +- [ ] Implementation phase @team planned:18/03/2024 due:01/04/2024 +- [ ] Weekly sync @team planned:"25/03/2024 10:00" repeat:weekly ~1h +- [!] External API integration blocked @bob planned:20/03/2024 + +## Documentation +docs + +- [ ] Write user guide @charlie planned:25/03/2024 ~6h +- [-] Legacy docs migration cancelled diff --git a/tests/T14_custom_date_format/mutation.yaml b/tests/T14_custom_date_format/mutation.yaml new file mode 100644 index 0000000..e628cb6 --- /dev/null +++ b/tests/T14_custom_date_format/mutation.yaml @@ -0,0 +1,9 @@ +# Start a task with custom date format - tests datetime_format serialization +target: + title: "Database schema design" +changes: + state: in_progress +expected_result: + status: success +options: + today: "2024-03-12" diff --git a/tests/T14_custom_date_format/parsed.yaml b/tests/T14_custom_date_format/parsed.yaml new file mode 100644 index 0000000..404109e --- /dev/null +++ b/tests/T14_custom_date_format/parsed.yaml @@ -0,0 +1,131 @@ +# Custom date format example - tests frontmatter with locale, datetime_format, timezone + +frontmatter: + locale: en_GB + datetime_format: "%d/%m/%Y[ %H:%M]" + timezone: Europe/London + +tasks: + # Sprint 1 section + - title: Project kickoff meeting + state: open + priority: A + assignees: [team, alice] + planned: "2024-03-15" + due: "2024-03-15" + projects: [uk-project, sprint1] + tags: [team] + section: "Sprint 1" + file: input.md + line: 11 + indent: 0 + + - title: Complete requirements document + state: done + assignees: [team, bob] + planned: "2024-03-01" + done: "2024-03-05" + estimate: 4h + projects: [uk-project, sprint1] + tags: [team] + section: "Sprint 1" + file: input.md + line: 12 + indent: 0 + + - title: Design system architecture + state: open + priority: B + assignees: [team, charlie, alice] + planned: "2024-03-10T09:00" + estimate: 8h + projects: [uk-project, sprint1] + tags: [team] + section: "Sprint 1" + file: input.md + line: 13 + indent: 0 + subtasks: + - title: Database schema design + state: open + estimate: 2h + indent: 2 + + - title: API contract definition + state: open + estimate: 3h + indent: 2 + + - title: Infrastructure planning + state: done + assignees: [alice] + planned: "2024-03-08" + done: "2024-03-09" + estimate: 3h + indent: 2 + notes: + - text: "Review with tech lead before finalizing" + tags: [] + + - text: "Consider scalability requirements" + tags: [repeat] + + # Sprint 2 section + - title: Implementation phase + state: open + assignees: [team] + planned: "2024-03-18" + due: "2024-04-01" + projects: [uk-project, sprint2] + tags: [team] + section: "Sprint 2" + file: input.md + line: 22 + indent: 0 + + - title: Weekly sync + state: open + assignees: [team] + planned: "2024-03-25T10:00" + repeat: weekly + estimate: 1h + projects: [uk-project, sprint2] + tags: [team] + section: "Sprint 2" + file: input.md + line: 23 + indent: 0 + + - title: External API integration blocked + state: blocked + assignees: [team, bob] + planned: "2024-03-20" + projects: [uk-project, sprint2] + tags: [team] + section: "Sprint 2" + file: input.md + line: 24 + indent: 0 + + # Documentation section + - title: Write user guide + state: open + assignees: [team, charlie] + planned: "2024-03-25" + estimate: 6h + projects: [uk-project, docs] + tags: [team] + section: "Documentation" + file: input.md + line: 28 + indent: 0 + + - title: Legacy docs migration cancelled + state: cancelled + projects: [uk-project, docs] + assignees: [team] + tags: [team] + section: "Documentation" + file: input.md + line: 29 + indent: 0 diff --git a/examples/comprehensive.md b/tests/T15_comprehensive/input.md similarity index 71% rename from examples/comprehensive.md rename to tests/T15_comprehensive/input.md index 04c0d10..5ae414c 100644 --- a/examples/comprehensive.md +++ b/tests/T15_comprehensive/input.md @@ -4,12 +4,16 @@ ### Backend +api -- [ ] (A) Design REST API ~4h @alice #architecture due:2025-02-01 -- [x] 2025-01-10 2025-01-12 Implement auth endpoint @bob #security +- [.] (A) Design REST API ~4h @alice #architecture due:2025-02-01 started:2025-01-20 +- [x] Implement auth endpoint @bob #security planned:2025-01-10 done:2025-01-12 - [ ] (B) Add rate limiting ~2h #performance - [ ] Research libraries ~30m - - [x] 2025-01-15 Choose implementation @alice + - [x] Choose implementation @alice done:2025-01-15 + - Check existing rate limit in nginx #repeat + - Consider Redis for distributed limiting - [!] Database migration blocked #urgent + - Waiting for DBA approval + - Scheduled for next maintenance window ### Frontend +ui diff --git a/tests/T15_comprehensive/mutated.md b/tests/T15_comprehensive/mutated.md new file mode 100644 index 0000000..2a2aa3c --- /dev/null +++ b/tests/T15_comprehensive/mutated.md @@ -0,0 +1,46 @@ +# Project Alpha +alpha #team @lead status:active + +## Sprint 1 +sprint1 @dev + +### Backend +api + +- [x] (A) Design REST API ~4h @alice #architecture due:2025-02-01 started:2025-01-20 done:2025-01-25 +- [x] Implement auth endpoint @bob #security planned:2025-01-10 done:2025-01-12 +- [ ] (B) Add rate limiting ~2h #performance + - [ ] Research libraries ~30m + - [x] Choose implementation @alice done:2025-01-15 + - Check existing rate limit in nginx #repeat + - Consider Redis for distributed limiting +- [!] Database migration blocked #urgent + - Waiting for DBA approval + - Scheduled for next maintenance window + +### Frontend +ui + +- [ ] Build dashboard ~8h @charlie #feature priority:high +- [-] Old feature cancelled + +## Sprint 2 +sprint2 + +### Mobile +mobile + +- [ ] iOS app setup ~16h @dev repeat:weekly +- [ ] Android app ~20h env:"production" version:"2.0" + +## Standalone Section + +- [ ] Task without project inheritance #misc +- [ ] Task with escaped \@mention and \#hashtag + +# Inheritance Override Test +override #base @base-user + +- [ ] (C) Task overrides all #explicit @explicit-user +explicit-project +- [ ] Task inherits everything + +# Edge Cases + +- [ ] Very long title that goes on and on and on and on and on and on and on and on and on +- [ ] ~15m Quick task +- [ ] ~2d Multi-day task +- [ ] Multiple @user1 @user2 @user3 assignees +- [ ] Multiple #tag1 #tag2 #tag3 tags diff --git a/tests/T15_comprehensive/mutation.yaml b/tests/T15_comprehensive/mutation.yaml new file mode 100644 index 0000000..163ce2c --- /dev/null +++ b/tests/T15_comprehensive/mutation.yaml @@ -0,0 +1,9 @@ +# Complete an in-progress task - tests state transition with existing started date +target: + title: "Design REST API" +changes: + state: done +expected_result: + status: success +options: + today: "2025-01-25" diff --git a/tests/T15_comprehensive/parsed.yaml b/tests/T15_comprehensive/parsed.yaml new file mode 100644 index 0000000..daddd1d --- /dev/null +++ b/tests/T15_comprehensive/parsed.yaml @@ -0,0 +1,214 @@ +# Comprehensive example - tests deep nesting, escaping, multiple files, edge cases + +tasks: + # Project Alpha > Sprint 1 > Backend + - title: Design REST API + state: in_progress + priority: A + estimate: 4h + assignees: [lead, dev, alice] + tags: [architecture] + due: "2025-02-01" + started: "2025-01-20" + projects: [alpha, sprint1, api] + custom: + status: active + section: "Backend" + file: input.md + line: 7 + indent: 0 + + - title: Implement auth endpoint + state: done + assignees: [lead, dev, bob] + tags: [security] + planned: "2025-01-10" + done: "2025-01-12" + projects: [alpha, sprint1, api] + custom: + status: active + section: "Backend" + file: input.md + line: 8 + indent: 0 + + - title: Add rate limiting + state: open + priority: B + estimate: 2h + tags: [performance] + projects: [alpha, sprint1, api] + assignees: [lead, dev, alice] + custom: + status: active + section: "Backend" + file: input.md + line: 9 + indent: 0 + subtasks: + - title: Research libraries + state: open + estimate: 30m + indent: 2 + + - title: Choose implementation + state: done + assignees: [alice] + done: "2025-01-15" + indent: 2 + notes: + - text: "Check existing rate limit in nginx" + tags: [repeat] + + - text: "Consider Redis for distributed limiting" + tags: [] + + - title: Database migration blocked + state: blocked + tags: [urgent] + projects: [alpha, sprint1, api] + assignees: [lead, dev] + custom: + status: active + section: "Backend" + file: input.md + line: 14 + indent: 0 + notes: + - text: "Waiting for DBA approval" + tags: [] + + - text: "Scheduled for next maintenance window" + tags: [] + + # Project Alpha > Sprint 1 > Frontend + - title: Build dashboard + state: open + estimate: 8h + assignees: [lead, dev, charlie] + tags: [feature] + projects: [alpha, sprint1, ui] + custom: + status: active + priority: high + section: "Frontend" + file: input.md + line: 20 + indent: 0 + + - title: Old feature cancelled + state: cancelled + projects: [alpha, sprint1, ui] + assignees: [lead, dev] + custom: + status: active + section: "Frontend" + file: input.md + line: 21 + indent: 0 + + # Project Alpha > Sprint 2 > Mobile + - title: iOS app setup + state: open + estimate: 16h + assignees: [lead, dev] + repeat: weekly + projects: [alpha, sprint2, mobile] + custom: + status: active + section: "Mobile" + file: input.md + line: 27 + indent: 0 + + - title: Android app + state: open + estimate: 20h + projects: [alpha, sprint2, mobile] + assignees: [lead, dev] + custom: + status: active + env: production + version: "2.0" + section: "Mobile" + file: input.md + line: 28 + indent: 0 + + # Standalone Section (no project inheritance from above) + - title: Task without project inheritance + state: open + tags: [misc] + section: "Standalone Section" + file: input.md + line: 32 + indent: 0 + + - title: "Task with escaped @mention and #hashtag" + state: open + section: "Standalone Section" + file: input.md + line: 33 + indent: 0 + + # Inheritance Override Test section + - title: Task overrides all + state: open + priority: C + tags: [explicit] + assignees: [explicit-user] + projects: [explicit-project] + section: "Inheritance Override Test" + file: input.md + line: 37 + indent: 0 + + - title: Task inherits everything + state: open + tags: [base] + assignees: [base-user] + projects: [override] + section: "Inheritance Override Test" + file: input.md + line: 38 + indent: 0 + + # Edge Cases section + - title: Very long title that goes on and on and on and on and on and on and on and on and on + state: open + section: "Edge Cases" + file: input.md + line: 42 + indent: 0 + + - title: Quick task + state: open + estimate: 15m + section: "Edge Cases" + file: input.md + line: 43 + indent: 0 + + - title: Multi-day task + state: open + estimate: 2d + section: "Edge Cases" + file: input.md + line: 44 + indent: 0 + + - title: Multiple assignees + state: open + assignees: [user1, user2, user3] + section: "Edge Cases" + file: input.md + line: 45 + indent: 0 + + - title: Multiple tags + state: open + tags: [tag1, tag2, tag3] + section: "Edge Cases" + file: input.md + line: 46 + indent: 0 diff --git a/tests/TESTING.md b/tests/TESTING.md new file mode 100644 index 0000000..104fc7e --- /dev/null +++ b/tests/TESTING.md @@ -0,0 +1,708 @@ +# Golden Testing Framework + +This document describes how golden tests work for TaskMark conformance testing. + +--- + +## Overview + +Golden tests verify that a TaskMark implementation correctly: + +1. **Parses** markdown files into a structured data store +2. **Mutates** tasks according to mutation instructions +3. **Serializes** the modified data back to markdown files + +Each test is a self-contained universe with its own files, expected parse output, mutations, and expected serialized output. + +--- + +## Test Structure + +``` +tests/golden/ +├── CATALOG.md # Test catalog and coverage documentation +├── TESTING.md # This file +└── T{NN}_{name}/ + ├── input.md # Root file to parse (required) + ├── input_{linked}.md # Linked files (optional, 0 or more) + ├── parsed.yaml # Expected parse output (required) + ├── mutation.yaml # Mutations to apply (optional) + ├── mutated.md # Expected output after mutation (required if mutation.yaml exists) + └── mutated_{linked}.md # Expected linked file outputs (required if linked files exist and mutation.yaml exists) +``` + +--- + +## Test Execution Flow + +### Phase 1: Setup + +``` +┌─────────────────────────────────────────────────────────────┐ +│ TEST DIRECTORY │ +│ T06_frontmatter/ │ +│ input.md │ +│ input_us_office.md │ +│ input_uk_office.md │ +│ parsed.yaml │ +│ mutation.yaml │ +│ mutated.md │ +│ mutated_us_office.md │ +│ mutated_uk_office.md │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ TEMP DIRECTORY │ +│ /tmp/taskmark_test_xyz/ │ +│ input.md ← copied from input.md │ +│ us_office.md ← copied from input_us_office.md │ +│ uk_office.md ← copied from input_uk_office.md │ +└─────────────────────────────────────────────────────────────┘ +``` + +**File naming transformation:** + +- `input.md` → `input.md` (no change) +- `input_{name}.md` → `{name}.md` (remove `input_` prefix) + +This simulates a real project structure where linked files don't have test prefixes. + +### Phase 2: Parse + +``` +┌──────────────────┐ ┌──────────────────────────────────┐ +│ TEMP FILES │ │ TasksDataStore │ +│ │ │ │ +│ input.md │ │ files: [...] │ +│ us_office.md │──parse──│ tasks: [...] │ +│ uk_office.md │ │ file_links: [...] │ +│ │ │ │ +└──────────────────┘ └──────────────────────────────────┘ + + + ┌──────────────────────────────────┐ + │ ParseResult │ + │ │ + │ warnings: [...] │ + │ errors: [...] │ + │ malformed_lines: [...] │ + └──────────────────────────────────┘ +``` + +The parser reads `input.md` (the root file), follows any `[[file.md]]` links, and returns: + +- `TasksDataStore` - the parsed data structure +- `ParseResult` - warnings, errors, and malformed lines encountered during parsing + +### Phase 3: Verify Parse Output + +``` +┌──────────────────────────────────┐ ┌──────────────────────────────────┐ +│ TasksDataStore │ │ ParseResult │ +│ │ │ │ +│ files: [...] │ │ warnings: [...] │ +│ tasks: [...] │ │ errors: [...] │ +│ file_links: [...] │ │ malformed_lines: [...] │ +└──────────────────────────────────┘ └──────────────────────────────────┘ + │ │ + │ COMPARE │ + └──────────────────┬────────────────────┘ + │ + ▼ + ┌──────────────────────────────────┐ + │ parsed.yaml │ + │ │ + │ tasks: [...] │ + │ file_links: [...] │ + │ frontmatter: {...} │ + │ warnings: [...] │ + │ errors: [...] │ + │ malformed_lines: [...] │ + └──────────────────────────────────┘ + │ + ▼ + PASS/FAIL +``` + +**Comparison rules:** + +- `parsed.yaml` contains expected values from BOTH `TasksDataStore` AND `ParseResult` +- Every field in `parsed.yaml` MUST exist in the corresponding structure +- Every field in `parsed.yaml` MUST have matching value +- `TasksDataStore` and `ParseResult` MAY have additional fields not in `parsed.yaml` +- `parsed.yaml` is a **subset** of the combined output + +### Phase 4: Mutate (if mutation.yaml exists) + +``` +┌──────────────────────────────────┐ ┌──────────────────┐ +│ TasksDataStore │ │ mutation.yaml │ +│ (from parse) │ │ │ +│ │ │ mutations: [...] │ +│ files: [...] │ │ options: │ +│ tasks: [...] │ │ today: "..." │ +│ file_links: [...] │ │ │ +└──────────────────────────────────┘ └──────────────────┘ + │ │ + └───────────────┬───────────────────┘ + │ + ▼ + ┌──────────────────┐ + │ mutate() │ + └──────────────────┘ + │ + ┌───────────────┴───────────────┐ + ▼ ▼ +┌──────────────────────────────────┐ ┌──────────────────────────────────┐ +│ TasksDataStore │ │ MutationResult │ +│ (modified) │ │ │ +│ │ │ results: [ │ +│ files: [...] │ │ { status: success }, │ +│ tasks: [...] (updated) │ │ { status: error, │ +│ file_links: [...] │ │ error: "Task not found" } │ +│ │ │ ] │ +└──────────────────────────────────┘ └──────────────────────────────────┘ +``` + +**Mutation behavior:** + +- All mutations in `mutation.yaml` are applied together (atomically) +- `options.today` and other options are passed to the mutator for deterministic behavior +- The mutator returns TWO objects: + - `TasksDataStore` - the modified data structure + - `MutationResult` - status of each mutation (success or error) + +### Phase 5: Serialize + +``` +┌──────────────────────────────────┐ +│ TasksDataStore │ +│ (after mutation) │ +│ │ +│ files: [...] │ +│ tasks: [...] (updated) │ +│ file_links: [...] │ +└──────────────────────────────────┘ + │ + ▼ + ┌──────────────┐ + │ serialize() │ + └──────────────┘ + │ + ▼ +┌──────────────────────────────────┐ +│ TEMP FILES │ +│ (modified in place) │ +│ │ +│ input.md (updated) │ +│ us_office.md (updated) │ +│ uk_office.md (updated) │ +└──────────────────────────────────┘ +``` + +**Serialization rules:** + +- The serializer receives ONLY the `TasksDataStore` +- Files are modified **in place** in the temp directory +- Only task lines and their children (subtasks, notes) are modified +- **Frontmatter is NOT touched** by the serializer +- Non-task content (headers, plain text, malformed lines) is preserved + +### Phase 6: Verify Serialized Output + +``` +┌──────────────────────────────────┐ ┌──────────────────────────────────┐ +│ TEMP FILES │ │ EXPECTED FILES │ +│ (after serialize) │ │ (from test dir) │ +│ │ │ │ +│ input.md │────▶│ mutated.md │ +│ us_office.md │ │ mutated_us_office.md │ +│ uk_office.md │ │ mutated_uk_office.md │ +└──────────────────────────────────┘ └──────────────────────────────────┘ + │ + ▼ + COMPARE (normalized) + │ + ▼ + PASS/FAIL + (+ warnings) +``` + +**File mapping for comparison:** + +- `input.md` (temp) ↔ `mutated.md` (test dir) +- `{name}.md` (temp) ↔ `mutated_{name}.md` (test dir) + +**Comparison rules:** + +- Comparison is **normalized** to prevent false failures: + - Trailing whitespace ignored + - Trailing newlines normalized + - Line endings normalized (CRLF → LF) +- If normalized content matches: **PASS** +- If normalized content matches but byte-for-byte differs: **PASS with WARNING** +- If normalized content differs: **FAIL** + +--- + +## Data Structures + +### TasksDataStore + +The central data structure produced by parsing. Contains ONLY the successfully parsed data. + +```yaml +TasksDataStore: + # Files that were parsed + files: + - path: "input.md" # Relative path + frontmatter: # Parsed frontmatter (null if none) + locale: "en_US" + timezone: "America/New_York" + date_format: "%B %d, %Y" + content: "..." # Raw file content (for serialization) + + # All tasks across all files + tasks: + - id: "task_001" # Unique identifier + title: "Task description" + state: open # open | in_progress | done | cancelled | blocked + + # Location + file: "input.md" + line: 5 + indent: 0 + + # Metadata - combined (inherited + local + downstream) + priority: "A" + project_path: "Project/Sub" + assignees: ["alice", "bob"] + tags: ["urgent", "backend"] + estimate_minutes: 120 + + # Dates + created_date: "2024-03-01" + planned_date: "2024-03-10" + started_date: "2024-03-10" + paused_date: null + due_date: "2024-03-15" + done_date: null + + # Recurrence + recurrence: "weekly" # null if none + + # Custom fields + custom_fields: + type: "bug" + ticket: "ENG-123" + + # Metadata provenance - tracks where each attribute came from + inherited: # From parent sections/headers + project_path: "Project" + assignees: ["alice"] + tags: ["backend"] + custom_fields: + severity: "high" + + local: # From the task line itself + priority: "A" + project_path: "Sub" # Joins with inherited: Project/Sub + assignees: ["bob"] + tags: ["urgent"] + estimate_minutes: 120 + due_date: "2024-03-15" + + downstream: # Propagated from subtasks + assignees: ["charlie"] # Subtask had @charlie + tags: ["critical"] # Subtask had #critical + + # Children + subtasks: + - id: "subtask_001" + title: "Subtask description" + state: open + file: "input.md" + line: 6 + indent: 2 + # ... same structure as parent task + + notes: + - text: "Note content" + file: "input.md" + line: 7 + has_repeat_tag: false + + # File links discovered during parsing + file_links: + - source: "input.md" + target: "us_office.md" + section: "US Office" + line: 4 +``` + +### ParseResult + +Returned alongside TasksDataStore from the parser. Contains warnings, errors, and malformed lines. + +```yaml +ParseResult: + # Parser warnings (non-fatal issues) + warnings: + - file: "input.md" + line: 10 + code: "W001" + message: "Duplicate tag '#urgent' ignored" + + # Parser errors (fatal issues for specific items) + errors: + - file: "input.md" + line: 15 + code: "E001" + message: "Invalid state character 'y'" + + # Malformed lines (preserved but not parsed as tasks) + malformed_lines: + - file: "input.md" + line: 14 + content: "- [] Missing space after bracket" + reason: "No space after opening bracket" +``` + +### parsed.yaml Schema + +The expected parse output file. A **subset** of TasksDataStore AND ParseResult combined. + +```yaml +# Comment describing what this test covers + +tasks: + - title: "Task description" + state: open + file: input.md + line: 5 + indent: 0 + # Only include fields being tested + # Omitted fields are not verified + +# Optional sections - only include what you're testing +file_links: + - source: input.md + target: us_office.md + section: "US Office" + line: 4 + +frontmatter: + us_office.md: + locale: en_US + timezone: America/New_York + date_format: "%B %d, %Y" + +warnings: + - file: input.md + line: 10 + code: "W001" + message: "Duplicate tag ignored" + +errors: + - file: input.md + line: 15 + code: "E001" + message: "Invalid state character" + +malformed_lines: + - line: 14 + content: "- [] Missing space" + reason: "No space after opening bracket" +``` + +### mutation.yaml Schema + +Mutations to apply after parsing. + +```yaml +# Comment describing mutation behavior being tested + +mutations: + - target: + title: "Task description" # Match by title (simple case) + changes: + state: done + expected_result: + status: success + + - target: + file: "us_office.md" # Match by file + title (multi-file) + title: "Quarterly review" + changes: + state: done + expected_result: + status: success + + - target: + title: "Nonexistent task" + changes: + state: done + expected_result: + status: error + error_code: "E100" + error_message: "Task not found" + +# Options passed to mutator for deterministic behavior +options: + today: "2024-03-15" # Fixed date for done:/started:/paused: + now: "2024-03-15T10:30:00Z" # Fixed datetime (optional) +``` + +### MutationResult Schema + +Result returned by the mutator. + +```yaml +MutationResult: + results: + - target: + title: "Task description" + status: success + changes_applied: + state: done + done_date: "2024-03-15" # Auto-added + + - target: + title: "Nonexistent task" + status: error + error_code: "E100" + error_message: "Task not found" + + # Summary + total: 3 + succeeded: 2 + failed: 1 +``` + +--- + +## Warning and Error Codes + +### Parser Warnings (Wxxx) + +| Code | Description | +|------|-------------| +| W001 | Duplicate tag ignored | +| W002 | Duplicate assignee ignored | +| W003 | Duplicate custom field (last value used) | +| W004 | Duplicate date field (last value used) | +| W005 | Mixed indentation (tabs and spaces) | + +### Parser Errors (Exxx) + +| Code | Description | +|------|-------------| +| E001 | Invalid state character | +| E002 | Malformed checkbox (no space after bracket) | +| E003 | Invalid date format | +| E004 | Invalid estimate format | +| E005 | Linked file not found | + +### Mutation Errors (E1xx) + +| Code | Description | +|------|-------------| +| E100 | Task not found | +| E101 | Ambiguous target (multiple tasks match) | +| E102 | Invalid state transition | +| E103 | Cannot modify inherited metadata | + +--- + +## Test Runner Pseudocode + +```python +def run_golden_test(test_dir): + # Phase 1: Setup + temp_dir = create_temp_directory() + copy_file(test_dir / "input.md", temp_dir / "input.md") + for file in glob(test_dir / "input_*.md"): + name = file.name.replace("input_", "") + copy_file(file, temp_dir / name) + + # Phase 2: Parse + data_store, parse_result = parse(temp_dir / "input.md") + + # Phase 3: Verify Parse + expected = load_yaml(test_dir / "parsed.yaml") + # Compare TasksDataStore fields + assert_subset_match(expected, data_store, keys=["tasks", "files", "file_links", "frontmatter"]) + # Compare ParseResult fields + assert_subset_match(expected, parse_result, keys=["warnings", "errors", "malformed_lines"]) + + # Phase 4: Mutate (if mutation.yaml exists) + mutation_file = test_dir / "mutation.yaml" + if mutation_file.exists(): + mutations = load_yaml(mutation_file) + data_store, mutation_result = mutate(data_store, mutations["mutations"], mutations["options"]) + verify_mutation_results(mutation_result, mutations["mutations"]) + + # Phase 5: Serialize + serialize(data_store) + + # Phase 6: Verify Output + compare_normalized(temp_dir / "input.md", test_dir / "mutated.md") + for file in glob(test_dir / "mutated_*.md"): + name = file.name.replace("mutated_", "") + compare_normalized(temp_dir / name, file) + + # Cleanup + cleanup_temp_directory(temp_dir) + +def assert_subset_match(expected, actual): + """Verify all fields in expected exist and match in actual.""" + for key, value in expected.items(): + assert key in actual, f"Missing key: {key}" + if isinstance(value, dict): + assert_subset_match(value, actual[key]) + elif isinstance(value, list): + assert_list_match(value, actual[key]) + else: + assert actual[key] == value, f"Mismatch for {key}" + +def compare_normalized(actual_file, expected_file): + """Compare files with normalization.""" + actual = read_file(actual_file) + expected = read_file(expected_file) + + actual_normalized = normalize(actual) + expected_normalized = normalize(expected) + + if actual_normalized != expected_normalized: + raise AssertionError(f"Content mismatch:\n{diff(actual, expected)}") + + if actual != expected: + emit_warning(f"Byte-level difference in {actual_file} (normalized match)") + +def normalize(content): + """Normalize content for comparison.""" + # Normalize line endings + content = content.replace("\r\n", "\n") + # Remove trailing whitespace from lines + lines = [line.rstrip() for line in content.split("\n")] + # Remove trailing empty lines + while lines and lines[-1] == "": + lines.pop() + return "\n".join(lines) +``` + +--- + +## Example Test Walkthrough + +### T01_minimal + +**Test directory contents:** + +``` +T01_minimal/ + input.md + parsed.yaml + mutation.yaml + mutated.md +``` + +**Phase 1: Setup** + +``` +/tmp/test_xyz/ + input.md ← copy of T01_minimal/input.md +``` + +**Phase 2: Parse** `input.md`: + +```markdown +# Tasks + +- [ ] Simple task +- [ ] Another task +``` + +**Returns two objects:** + +`TasksDataStore`: + +```yaml +files: + - path: "input.md" + frontmatter: null +tasks: + - title: "Simple task" + state: open + file: input.md + line: 3 + indent: 0 + - title: "Another task" + state: open + file: input.md + line: 4 + indent: 0 +file_links: [] +``` + +`ParseResult`: + +```yaml +warnings: [] +errors: [] +malformed_lines: [] +``` + +**Phase 3: Verify** against `parsed.yaml`: + +```yaml +tasks: + - title: Simple task + state: open + file: input.md + line: 3 + indent: 0 + - title: Another task + state: open + file: input.md + line: 4 + indent: 0 +``` + +✓ All fields match + +**Phase 4: Mutate** using `mutation.yaml`: + +```yaml +target: + title: "Simple task" +changes: + state: done +options: + today: "2024-03-15" +``` + +**Phase 5: Serialize** to temp files + +**Phase 6: Compare** `/tmp/test_xyz/input.md` to `T01_minimal/mutated.md`: + +```markdown +# Tasks + +- [x] Simple task done:2024-03-15 +- [ ] Another task +``` + +✓ Match + +--- + +## Best Practices for Writing Golden Tests + +1. **One concept per test**: Each test should focus on a single feature or behavior +2. **Minimal input**: Use the smallest input that demonstrates the behavior +3. **Explicit expectations**: Only include fields in `parsed.yaml` that you want to verify +4. **Deterministic dates**: Always use `options.today` in mutations +5. **Document intent**: Add comments at the top of `parsed.yaml` and `mutation.yaml` +6. **Cover edge cases**: Include malformed input to test error handling +7. **Test both success and failure**: Include mutations that should error