Skip to content

feat(sync): add incremental Obsidian DataPlug - #92

Open
vcvyg wants to merge 2 commits into
ob-labs:mainfrom
vcvyg:issue-48-incremental-dataplug
Open

feat(sync): add incremental Obsidian DataPlug#92
vcvyg wants to merge 2 commits into
ob-labs:mainfrom
vcvyg:issue-48-incremental-dataplug

Conversation

@vcvyg

@vcvyg vcvyg commented Jul 17, 2026

Copy link
Copy Markdown

Summary

  • add an ObsidianVaultPlug that persists per-scope file mtime, size, content hash, and stable item identity
  • extend incremental RawEvent materialization with stable upserts, typed links, and soft deletes
  • route Obsidian vaults through the existing contextseek sync command and report add/update/delete/skip counts
  • resolve Obsidian [[wikilinks]] to LinkType.related_to edges
  • cover unchanged, edited, deleted, linked, and cross-scope sync behavior with a fixture vault

Why

The generic sync path deduplicated content, but it could not update a changed source, remove a vanished source, or preserve Obsidian relationships. Stable per-file identities and a committed-after-success cursor make repeated vault syncs incremental and recoverable.

Checks

  • uv run ruff format --check src tests
  • uv run ruff check src tests
  • uv run pytest tests/ -q --ignore=tests/unit_tests/test_powermem_plug.py (445 passed, 8 skipped on Windows)
  • focused Obsidian integration tests pass

The excluded PowerMem test file has existing Windows-specific executable/path assumptions (.EXE, backslashes); all observed full-suite failures were isolated to that file.

Closes #48

Copilot AI review requested due to automatic review settings July 17, 2026 06:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds first-class incremental sync support for Obsidian vaults by introducing an ObsidianVaultPlug with a persisted cursor and extending the generic plug materialization path to support stable upserts, typed links, and soft deletes. This routes Obsidian vaults through the existing contextseek sync command and validates behavior with an integration test + fixture vault.

Changes:

  • Add ObsidianVaultPlug that tracks per-file state (mtime/size/hash) and emits add/update/delete RawEvents with stable item_ids and wikilink-derived LinkType.related_to edges.
  • Extend RawEvent and ContextSeek.plug() to support incremental operations (upsert + soft delete) and attach typed links.
  • Update CLI/sync reporting to include added/updated/deleted/skipped counts and add integration coverage using a fixture vault.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/integration_tests/test_obsidian_sync.py Integration tests covering incremental add/update/delete/skip behavior, stable IDs, and wikilink materialization.
tests/fixtures/obsidian_vault/Projects/Alpha.md Fixture note used to validate incremental updates/deletes.
tests/fixtures/obsidian_vault/Home.md Fixture note containing a wikilink to validate link materialization.
tests/fixtures/obsidian_vault/.obsidian/app.json Minimal marker to ensure vault format detection via .obsidian/.
src/contextseek/plugs/obsidian/plug.py New incremental Obsidian vault DataPlug with cursor persistence, hashing, and wikilink extraction.
src/contextseek/plugs/obsidian/init.py Exports Obsidian plug types for import/use via contextseek.plugs.
src/contextseek/plugs/core/protocols.py Extends RawEvent with operation, item_id, and links for incremental materialization.
src/contextseek/plugs/init.py Adds lazy export of ObsidianVaultPlug in the plugs package.
src/contextseek/daemon/sync_cmd.py Detects Obsidian vaults and routes them through ObsidianVaultPlug, populating full SyncReport counts.
src/contextseek/client/contextseek.py Implements incremental upsert/delete handling in plug() via _upsert_raw_event.
src/contextseek/cli/main.py Updates sync output formatting to report added/updated/deleted/skipped for all formats.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +80 to +104
for rel, path in markdown_files.items():
stat = path.stat()
old = old_files.get(rel)
if (
old is not None
and int(old.get("mtime_ns", -1)) == stat.st_mtime_ns
and int(old.get("size", -1)) == stat.st_size
):
next_files[rel] = dict(old)
skipped += 1
continue

raw = path.read_text(encoding="utf-8", errors="replace")
digest = hashlib.sha256(raw.encode("utf-8")).hexdigest()
record = {
"mtime_ns": stat.st_mtime_ns,
"size": stat.st_size,
"content_hash": digest,
"item_id": item_ids[rel],
}
next_files[rel] = record
if old is not None and old.get("content_hash") == digest:
skipped += 1
continue

Comment thread src/contextseek/plugs/core/protocols.py Outdated
@@ -66,6 +68,15 @@ class RawEvent:
metadata: dict = field(default_factory=dict)
"""Extra key-value pairs the plug can supply (passed to provenance context)."""
Copilot AI review requested due to automatic review settings July 17, 2026 07:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Comment on lines +227 to +236
def _markdown_files(vault: Path) -> dict[str, Path]:
files: dict[str, Path] = {}
for path in vault.rglob("*"):
if not path.is_file() or path.suffix.casefold() != ".md":
continue
rel_path = path.relative_to(vault)
if any(part.startswith(".") for part in rel_path.parts[:-1]):
continue
files[rel_path.as_posix()] = path
return dict(sorted(files.items()))
Comment on lines +76 to +77
events: list[RawEvent] = []
next_files: dict[str, dict[str, Any]] = {}
@vcvyg

vcvyg commented Jul 17, 2026

Copy link
Copy Markdown
Author

#48

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incremental Obsidian / Notion DataPlug

2 participants