Minimalist, ultra-fast, Markdown-native task tracker for devs and agents.
Lash is a terminal-first task tracker where Markdown is the source of truth and everything else is an acceleration layer.
It is intended to be:
- Minimalist – few moving parts, opinionated format.
- Ultra-fast – optimized parsing, indexing, and search.
- Markdown-native – all task data lives in a strictly formatted Markdown tree.
- Agent-friendly – trivial for LLM agents (Claude Code, etc.) to use safely.
This document defines the concepts, data model, file formats, commands, and architecture so that we can derive concrete implementation tasks from it.
This is a living document: sections marked as “TBD/Decide” are design surfaces to refine, not blockers.
-
Markdown as the single source of truth
- All tasks/subtasks live in
.mdfiles. - The SQLite layer is fully reconstructible from the Markdown tree.
- All tasks/subtasks live in
-
Strict, linter-enforced format
- Predictable for agents.
- Predictable for tools and code.
- Enforced by a fast Rust linter accessible via CLI and TUI.
-
Dependency graph over a tree
- Within a file: checkbox hierarchy.
- Across files: directory structure + explicit links.
- A task or file “completes” only when its dependent children are complete or explicitly waived.
-
Terminal-first UX
-
Single binary CLI for:
- Linting, formatting, indexing.
- Querying tasks.
- Generating agent usage prompts.
-
Simple TUI for browsing, filtering, and editing tasks (via external
$EDITORor inline operations).
-
-
Cross-cutting labels & fast search
- Labels for dynamic slices (e.g.
#backend,#infra,#agent). - Fuzzy search across task titles, bodies, labels, filenames, and references.
- Labels for dynamic slices (e.g.
-
Agent-oriented design
- Easy to generate context-minimized prompts for LLMs.
- Data structures & algorithms tuned to reduce token usage.
- Rich, structured errors that are understandable both by humans and agents.
- Multi-user synchronization / shared server.
- Complex workflow automation (e.g. Kanban, burndown charts).
- Rich formatting beyond what is needed for tasks and annotations.
- Web UI (CLI/TUI only in v1).
-
A Lash repo lives under a filesystem root.
-
A root index Markdown file (e.g.
lash.index.mdorindex.lash.md) exists at the root. -
The root index:
- Provides a nested outline of the directory structure.
- Points to topic files.
- Optionally stores project-wide metadata (title, description, global tags).
-
A topic file is a Markdown file containing a depth-limited hierarchical checklist plus annotations.
-
Examples:
photo-app.filters.sepia.mdcore.api.auth.md
-
Each topic file:
- Encodes a subtree of tasks.
- May reference other topic files.
- Forms part of the dependency graph.
-
Directory names use the same dot-delimited topical naming convention as files.
-
Filenames reflect their place in the conceptual graph:
photo-app/filters/sepia.md- Or
photo-app.filters/sepia.md - Or pure “flat with dot-paths” like
photo-app.filters.sepia.md.
-
Decision: Lash treats path + filename as the canonical task “namespace”. Dot-conventions help agents/humans see conceptual structure at a glance.
-
Tasks are primarily expressed as checkbox items with optional metadata.
-
Dependencies:
-
Within file: parent checkbox depends on its nested child checkboxes.
-
Across files: files depend on the completion of:
- Their direct subdirectories.
- Any tasks explicitly referenced via links.
-
-
A task/file is:
- Incomplete until all dependencies are
doneorwaived. - Blocked if any dependency is incomplete and not waived.
- Incomplete until all dependencies are
-
Labels are lightweight tags attached to tasks.
-
Modeled either as:
- Inline markers (e.g.
#backend,#agent,#rust). - Or a structured annotation line (
@labels: backend, agent).
- Inline markers (e.g.
-
Labels support:
- Cross-cutting slices (e.g., “all tasks
#agentfor the coding agent”). - Agent prompt generation (“give me all
#high-prioritytasks for this agent”).
- Cross-cutting slices (e.g., “all tasks
-
A SQLite database stores:
- Parsed tasks.
- Dependency edges.
- Labels.
- File metadata (paths, hashes, mtime).
- Search indexes.
-
It is rebuildable from the Markdown tree at any time.
Each task file has:
- Header block (YAML-like or fenced metadata; exact syntax TBD but consistent).
- Description section (optional but recommended) - Free-form Markdown text explaining the scope, context, and intent of the file.
- Task tree (hierarchical checkbox list).
- Optional reference/notes section.
Example sketch (not final syntax, but concrete enough to implement):
# Photo App – Sepia Filter
@id: photo-app.filters.sepia
@labels: photo-app, filters, image-processing
@owner: frank
@created: 2025-11-16
## Description
This file tracks implementation of the sepia filter effect for the photo app.
The sepia filter applies a warm, vintage tone to images by shifting RGB values
toward brown/tan colors. @agent-note: This is a non-destructive operation that
should preserve the original image data.
Key constraints: must process 4K images in under 100ms, integrate with existing
filter pipeline, support both CPU and GPU implementations.
## Tasks
- [ ] Implement sepia filter core
- [ ] Define parameter schema
- [ ] Write Rust core function
- [ ] Add tests
- [ ] Integrate with UI
- [ ] Wire up settings panel
- [ ] Hook into preview
- [ ] Performance & QA
- [ ] Benchmark on sample images
- [ ] Fix regressions
## References
- Depends on: `../core/image-pipeline.md`
- Related: `../photo-app.filters.vignette.md`The ## Description section:
- Placement: Must appear after the metadata block and before
## Tasks. - Purpose: Provides contextual information about the file's scope, goals, constraints, and implementation notes. This helps both humans and agents understand the purpose and context before diving into tasks.
- Content: Free-form Markdown text. May include:
- Overview of what this file covers
- Key constraints or requirements
- Design decisions or architectural notes
- Implementation guidance
- Inline
@agent-note:annotations for LLM-specific hints
- Length limits:
- Recommended: 500-1000 characters
- Warning threshold: 1000 characters (linter will warn)
- Error threshold: 2000 characters (linter will fail)
- Rationale: Keeps descriptions concise and agent-token-friendly while allowing sufficient context.
- Optional but recommended: While not strictly required, the linter may suggest adding a description section for files with significant task trees or complex dependencies.
Canonical task line grammar (conceptual):
TASK_LINE := INDENT* "- [STATUS] " TITLE (METADATA_BLOCK?) (INLINE_LABELS?)
NOTE_LINE := INDENT* "- " TEXT
Where:
-
STATUSis one of:[ ]= open[-]= waived/not applicable[x]= done[!]= blocked (optional extension)
-
METADATA_BLOCKmight be a trailing structured element like:[@id: sepia-core, @labels: filters,backend, @estimate: 2h]
-
INLINE_LABELS:#backend #filters #agent
Contextual notes are plain bullet points (without checkboxes) nested under tasks that serve as inline context, requirements, or acceptance criteria:
- [ ] Implement sepia filter core
- Use ColorMatrix transformation for efficiency
- Target < 50ms for 4K images
- [ ] Define parameter schema
- [ ] Write Rust core functionKey semantics:
- Checkbox items (
- [ ]) = actionable tasks requiring completion tracking - Plain bullet items (
-) = contextual notes (no completion tracking) - Notes must be indented exactly 2 spaces deeper than their parent task
- Notes cannot have children (enforced by linter)
- Notes should appear before child tasks (style convention, soft warning)
- Notes are indexed in SQLite as searchable metadata on the parent task
- Notes are included in FTS5 fuzzy search
Disambiguation: Markdown links like - [Link Text](url) are NOT parsed as contextual notes. The parser detects the ]( pattern after the bracket to distinguish links from notes.
Best Practices:
When to use contextual notes vs. child tasks:
-
Use notes for:
- Requirements and constraints ("Must support multi-tenancy")
- Acceptance criteria ("Results within 100ms for 95th percentile")
- Implementation hints ("Use Redis for session storage")
- API/library specifics ("Stripe API v3, not v2")
- Design context ("Moving from JSON to TOML per ADR-015")
-
Use child tasks for:
- Multi-step processes that need completion tracking
- Independently trackable work items
- Work that can be done in parallel
- Sub-tasks that may themselves have sub-tasks
-
Style guidelines:
- Keep notes concise (warning at 200 chars, error at 500 chars)
- Place notes before child tasks for better readability
- Use notes for "why" and "how", tasks for "what"
- Never nest notes under notes
See examples/contextual-notes.md for comprehensive examples and patterns.
- Maximum depth, e.g. 3 or 4 levels (
-,-,-,-). - Enforced by linter to keep trees shallow and agent-friendly.
A small, fixed set of annotations, for example:
@id: <string>– unique within the file; combined with file path to form a globally unique task ID.@labels: a, b, c@owner: <string>@estimate: <duration>(for optional planning).@depends-on: <path or id>(explicit extra dependencies).@agent-note:– a short note addressed to agents (“LLM-specific hints”).
These appear in either:
- The header, e.g. file-level metadata.
- Inline with tasks (inside a trailing metadata block).
The linter ensures:
- No unknown annotation tags.
- Correct formats.
-
Parent tasks are implicitly dependent on all of their descendant tasks.
-
Rules:
- A non-leaf task cannot be
donewhile any descendant isopenorblocked. - A descendant may be
waived([-]) to explicitly mark it as unnecessary.
- A non-leaf task cannot be
-
Let
Dbe a directory. -
Let
child_files(D)be all topic files directly underD. -
Let
child_dirs(D)be all subdirectories underD. -
Rule:
-
A topic file in directory
Dis considered conceptually dependent on the complete closure ofchild_dirs(D)if the root index defines such a relationship. -
This is encoded either:
- Implicitly: directory-level semantics.
- Or explicitly: via annotations in the root index or directory metadata file (e.g.
@depends-on-dirs: filters, core).
-
- Tasks can explicitly reference other tasks/files:
Examples:
@depends-on: ../core/image-pipeline.md#task:normalize-frames
@depends-on: photo-app.filters.base#task:filter-registry-
The linter and accelerator:
- Resolve these references.
- Track them as graph edges.
- Detect broken links.
-
Task is complete when:
- Status
done, and - All descendants are
doneorwaived, and - All explicit dependencies (by ID/path) are complete or waived.
- Status
-
File/topic is complete when:
- All top-level tasks are complete, and
- Any configured directory-level dependencies are complete.
The root index file gives a coarse map of the project.
Example:
# Lash Task Index
@project: Lash
@version: 0.1
## Core
- [ ] `core/architecture.md`
- [ ] `core/cli.md`
- [ ] `core/tui.md`
## Acceleration Layer
- [ ] `accel/indexing.md`
- [ ] `accel/sqlite-schema.md`
## Agents
- [ ] `agents/usage.md`
- [ ] `agents/prompt-templates.md`The linter enforces:
- Paths exist.
- No cycles in this high-level reference layer (or cycles are detectable and surfaced).
- Single binary:
lash - Built in Rust as a static or mostly static binary where possible.
- Cross-platform: macOS, Linux, Windows.
-
lashcommands always run relative to a project root:- Either inferred (search upward for
lash.index.mdor.lash/). - Or explicitly specified via
--root.
- Either inferred (search upward for
-
Exit codes & messages designed to be:
- Human friendly.
- Agent friendly (optional
--jsonoutput).
1. Linting & Formatting
-
lash lint [PATH...]-
Validate Markdown format.
-
Validate annotations and task structures.
-
Validate links and IDs.
-
Options:
--json: machine-readable diagnostics.--fix: auto-fix simple issues (whitespace, ordering, etc.).
-
-
lash format [PATH...]- Normalize indentation, ordering of annotations, etc.
2. Indexing & DB Operations
-
lash index- Walk the project tree.
- Parse all Markdown files.
- Rebuild the SQLite acceleration layer from scratch.
-
lash check-index- Verify that DB is consistent with Markdown.
- Optionally show drift.
3. Querying
-
lash list [FILTERS]-
List tasks matching criteria:
--label backend--status open--path core/--blocked--agent <name>(based on owner/labels).
-
-
lash search <QUERY>- Fuzzy search against titles, bodies, and labels.
-
lash show <TASK_ID_OR_PATH>- Render the canonical view of a specific task/file, including dependencies and status.
4. Graph & Links
-
lash graph [--format=dot] [--scope=path_or_label]- Output dependency graph (e.g., for
dot).
- Output dependency graph (e.g., for
-
lash check-links-
Scan for:
- Broken
@depends-onreferences. - Broken file paths in index.
- Broken
-
5. Agent Prompt Generation
-
lash agent-prompt --format=plain|json|agents-md [OPTIONS] -
lash skill install --target=claude|codex|cursor|agents-md [--scope=project|user]-
Generate text that explains:
-
How the task system is structured.
-
How to safely:
- Add tasks.
- Mark tasks done.
- Add dependencies.
-
-
Context-minimized:
- Only include the essential schema, examples, and allowed operations.
-
Options:
--for-owner <name>: include relevant tasks for that agent.--include-examples: embed exemplar task files.
-
6. Maintenance Utilities
-
lash archive [OPTIONS]- Move completed/obsolete tasks into an archive directory.
- Update index and links (where possible).
-
lash reindex- Alias for
lash index.
- Alias for
-
lash fix-links- Attempt to auto-resolve broken links based on fuzzy matching, optionally interactive.
7. TUI Launcher
-
lash tui- Start TUI interface (see section 8).
The TUI focuses on readability & velocity, not fancy UI.
-
Left pane: navigation tree (directories, files, labels).
-
Right pane: current topic file with:
- Task list.
- Status summary.
- Dependencies overview.
-
Keyboard-centric:
j/kor arrows: move selection.space: toggle task status.e: open file in$EDITOR./: fuzzy search.l: filter by label.g: view dependency graph summary for selected task/file.?: help overlay.
-
TUI can show a “Agent view”:
- Highlight tasks tagged with
#agentor owned by an agent. - Summarize tasks into a short bullet list that matches token budgets.
- Highlight tasks tagged with
-
Source of truth: Markdown.
-
DB: ephemeral and fully reconstructible.
-
Performance:
-
Optimize for:
lint + indexon medium-large repos.- Interactive search and filtering.
-
Tables (names illustrative):
-
filesid(PK)path(unique)hash(content hash, e.g. blake3)mtimestatus(overall file state)description(TEXT; content from## Descriptionsection)labels(optional normalized relation or JSON)meta(JSON blob for extra header info)
-
tasksid(PK; internal integer)file_id(FK)local_id(string from@idor synthesized)full_id(uniquepath#local_id)titlestatus(open, done, waived, blocked)depthparent_id(FK to tasks.id for hierarchy)order_indexlabels(optional relation or JSON)ownerestimatebody(optional long text for detail)contextual_notes(JSON array of strings; plain bullet notes nested under this task)
-
dependenciesfrom_task_idto_task_idkind(implicit-hierarchy, explicit-id, explicit-path, directory)
-
labelsidname
-
task_labelstask_idlabel_id
-
file_labelsfile_idlabel_id
Indexes:
- On
tasks.status,labels.name,files.path. - FTS (if used) on
tasks.title,tasks.body,tasks.contextual_notes,files.path,files.description.
Two possible approaches (can be evaluated experimentally):
- SQLite FTS5 for full-text search.
- Lightweight in-Rust fuzzy matcher (e.g. building a simple index in memory from DB rows).
In both cases, the DB stores the canonical data; fuzzy search runs on top.
-
Validate syntax:
- Correct Markdown checkbox patterns.
- Indentation rules.
- Annotation formats.
-
Validate semantics:
- Unique
@idwithin file. - Proper depth limits.
- No unknown annotation keys.
- Correct
@depends-onresolution (optionally using DB if available). - Status consistency: parents vs children.
- Unique
-
Provide fixes where safe:
- Normalize indentation.
- Sort annotations.
- Add missing header boilerplate.
-
Implemented in Rust using:
- Handwritten or lightweight parser (no overkill).
-
Operates in:
- Single-file mode: for pre-commit hooks.
- Project mode: integrated with
lash index.
-
Human-readable default:
- File path, line/col, short message.
-
Machine-readable:
-
--jsonwith:code(stable diagnostic ID).severity.message.location.- Optional
suggested_fix.
-
This allows agents to:
- Detect issues.
- Offer auto-fix workflows.
The typical agent (e.g., Claude Code) will:
- Call
lash agent-prompt ...to get instructions and optionally a task subset. - Read/modify specific Markdown files.
- Call
lash lintto validate changes. - Possibly call
lash indexor incremental indexing commands after larger updates.
-
Schema-first prompts
- Provide a minimal, stable schema for the task format.
- Avoid embedding large samples unless requested.
- Use small, representative snippets as examples.
-
Sparse context
-
For a given agent action, include:
-
The specific topic file being edited.
-
Its immediate parents/children (just headers & statuses).
-
A short textual summary (generated by Lash) of relevant dependencies:
- e.g. “This task depends on 3 tasks in
core/image-pipeline.md(all done).”
- e.g. “This task depends on 3 tasks in
-
-
-
Summarization layer
-
Lash can optionally store auto-generated summaries of large files:
- Short bullet list of major tasks and status.
-
For agents, only the summary + specific task snippet is included.
-
-
ID-based references
- Encourage agents to refer to tasks by
full_idrather than copying entire descriptions into the prompt.
- Encourage agents to refer to tasks by
-
Compact formats
- When generating JSON for agents (e.g.
--format=json), keep keys short but stable. - Allow agents to request “schema-only mode” vs “schema+examples”.
- When generating JSON for agents (e.g.
lash agent-prompt should be able to output:
-
Plain text instructions explaining:
- Allowed operations.
- Format rules.
- Error-handling expectations.
-
Claude Code skill spec:
-
A JSON/YAML spec containing:
- Command names.
- Arguments.
- Example invocations.
-
-
Agents.md fragment:
-
A ready-to-paste Markdown block describing:
- “How Lash works.”
- “How to use Lash safely as an agent.”
-
-
Errors are:
- Expressive – clearly explain what went wrong and how to fix it.
- Structured – stable machine-readable codes and fields.
- Non-panicky – prefer “soft” errors where possible with guidance.
-
“Broken dependency”:
-
Human:
core/cli.md:42: Unknown dependency '@depends-on: core/parser#task:xyz' (target not found). -
JSON:
{ "code": "E_DEP_NOT_FOUND", "file": "core/cli.md", "line": 42, "dependency": "core/parser#task:xyz", "suggestion": "Check that the target file exists and contains a task with this id." }
-
-
“Depth limit exceeded”:
-
Human:
core/architecture.md:57: Task depth (5) exceeds configured maximum (3). Consider flattening or splitting the file.
-
Standardized exit codes, e.g.:
0– success.1– general error.2– lint errors.3– index errors.4– configuration or project root issues.
Suggested crate/module boundaries:
-
lash-cli- CLI parsing (e.g.
clap). - Integrates all subsystems.
- CLI parsing (e.g.
-
lash-core- Markdown parser.
- Task model.
- Linter.
- Dependency resolution.
-
lash-db- SQLite schema & migrations.
- Indexing and query layer.
-
lash-tui- TUI implementation (e.g. based on
crossterm+ratatuior equivalent).
- TUI implementation (e.g. based on
-
lash-agent- Prompt generation.
- Token minimization utilities.
- JSON schema definitions for agents.
-
Use incremental indexing where possible:
- Track
mtimeand hash of files. - Only re-parse on change.
- Track
-
Optionally use
PRAGMAtuning in SQLite (WAL mode, synchronous options) if safe for the use case.
Potential future features (beyond v1):
-
Per-user configuration file for:
- Depth limits.
- Default labels.
- Agent-specific settings.
-
Templates for creating new topic files.
-
Integration with external tools:
- Git hooks (
pre-commitlint). - Editors (VS Code, Neovim) via a language server or simple CLI integration.
- Git hooks (
-
More sophisticated archiving / history:
- Optional commit snapshots of completed tasks.
These don’t need answers before starting v1 but should be decided as we iterate:
-
Exact syntax of the header block (YAML frontmatter vs inline
@annotations only). -
Concrete maximum depth for task trees (3 vs 4).
-
Preferred canonical naming scheme:
photo-app.filters.sepia.mdin a flat directory vs nested directories.
-
Choice of crates for:
- TUI.
- Fuzzy search (native vs SQLite FTS).
-
Whether to support inline rich Markdown (code blocks, tables) inside task bodies in v1.