Skip to content

Commit 21eae7e

Browse files
chore: release 1.2.0
1 parent f9859a2 commit 21eae7e

4 files changed

Lines changed: 40 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10-
### Fixed
11-
12-
- **Round-trip serialization** — soft-break continuations no longer double-indent; list-shaped block properties (`tags::` + bullets) serialize as Logseq bullet lists instead of Python repr; `:LOGBOOK:` drawers and derived temporal fields (`scheduled::`, `repeater::`, …) are not emitted as bogus `key::` lines; YAML frontmatter pages round-trip with `---` fences and stable block UUIDs; `title` from YAML or `title::` frontmatter overrides the graph page title for deterministic IDs.
13-
1410
## [1.2.0] - 2026-05-29
1511

1612
### Added
@@ -21,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2117

2218
### Fixed
2319

20+
- **Round-trip serialization** — soft-break continuations no longer double-indent; list-shaped block properties (`tags::` + bullets) serialize as Logseq bullet lists instead of Python repr; `:LOGBOOK:` drawers and derived temporal fields (`scheduled::`, `repeater::`, …) are not emitted as bogus `key::` lines; YAML frontmatter pages round-trip with `---` fences and stable block UUIDs; `title` from YAML or `title::` frontmatter overrides the graph page title for deterministic IDs.
2421
- **Property comma-split in wikilinks**`tags::` / `alias::` comma separation ignores commas inside `[[...]]` tokens.
2522
- **Properties after code fences**`key::` lines immediately following a closing fence are parsed into block properties (Logseq contiguity exception).
2623
- **Org warning periods**`DEADLINE` / `SCHEDULED` payloads with `-3d`-style warning periods parse without datetime failures.

README.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
[![Status: Stable](https://img.shields.io/badge/Status-Stable-22c55e.svg?style=flat-square)](#)
1313
![Origin: Matryca.ai](https://img.shields.io/badge/Origin-Matryca.ai-gold?style=for-the-badge)
1414

15-
**v1.2.0** — Graph parity & multimodal assets (see [CHANGELOG](CHANGELOG.md)) — **226 tests**, YAML frontmatter ingest, asset path resolution, case-insensitive page routing, and extended LOGOS shielding; ready for production Enterprise integration.
15+
**v1.2.0** — Graph parity, multimodal assets & format-preserving round-trip (see [CHANGELOG](CHANGELOG.md)) — **233 tests**, YAML frontmatter ingest/serialize, asset path resolution, case-insensitive page routing, and extended LOGOS shielding; ready for production Enterprise integration.
1616

1717
> *Turning a forest of local plain-text files into a unified semantic powerhouse.*
1818
@@ -113,12 +113,22 @@ Logseq Matryca Parser is a deterministic **Stack-Machine engine** that acts as t
113113
| Area | Capability |
114114
| :--- | :--- |
115115
| **Asset extraction** | `LogseqNode.assets` collects markdown images, `{{pdf}}` macros, and local `[label](path)` attachments; `LogseqPage.resolve_asset_path` maps to absolute paths (`%20` decode, graph-root relative). |
116-
| **YAML frontmatter** | `---` blocks at file start populate `LogseqPage.properties` like native `key::` page properties (native **serialize** still emits `key::` only). |
116+
| **YAML frontmatter** | `---` blocks at file start populate `LogseqPage.properties` like native `key::` lines; **`title:`** in YAML sets `page.title` at parse; **`serialize_logseq_page`** preserves `---` fences on round-trip when the source file used YAML. |
117117
| **`page-tags::`** | Block and page `page-tags::` inject implicit graph tokens like `tags::`; list-shaped values feed `refs`. |
118118
| **Case-insensitive routing** | `LogseqGraph.get_page` and `resolve_relative_page_link` resolve titles via a lowercase index (Datomic parity). |
119119
| **Extended shielding** | HTML comments, `{{query}}` / `{{advancedquery}}`, and escaped `\#` / `\[\[` do not emit false graph tokens (embed macros still harvest nested wikilinks). |
120120
| **Property & temporal fixes** | Comma-split ignores commas inside `[[wikilinks]]`; properties after code fences; quoted value stripping; `SCHEDULED`/`DEADLINE` ranges, repeaters, and Org warning periods; legacy `___` / `%2F` / Dendron filenames; UTF-8 BOM via `utf-8-sig`. |
121121

122+
### Round-trip serialization (v1.2.0)
123+
124+
| Area | Capability |
125+
| :--- | :--- |
126+
| **Soft-break bodies** | Multiline block continuations serialize without double-indenting alignment spaces. |
127+
| **List-shaped block props** | `tags::` / `page-tags::` with indented `-` bullets round-trip as Logseq lists (not Python repr). |
128+
| **`:LOGBOOK:` drawers** | Org drawers re-emit as `:LOGBOOK:` / `:END:` blocks, not bogus `logbook::` property lines. |
129+
| **Derived temporal keys** | Parsed `scheduled::`, `repeater::`, and related derived fields are omitted from serialized `key::` output. |
130+
| **Stable block UUIDs** | Parse → `serialize_logseq_page` → parse preserves block `id::` / UUIDs on the same outline. |
131+
122132
```python
123133
from logseq_matryca_parser.graph import LogseqGraph
124134
from logseq_matryca_parser.logos_parser import LogosParser
@@ -202,15 +212,15 @@ For graph hygiene, **`LogseqGraph.get_broken_references()`** flags nodes whose `
202212

203213
| Feature | Description |
204214
| :--- | :--- |
205-
| **LOGOS Engine** | Deterministic AST parsing. YAML + native frontmatter ingest, **assets**, property contiguity (incl. post-fence), comma-safe wikilink splits, temporal ranges/repeaters, legacy filename decode, BOM-safe reads, and **shielded** code/math/query/HTML/escape regions. |
215+
| **LOGOS Engine** | Deterministic AST parsing. YAML + native frontmatter ingest, **format-preserving** `serialize_logseq_page` (YAML vs `key::` by source), list-shaped block property layout, **assets**, property contiguity (incl. post-fence), comma-safe wikilink splits, temporal ranges/repeaters, legacy filename decode, BOM-safe reads, and **shielded** code/math/query/HTML/escape regions. |
206216
| **Multimodal assets** | **`LogseqNode.assets`** + **`LogseqPage.resolve_asset_path`** for PDFs and images relative to the graph root (Vision / document RAG). |
207217
| **LogseqGraph** | In-memory vault: `pages` index (with **title/alias enrichment** and **case-insensitive lookup**), backlinks, effective properties, namespace resolution, fluent `GraphQuery`, optional **watchdog** invalidation. |
208218
| **Advanced Task Extraction** | Task **state** (TODO / DOING / DELEGATED / IN-PROGRESS / …), **priority** markers `[#A]``[#C]` promoted to `task_priority`, and **SCHEDULED** / **DEADLINE** Logseq timestamps normalized to **UTC Unix epoch seconds** on `scheduled_at` / `deadline_at` for temporal graph and retrieval pipelines. |
209219
| **SYNAPSE Adapter** | Native exports for **LangChain** and **LlamaIndex** with automated lineage metadata; **context-enriched** chunks with breadcrumbs, embed expansion, and inherited properties. |
210220
| **FORGE** | JSON, clean Markdown, and **Obsidian** vault serialization (`ObsidianForgeVisitor`, `ForgeExporter.to_obsidian_markdown`). |
211221
| **LENS Visualizer** | 60FPS interactive graph rendering (10k+ nodes) with Glassmorphism HUD. |
212222
| **Agent-Native Printing Press** | [`agent_press.py`](src/logseq_matryca_parser/agent_press.py): **`SessionAliasRegistry`** maps session aliases ↔ block UUIDs; **`to_xray_markdown`** emits token-minimal outline text for autonomous agents (`matryca-parse agent-read`). |
213-
| **Native Markdown Serialization** | [`logseq_markdown.py`](src/logseq_matryca_parser/logseq_markdown.py) + [`logseq_paths.py`](src/logseq_matryca_parser/logseq_paths.py): rebuild and write Logseq-compliant markdown pages from an AST—page properties as raw `key:: value` lines, block properties indented at **parent whitespace + exactly 2 spaces**, and namespace titles mapped via **`___`** pathing rules. |
223+
| **Native Markdown Serialization** | [`logseq_markdown.py`](src/logseq_matryca_parser/logseq_markdown.py) + [`logseq_paths.py`](src/logseq_matryca_parser/logseq_paths.py): rebuild and write Logseq-compliant markdown from an AST—page header preserves **YAML `---` or native `key::`** by source format, block properties at **parent whitespace + 2 spaces** (including bullet-list `tags::`), `:LOGBOOK:` drawers, and namespace titles via **`___`** pathing rules. |
214224
| **Headless Write Engine** | [`agent_writer.py`](src/logseq_matryca_parser/agent_writer.py): **`append_child_to_node`** splices child bullets into on-disk Markdown from AST topology; **`serialize_logseq_page`** / **`write_logseq_page`** emit full pages; **`matryca-parse agent-write`** resolves aliases via **`.matryca_xray_state.json`**. |
215225
| **AST Linters** | **`LogseqGraph.get_broken_references()`** returns originating nodes when `block_refs` target UUIDs absent from the global registry. |
216226
| **Sovereign AI** | 100% Local. Zero telemetry. Private by design. |

docs/ARCHITECTURE.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -417,25 +417,30 @@ Wave 12 established **surgical writes** (single-line splices); v1.0 completes th
417417
1. **Native Logseq frontmatter** — raw `key:: value` lines (no leading `- `), blank line, then bullets.
418418
2. **YAML frontmatter**`---` delimited block with `key: value` lines mapped into **`LogseqPage.properties`** (same lowercase key normalization).
419419

420-
**Write path:** [`serialize_logseq_page`](../src/logseq_matryca_parser/logseq_markdown.py) always emits **native `key::` lines** only (see below). YAML wrappers appear only in **FORGE Obsidian** export (§3.5), not in sovereign round-trip writes.
420+
**Write path:** [`serialize_logseq_page`](../src/logseq_matryca_parser/logseq_markdown.py) is **format-preserving** for page headers. If `page.raw_content` began with `---`, page properties re-emit as a YAML fence via [`_format_yaml_frontmatter`](../src/logseq_matryca_parser/logseq_markdown.py); otherwise [`format_logseq_page_properties`](../src/logseq_matryca_parser/logseq_markdown.py) writes native **`key:: value`** lines. At parse time, a non-empty **`title`** property (YAML or `title::`) sets **`LogseqPage.title`**; [`LogseqGraph`](../src/logseq_matryca_parser/graph.py) re-applies **`title::`** overrides when loading a vault directory.
421+
422+
**Obsidian FORGE** (§3.5) may emit YAML frontmatter on export even for pages that were natively `key::` on disk — that projection is separate from sovereign round-trip writes.
421423

422424
#### Page properties (file header)
423425

424-
Page-level metadata is emitted as **raw `key:: value` lines** at the top of the file — no YAML frontmatter wrapper. [`format_logseq_page_properties`](../src/logseq_matryca_parser/logseq_markdown.py) renders each entry on its own line (list-valued keys such as `tags::` are flattened to comma-separated tokens), followed by a **blank separator line** before the first outline bullet. This mirrors how Logseq stores page properties in vanilla `.md` exports and keeps Git diffs line-granular.
426+
- **Native:** [`format_logseq_page_properties`](../src/logseq_matryca_parser/logseq_markdown.py) renders `key:: value` lines (list-valued keys such as `tags::` flatten to comma-separated tokens), then a blank line before the first bullet.
427+
- **YAML:** [`_format_yaml_frontmatter`](../src/logseq_matryca_parser/logseq_markdown.py) renders `key: value` lines inside `---` fences when the ingested file used YAML.
425428

426429
#### Block properties (strict indentation contract)
427430

428-
Block-scoped properties are serialized **immediately after the bullet text line**, never interleaved with child bullets. The indent rule is strict and deterministic:
431+
Block-scoped properties are serialized **immediately after the bullet text line** (and after `:LOGBOOK:` drawers when present), never interleaved with child bullets. The indent rule is strict and deterministic:
429432

430433
```text
431434
{parent_leading_whitespace} {key}:: {value}
432435
```
433436

434-
That is, take the **exact leading whitespace** of the parent bullet line and append **exactly two additional spaces** (`_block_property_indent`). Continuation lines of multiline block bodies use the same `parent + 2` column. [`format_logseq_block_property_lines`](../src/logseq_matryca_parser/logseq_markdown.py) respects **`properties_order`** when present so round-trips preserve author ordering.
437+
That is, take the **exact leading whitespace** of the parent bullet line and append **exactly two additional spaces** (`_block_property_indent`). Continuation lines of multiline block bodies use the same `parent + 2` column; [`_serialize_logseq_node_lines`](../src/logseq_matryca_parser/logseq_markdown.py) strips redundant alignment prefix on soft-break lines so continuations do not double-indent on round-trip.
438+
439+
[`format_logseq_block_property_lines`](../src/logseq_matryca_parser/logseq_markdown.py) respects **`properties_order`** when present. **List-shaped** keys (`tags`, `alias`, `aliases`, `page-tags`) with list values emit `key::` plus indented `-` item lines via [`_format_block_property_list_lines`](../src/logseq_matryca_parser/logseq_markdown.py). **`:LOGBOOK:`** metadata re-emits as Org drawers via [`_format_logbook_drawer_lines`](../src/logseq_matryca_parser/logseq_markdown.py), not as `logbook::` property lines. Keys in **`_DERIVED_BLOCK_PROPERTY_KEYS`** (`scheduled`, `repeater`, `logbook`, etc.) are parsed into AST fields or drawers and are **not** written back as bogus `key::` lines.
435440

436441
#### Full-page emission
437442

438-
[`serialize_logseq_page`](../src/logseq_matryca_parser/logseq_markdown.py) walks `page.root_nodes` depth-first, emitting `- {first_line}` bullets scaled by `indent_level × tab_size`, then property lines, then continuations, then children. [`write_logseq_page`](../src/logseq_matryca_parser/logseq_markdown.py) persists the result with UTF-8 encoding. Together with §3.4’s **`append_child_to_node`**, the stack now supports **point mutations** and **whole-page regeneration** from the same AST — bidirectional I/O without Logseq’s HTTP API.
443+
[`serialize_logseq_page`](../src/logseq_matryca_parser/logseq_markdown.py) walks `page.root_nodes` depth-first, emitting `- {first_line}` bullets scaled by `indent_level × tab_size`, then continuations, then drawers/properties, then children. [`write_logseq_page`](../src/logseq_matryca_parser/logseq_markdown.py) persists the result with UTF-8 encoding. Together with §3.4’s **`append_child_to_node`**, the stack now supports **point mutations** and **whole-page regeneration** from the same AST — bidirectional I/O without Logseq’s HTTP API.
439444

440445
### 3.9 Namespace & Path Translation
441446

docs/logseq_ast_primer.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,16 +129,29 @@ tags: parser, logseq
129129
- First root block
130130
```
131131

132-
Both map into **`LogseqPage.properties`** with **lowercase keys**. Matryca’s **native serializer** (`serialize_logseq_page`) still **writes** `key::` lines only — not YAML wrappers. YAML appears in **Obsidian FORGE** export, not sovereign round-trip writes.
132+
Both map into **`LogseqPage.properties`** with **lowercase keys**. **`serialize_logseq_page`** is **format-preserving**: pages that began with `---` re-emit YAML frontmatter; otherwise the serializer writes native `key::` lines. **Obsidian FORGE** export may add YAML even on natively `key::` pages — that is a separate projection, not sovereign round-trip output.
133133

134134
**`page-tags::`** at page or block level is treated like **`tags::`** for implicit graph token injection.
135135

136136
### Parsing rules
137137

138-
1. Keys and values are stored in **`LogseqPage.properties`** with **lowercase keys**; the parser does **not** automatically change **`LogseqPage.title`** (that remains filename-derived until graph load).
138+
1. Keys and values are stored in **`LogseqPage.properties`** with **lowercase keys**. A non-empty **`title`** property (YAML `title:` or `title::`) sets **`LogseqPage.title`** at parse time (overriding the filename-derived default). **`LogseqGraph.load_directory`** re-applies **`title::`** enrichment when indexing the vault.
139139
2. **`alias::`** and **`aliases::`** accept comma-separated strings, **bullet-list** values (`alias::` followed by indented `-` lines), or Python `list` values after parse. Serialization strips `[[wikilink]]` and `#tag` adornments from each token.
140140
3. Harvested page wikilinks/tags merge into **`LogseqPage.refs`** for graph indexing.
141141

142+
### Serialization round-trip
143+
144+
[`serialize_logseq_page`](../src/logseq_matryca_parser/logseq_markdown.py) rebuilds sovereign Spatial Markdown from a parsed **`LogseqPage`**:
145+
146+
| Behavior | Detail |
147+
| :--- | :--- |
148+
| **Page header** | YAML `---` preserved when `raw_content` started with `---`; else `key::` lines via `format_logseq_page_properties`. |
149+
| **List block properties** | `tags::` / `page-tags::` (and ref keys) with list values emit `key::` plus indented `-` bullets — not Python repr. |
150+
| **`:LOGBOOK:`** | Drawer blocks re-emit as `:LOGBOOK:` / `:END:`, not `logbook::` lines. |
151+
| **Derived temporal keys** | `scheduled::`, `repeater::`, and related parsed fields are omitted from serialized `key::` output. |
152+
| **Soft-break bodies** | Continuation lines keep single alignment at `parent + 2` spaces without double-indent. |
153+
| **Block UUIDs** | Parse → serialize → parse preserves block UUIDs on the same outline (see `tests/test_pre_release_roundtrip.py`). |
154+
142155
### `LogseqGraph` enrichment
143156

144157
When you call **`LogseqGraph.load_directory`**, a post-parse pass:

0 commit comments

Comments
 (0)