feat: local markdown sync (vk sync) #1

Merged
jmarya merged 4 commits from mdsync into main 2026-09-29 16:40:16 +00:00
Owner

Adds vk sync: a one-way pull of Vikunja into a local markdown tree. Vikunja stays the source of truth; the local tree is a derived backup.

  • One note per task (YAML frontmatter + description converted to markdown), under directories that mirror the project hierarchy, plus an index note per project
  • A .vk-sync.json manifest records what was written, so re-runs move renamed notes and remove deleted ones. Files the manifest doesn't list are never touched
  • Flags: --output/-o (default ./local), --project/-p (repeatable, by title or id), --done, --archived, --dry-run/-n
  • Exits non-zero if any operation failed

Also adds get_all_tasks_filtered, which pages through every result and returns request errors instead of silently truncating.

Verified: merged with current main (vikunjars 2.5.0, dirs 7). Build, clippy and fmt are clean and 35 tests pass. A live run against a real instance wrote 142 tasks across 21 projects, and a second run reported "already up to date".

🤖 Generated with Claude Code

Adds `vk sync`: a one-way pull of Vikunja into a local markdown tree. Vikunja stays the source of truth; the local tree is a derived backup. - One note per task (YAML frontmatter + description converted to markdown), under directories that mirror the project hierarchy, plus an index note per project - A `.vk-sync.json` manifest records what was written, so re-runs move renamed notes and remove deleted ones. Files the manifest doesn't list are never touched - Flags: `--output/-o` (default `./local`), `--project/-p` (repeatable, by title or id), `--done`, `--archived`, `--dry-run/-n` - Exits non-zero if any operation failed Also adds `get_all_tasks_filtered`, which pages through every result and returns request errors instead of silently truncating. **Verified:** merged with current main (vikunjars 2.5.0, dirs 7). Build, clippy and fmt are clean and 35 tests pass. A live run against a real instance wrote 142 tasks across 21 projects, and a second run reported "already up to date". 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat: implement local sync
All checks were successful
ci/woodpecker/pr/rust-test/2 Pipeline was successful
ci/woodpecker/pr/rust-test/1 Pipeline was successful
ci/woodpecker/pr/rust-test/3 Pipeline was successful
03743c27d5
jmarya force-pushed mdsync from 03743c27d5
All checks were successful
ci/woodpecker/pr/rust-test/2 Pipeline was successful
ci/woodpecker/pr/rust-test/1 Pipeline was successful
ci/woodpecker/pr/rust-test/3 Pipeline was successful
to 6dd3d1f4e6
Some checks failed
moira/test moira/test — failed
2026-07-28 07:55:10 +00:00
Compare
Finishes the local sync as a one-way pull: Vikunja stays the source of
truth and the local tree is a derived backup.

Reconciliation. Repeat runs previously left debris behind: a retitled
task produced a second note under its new name, and a task deleted
upstream kept its note forever. Sync now records what it wrote in a
.vk-sync.json manifest at the output root and reconciles against it, so
renames move notes and deletions remove them. Only paths listed in the
manifest are ever rewritten or deleted, so unrelated files in the output
directory are never touched, and a corrupt manifest degrades to writing
rather than deleting.

Project hierarchy. Projects are laid out mirroring Vikunja's
parent/child structure instead of being flattened. Siblings whose titles
sanitize to the same segment get their id appended so they can no longer
share a directory, and a malformed parent chain is depth-capped rather
than looping forever.

Done tasks. Tasks came from whichever view happened to be first, so
inclusion of done tasks was incidental. They are now fetched from the
task endpoint with an explicit filter, re-checked locally in case the
server ignores it, and exposed as --done.

Pseudo-projects such as Favorites duplicate tasks that already live in a
real project. They are now detected by their non-positive id rather than
the literal English title, which never matched on localized instances.

Also adds --project to sync a subset (entries for projects outside the
run are carried forward, never pruned), --archived, and --dry-run, plus
a per-project progress line and a change summary. Exits non-zero when
any operation failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
jmarya force-pushed mdsync from 96fe9e0604
Some checks failed
moira/test moira/test — failed
to 109dbf59e6
Some checks failed
moira/test moira/test — failed
2026-07-28 08:31:49 +00:00
Compare
# Conflicts:
#	Cargo.lock
#	Cargo.toml
When a task moved from a project outside the run (filtered by --project,
or archived) into one being synced, the new note was written and the old
one removed, but reconciliation then carried the stale manifest entry
forward over the fresh one. The manifest pointed at the deleted note and
the new note was untracked, so later renames or deletions left it behind.

Entries written this run now take precedence over carried ones. The task
reconciliation is split out as reconcile_tasks so it can be tested
without a server.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jmarya changed title from WIP: Local Sync to feat: local markdown sync (vk sync) 2026-09-29 16:35:47 +00:00
jmarya merged commit 2a5e6c4817 into main 2026-09-29 16:40:16 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jmarya/vk!1
No description provided.