lithos-loom project import — reference¶
Pulled from the lithos-loom repo
This page is imported at build time from docs/cli/project-import.md on main. Edit it there, not here.
Import a local Markdown file as a Lithos project — and (by default) extract its - [ ] task lines as real Lithos task entities with dependency edges derived from indentation.
This is the canonical doc for the command. The PRD that drove its design is at docs/prd/archive/bulk-task-import.md.
TL;DR¶
# Greenfield: create project + import open tasks from a vault doc
lithos-loom project import ~/Dropbox/obsidian/dave/projects/organising-myself.md
# Preview what would happen — no Lithos writes
lithos-loom project import path/to/doc.md --dry-run
# Just add tasks to an existing project (no doc create)
lithos-loom project import new-tasks.md --tasks-only --slug existing-project
# Bring an existing project to a clean state and re-import
lithos-loom project import doc.md --tasks-only --slug X --force-tasks --yes
# Doc-only: import the body verbatim, skip task extraction
lithos-loom project import doc.md --no-tasks
What gets created¶
For each - [ ] open-task line in the source body, a Lithos task is created with:
| Lithos field | Source |
|---|---|
title |
the line text after stripping - [ ], tags, priority emojis, [sequential] marker |
tags |
the line's #foo tags + auto-added #project/<slug> |
metadata.project |
the importing project's slug |
metadata.priority |
mapped from priority emoji if present |
task_type |
epic when this line had indented children below it, else task |
parent_task_id |
the containing line's task id, when this line was indented under one (creates a parent_child edge) |
depends_on |
the previous sibling's task id, when the parent carried [sequential] (creates a blocks edge) |
Tasks are created in document order, which Lithos requires: a parent_task_id
or depends_on target must already exist, and a parent always precedes its
children (as does a sequential sibling its successor).
The matched task lines are stripped from the persisted doc body — the project-context doc holds narrative only; tasks live as Lithos task entities (single source of truth).
- [x], - [/], - [-], - [>] and any other markers stay verbatim in the body — only - [ ] open tasks are extracted.
Two modes¶
Greenfield (default)¶
Creates the project doc and the tasks. Refuses if a project at the slug already exists (the error message suggests --tasks-only --slug X as the alternative).
--tasks-only¶
Skips doc creation; just imports tasks against an existing project. Requires --slug. Refuses if the project doesn't exist (suggests typo matches when edit-distance ≤ 2) or already has tasks (unless --force-tasks is passed).
--tasks-only ignores frontmatter title and any tags: for project routing — a safety check to prevent silent mis-routing. If the source file's frontmatter has a lithos_id AND it doesn't resolve to the same project as --slug, the import is refused.
--no-tasks (escape hatch)¶
Skips task extraction entirely; imports only the doc body. Mutually exclusive with --tasks-only.
Task extraction rules¶
Line filter¶
Only - [ ] at line start (after optional leading whitespace) counts as a task. Not parsed:
* [ ]or+ [ ](only-is supported, per the canonical Obsidian convention)- [ ]inside fenced code blocks (```and~~~)- [ ]inside blockquotes (> ...)- mid-sentence
- [ ]in prose
This restriction means the parser won't accidentally pick up example task lines you wrote in code samples or quoted material.
Tags¶
Tags matching #[A-Za-z0-9_/-]+ after a whitespace boundary are extracted from each line. All-digit tags (#123) are NOT parsed — they stay as literal text in the task description (so issue references like #42 are preserved).
#project/<other-slug> (different from the importing project) triggers a refusal with the line number — see "Validation" below.
#project/<importing-slug> is silently consumed (it's auto-added anyway).
Priority emojis¶
Mapped to metadata.priority:
| Emoji | Value |
|---|---|
| 🔺 | highest |
| ⏫ | high |
| 🔼 | medium |
| 🔽 | low |
| ⏬ | lowest |
Multiple priority emojis on one line: highest precedence wins; all are stripped from the description.
[sequential] marker¶
Append the literal token [sequential] to a parent task's description to flip its children from parallel (default) to a sequential chain — child[i].depends_on = child[i-1].
- [ ] Implement [sequential]
- [ ] Step 1
- [ ] Step 2 ← depends on Step 1
- [ ] Step 3 ← depends on Step 2
The marker is case-sensitive (lowercase s), must be a standalone token (won't false-positive on prose like "sequential planning"), and only takes effect on tasks that have indented children.
Hierarchy from indentation¶
- Top-level tasks (no indent) are flat: no dependencies between them. Doc ordering imposes no execution semantics.
- Indented children represent composition: the parent becomes an
epicand each child gets aparent_task_idpointing at it — aparent_childedge. The epic is a container, not work: Lithos's ready-queue excludes epics by type, so nothing ever dispatches it, and it takes no dependency on its children. Mark it complete manually once the children are done. - Sibling children are parallel by default — that is the absence of any
blocksedge between them, not a flag.[sequential]on the parent chains them instead: each child getsdepends_on = [previous sibling].
Mixed indentation (tabs + spaces in the same doc) is supported — anything with a strictly-deeper leading-whitespace count than the previous line is treated as a child. Pure-tab and pure-spaces docs work intuitively; mixed indentation gives you what you wrote.
- [ ] Feature A ← top-level (flat with B)
- [ ] A.1 ← parallel child of A
- [ ] A.2 ← parallel child of A
- [ ] Feature B [sequential] ← top-level
- [ ] B.1 ← first in B's chain
- [ ] B.2 ← depends on B.1
Slug derivation¶
Priority order:
--slugflag (explicit override).- Slugified frontmatter
title(NOT prefix-stripped — frontmatter is explicit operator intent). - Slugified file stem, with a leading
project-prefix stripped first.
The prefix-strip lets you keep filesystem-organisation prefixes:
# File: ~/Dropbox/.../projects/project-organising-myself.md
# No frontmatter title; no --slug
lithos-loom project import ./project-organising-myself.md
# → slug = "organising-myself" (not "project-organising-myself")
The strip applies only to the default-from-stem path. Override with --slug project-foo if your project is literally named "project-foo" (rare).
--dry-run flags when the strip fired:
WOULD CREATE project:
slug=organising-myself (stripped leading 'project-' from stem 'project-organising-myself'; override with --slug)
Validation (validate-all-then-abort)¶
The import refuses upfront — before any Lithos writes — when any of these occur. All problems are reported in one pass so you can fix everything in one edit cycle:
| Problem | Exit |
|---|---|
Cross-project tag (#project/<other-slug>) on any line |
2 |
Empty parent (parent task is just - [ ] with indented children below) |
2 |
Invalid slug (doesn't match ^[a-z0-9](https://github.com/agent-lore/lithos-loom/blob/main/docs/cli/?:[a-z0-9-]*[a-z0-9])?$) |
2 |
Greenfield-only refusals:
| Problem | Exit |
|---|---|
Source frontmatter has lithos_id (re-importing a projected file would duplicate) |
2 |
| Slug already exists in Lithos | 1 (error suggests --tasks-only --slug X) |
--tasks-only-only refusals:
| Problem | Exit |
|---|---|
--slug not provided (frontmatter is ignored for routing in tasks-only mode) |
2 |
| Project doesn't exist for slug | 1 (error suggests near-miss matches when edit-distance ≤ 2) |
Source lithos_id resolves to a different project than --slug |
2 |
Project already has tasks AND --force-tasks not passed |
1 (error suggests --force-tasks) |
--dry-run¶
Prints the full plan and exits without any Lithos mutations. The same read-only pre-flight as a real run is performed first, so problems surface before you commit:
- Greenfield: a
note_listagainstprojects/<slug>/checks for slug collision. Exits 1 with the same "slug already exists; did you mean--tasks-only --slug X?" message a real run would give. --tasks-only: verifies the project exists (with typo-hint on miss), and that anylithos_idin frontmatter resolves to the same slug. Exits 1 (project missing) or 2 (lithos_id mismatch) on pre-flight failure.
If the pre-flight passes, the full plan is printed. No note_write / task_create / task_cancel calls are made under any code path.
Output is framed with NO CHANGES MADE — re-run without --dry-run to apply at the start AND end so it can't be mistaken for a success log.
NO CHANGES MADE — re-run without --dry-run to apply
WOULD CREATE project:
slug=organising-myself
title=Organising Myself
tags=['project-context', 'productivity']
body=<284 chars after stripping 9 task lines>
WOULD CREATE 9 tasks (top-level: flat; nested: children of an epic):
1. "Set up GTD inbox" #productivity #project/organising-myself priority=high
2. "Triage weekly review" #productivity #weekly #project/organising-myself priority=medium
3. "Implement next-actions list" #project/organising-myself type=epic
3a. "Define context tags" #project/organising-myself
3b. "Build weekly review template" #project/organising-myself
...
NO CHANGES MADE — re-run without --dry-run to apply
The preview reflects the final state including auto-added #project/<slug> routing tags. What you see is what gets written.
--force-tasks¶
Cancels every open task for the project before importing fresh.
- Completed and cancelled tasks remain as history. Lithos has no hard-delete primitive (E5), and re-cancelling a completed task would rewrite history from "done" to "cancelled" — wrong. The new import creates a fresh open set alongside the preserved record.
- Gated by interactive y/N confirm. Default is no on bare Enter. Use
--yesto bypass for scripted use.
$ lithos-loom project import doc.md --tasks-only --slug existing --force-tasks
Cancel 4 open tasks (8 resolved tasks will remain as history) and create 12 new ones? [y/N]: y
The prompt always shows both counts so you know exactly what's being destroyed vs preserved.
Partial-failure recovery (E1)¶
If Lithos fails mid-batch (e.g. network blip after N of M tasks are created), the import:
- Stops creating tasks.
- Posts a
[Friction]finding attached to the first successfully-created task. Summary includes the recovery command, project slug, andN/Mcount. - Exits non-zero with an operator-facing message that surfaces the recovery command.
lithos-loom: partial import — created 4/10 tasks before failure (LithosClientError: network ...).
A [Friction] finding has been posted with the recovery command; re-run with
--tasks-only --slug X --force-tasks to complete.
The recovery command uses existing flag composition — --tasks-only --slug X --force-tasks will cancel the partial set and re-import fresh, no new mechanics needed.
If zero tasks were created before the failure (failure happened during doc-create or the very first task_create), the breadcrumb is logged as a [Friction] WARNING instead — no task exists to attach the finding to.
All flags¶
| Flag | Default | Purpose |
|---|---|---|
<source> (positional) |
required | Path to the local Markdown file. |
--slug, -s |
derived | Project slug. Greenfield: optional (see "Slug derivation" above). --tasks-only: required. |
--tags |
none | Comma-separated extra tags for the project doc (greenfield only; ignored with --tasks-only). Union'd with frontmatter tags: + project-context. |
--tasks-only |
false | Skip doc creation; just import tasks against an existing project. Requires --slug. |
--no-tasks |
false | Skip task extraction entirely. Mutually exclusive with --tasks-only. |
--force-tasks |
false | Cancel open tasks for this project before importing. Gated by interactive y/N unless --yes is passed. |
--yes, -y |
false | Suppress the --force-tasks confirmation prompt. Silently a no-op without --force-tasks. |
--dry-run |
false | Print the full plan and exit; no Lithos writes (only read-only pre-flight call). |
--format, -f |
text |
text (vault path on stdout) or json ({id, slug, vault_path, tasks_created}). |
--config, -c |
$LITHOS_LOOM_CONFIG |
Explicit TOML config path. |
Mutually-exclusive flag combinations¶
| Combo | Result |
|---|---|
--no-tasks + --tasks-only |
exit 2 |
--no-tasks + --force-tasks |
exit 2 (force-tasks needs task extraction) |
--tasks-only without --slug |
exit 2 |
Exit codes¶
Mirror project create:
| Code | Meaning |
|---|---|
0 |
Success (or clean user abort at --force-tasks prompt). |
1 |
Lithos call failure, slug-collision refusal, missing-project refusal, partial-import failure, config load failure. |
2 |
Input validation failure (mutually-exclusive flags, invalid slug, malformed task lines, cross-project tag, empty parent, lithos_id/--slug mismatch, unreadable source). |
Worked examples¶
Onboarding an existing Obsidian project doc¶
You have ~/Dropbox/obsidian/dave/projects/project-website-redesign.md with 14 open - [ ] lines.
# Preview first.
lithos-loom project import ~/Dropbox/obsidian/dave/projects/project-website-redesign.md --dry-run
# Looks good — go.
lithos-loom project import ~/Dropbox/obsidian/dave/projects/project-website-redesign.md
# → /home/dns/Dropbox/obsidian/dave/_lithos/projects/website-redesign/website-redesign-project-context.md
The new project lands as website-redesign (the leading project- prefix is stripped), the 14 tasks are created with metadata.project = "website-redesign" and #project/website-redesign tag, and the projected doc + tasks appear in your vault's _lithos/ tree within ~250ms.
Recovering from a partial-import failure¶
The first attempt failed at task 8 of 14 with a network blip. Lithos has:
- A new project doc (website-redesign).
- 7 tasks created.
- A [Friction] finding attached to the first task with the recovery command.
# Wipe the 7 partial tasks (8 resolved will remain as history) and re-import.
lithos-loom project import ~/Dropbox/.../project-website-redesign.md \
--tasks-only --slug website-redesign --force-tasks --yes
Resolved-task history from previous successful imports is preserved; only the partial set is cancelled.
Adding tasks to a project you created manually¶
You ran lithos-loom project create --title "Phase 2" to set up the project context, then drafted the task list in a separate phase-2-tasks.md scratch file.
No doc write happens; the file's phase-2-tasks stem is irrelevant — --slug phase-2 is what matters.
Importing just the doc body, no tasks¶
Some PRDs have prose - [ ] checklists that aren't really actionable tasks (success criteria, acceptance gates, etc.). For those:
Body is persisted verbatim, including the - [ ] lines.
Scripted usage¶
# Capture the new task count for downstream automation.
result=$(lithos-loom project import doc.md --format json)
echo "$result" | jq '.tasks_created' # → 12
See also¶
docs/SPECIFICATION.md— the operator + integrator spec; §4.8 covers this command in summary, §7 covers projection and bidirectional sync.docs/prd/archive/bulk-task-import.md— original design decision table.docs/macros/README.md— the Templater macros forcreate-projectandcapture-task. Theproject importCLI is intentionally not surfaced as a macro (it's a one-shot adoption tool, not a recurring capture flow).docs/prd/archive/task-archive.md— companion feature for migrating completed-task history.