logseq-matryca-parser 0.3.0__tar.gz → 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/PKG-INFO +15 -3
  2. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/README.md +14 -2
  3. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/docs/ARCHITECTURE.md +31 -9
  4. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/pyproject.toml +1 -1
  5. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/src/logseq_matryca_parser/__init__.py +1 -1
  6. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/src/logseq_matryca_parser/agent_press.py +2 -0
  7. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/src/logseq_matryca_parser/kinetic.py +73 -1
  8. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/tests/test_agent_press.py +35 -0
  9. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/uv.lock +1 -1
  10. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/.cursorignore +0 -0
  11. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/.cursorrules +0 -0
  12. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/.github/FUNDING.yml +0 -0
  13. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  14. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  15. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  16. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/.github/dependabot.yml +0 -0
  17. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/.github/workflows/ci.yml +0 -0
  18. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/.github/workflows/pypi_publish.yml +0 -0
  19. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/.gitignore +0 -0
  20. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/.pre-commit-config.yaml +0 -0
  21. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/.repomixignore +0 -0
  22. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/CONTRIBUTING.md +0 -0
  23. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/LICENSE +0 -0
  24. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/Makefile +0 -0
  25. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/NOTICE +0 -0
  26. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/ROADMAP_AGENT_NATIVE_XRAY.md +0 -0
  27. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/ROADMAP_HEADLESS_WRITER.md +0 -0
  28. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/ROADMAP_OBSIDIAN_ADAPTER.md +0 -0
  29. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/SECURITY.md +0 -0
  30. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/claude-skill-logseq-read/SKILL.md +0 -0
  31. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/claude-skill-logseq-read/scripts/parse_logseq.py +0 -0
  32. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/docs/design-docs/ARCHITECTURE_BLUEPRINT.md +0 -0
  33. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/docs/design-docs/CODE_SCAFFOLD.md +0 -0
  34. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/docs/design-docs/LOGSEQ_ASSET_RESOLUTION_SPEC.md +0 -0
  35. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/docs/design-docs/LOGSEQ_DATASCRIPT_MAPPING.md +0 -0
  36. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/docs/design-docs/LOGSEQ_TEMPORAL_ONTOLOGY.md +0 -0
  37. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/docs/design-docs/OFFICIAL_MLDOC_SPECS.md +0 -0
  38. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/docs/design-docs/REFERENCE_SPEC.md +0 -0
  39. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/docs/error_log.md +0 -0
  40. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/docs/logseq_ast_primer.md +0 -0
  41. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/docs/roadmaps/ROADMAP_CLI_HYDRATION_AND_ENRICHMENT.md +0 -0
  42. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/docs/roadmaps/ROADMAP_CONTEXT_SYNTHESIS_AND_SCOPING.md +0 -0
  43. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/docs/roadmaps/ROADMAP_EMBED_EXPANSION_AND_FLUENT_QUERIES.md +0 -0
  44. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/docs/roadmaps/ROADMAP_GRAPH_RAG_SEMANTICS.md +0 -0
  45. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/docs/roadmaps/ROADMAP_INCREMENTAL_WATCHER.md +0 -0
  46. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/docs/roadmaps/ROADMAP_INLINE_SHIELD_AND_NAMESPACES.md +0 -0
  47. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/docs/roadmaps/ROADMAP_ROBUSTNESS_AND_SOFT_BREAKS.md +0 -0
  48. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/docs/roadmaps/ROADMAP_TOML_FIX_AND_PYPI_DISTRIBUTION.md +0 -0
  49. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/docs/roadmaps/ROADMAP_UUID_AND_GRAPH_SUPERPOWERS.md +0 -0
  50. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/examples/demo_logseq_journal.md +0 -0
  51. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/examples/run_demo.py +0 -0
  52. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/legacy/local_digestor.py +0 -0
  53. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/lib/bindings/utils.js +0 -0
  54. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/lib/tom-select/tom-select.complete.min.js +0 -0
  55. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/lib/tom-select/tom-select.css +0 -0
  56. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/lib/vis-9.1.2/vis-network.css +0 -0
  57. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/lib/vis-9.1.2/vis-network.min.js +0 -0
  58. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/src/logseq_matryca_parser/.gitignore +0 -0
  59. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/src/logseq_matryca_parser/NOTICE +0 -0
  60. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/src/logseq_matryca_parser/__main__.py +0 -0
  61. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/src/logseq_matryca_parser/agent_writer.py +0 -0
  62. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/src/logseq_matryca_parser/exceptions.py +0 -0
  63. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/src/logseq_matryca_parser/forge.py +0 -0
  64. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/src/logseq_matryca_parser/graph.py +0 -0
  65. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/src/logseq_matryca_parser/lens.py +0 -0
  66. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/src/logseq_matryca_parser/logos_core.py +0 -0
  67. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/src/logseq_matryca_parser/logos_parser.py +0 -0
  68. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/src/logseq_matryca_parser/pyproject.toml +0 -0
  69. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/src/logseq_matryca_parser/synapse.py +0 -0
  70. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/tests/test_agent_writer.py +0 -0
  71. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/tests/test_forge.py +0 -0
  72. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/tests/test_graph.py +0 -0
  73. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/tests/test_kinetic.py +0 -0
  74. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/tests/test_lens.py +0 -0
  75. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/tests/test_logos_parser.py +0 -0
  76. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/tests/test_package_version.py +0 -0
  77. {logseq_matryca_parser-0.3.0 → logseq_matryca_parser-0.3.2}/tests/test_synapse.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: logseq-matryca-parser
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: The Logos Protocol: Deterministic Logseq AST parsing for Matryca.ai.
5
5
  Project-URL: Homepage, https://github.com/MarcoPorcellato/logseq-matryca-parser
6
6
  Project-URL: Bug Tracker, https://github.com/MarcoPorcellato/logseq-matryca-parser/issues
@@ -136,7 +136,7 @@ Logseq Matryca Parser is a deterministic **Stack-Machine engine** that acts as t
136
136
 
137
137
  ---
138
138
 
139
- ## ⚡ Recent superpowers (Waves 4–11)
139
+ ## ⚡ Recent superpowers (Waves 4–12)
140
140
 
141
141
  ### Obsidian-native export
142
142
  Compile an entire Logseq graph into an **Obsidian vault layout**: YAML frontmatter from page properties, list body preserved, Logseq `((uuid))` links rewritten to **`[[Page#^anchor]]`**, and trailing **`^block-id`** on referenced blocks. Namespace titles become nested folders (e.g. `Projects/AI/Demo.md`).
@@ -176,6 +176,16 @@ matryca-parse agent-read /path/to/graph --query "quantum"
176
176
 
177
177
  The agent reads cheap topology now; the registry resolves aliases back to sovereign UUIDs when you wire targeted writes.
178
178
 
179
+ ### Headless Write Engine & AST Linter (Wave 12)
180
+ The parser is **no longer read-only**. Wave 12 adds a **headless Markdown splicer** ([`agent_writer.py`](src/logseq_matryca_parser/agent_writer.py)): `append_child_to_node` uses AST line numbers and indentation (`(indent_level + 1) × tab_size`) to insert a new bullet **atomically** into the sovereign `.md` file—via `tempfile` + `os.replace`—without Logseq’s fragile HTTP API. Pair **`agent-read`** with **`agent-write`**: X-Ray persists its alias map to **`.matryca_xray_state.json`** at the graph root so stateless CLI invocations can **read, then write** in sequence.
181
+
182
+ ```bash
183
+ matryca-parse agent-read /path/to/graph --tag idea
184
+ matryca-parse agent-write /path/to/graph --alias 0 --content "Follow-up from the agent"
185
+ ```
186
+
187
+ For graph hygiene, **`LogseqGraph.get_broken_references()`** flags nodes whose `((uuid))` block refs point at missing registry targets—structural linting, not regex guessing.
188
+
179
189
  ---
180
190
 
181
191
  ## 🏗️ Core Capabilities
@@ -188,6 +198,8 @@ The agent reads cheap topology now; the registry resolves aliases back to sovere
188
198
  | **FORGE** | JSON, clean Markdown, and **Obsidian** vault serialization (`ObsidianForgeVisitor`, `ForgeExporter.to_obsidian_markdown`). |
189
199
  | **LENS Visualizer** | 60FPS interactive graph rendering (10k+ nodes) with Glassmorphism HUD. |
190
200
  | **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`). |
201
+ | **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; **`matryca-parse agent-write`** resolves aliases via **`.matryca_xray_state.json`**. |
202
+ | **AST Linters** | **`LogseqGraph.get_broken_references()`** returns originating nodes when `block_refs` target UUIDs absent from the global registry. |
191
203
  | **Sovereign AI** | 100% Local. Zero telemetry. Private by design. |
192
204
 
193
205
  ### Data model — `LogseqNode` task fields
@@ -201,7 +213,7 @@ Each AST block is a `LogseqNode`. Alongside `task_status`, the parser surfaces p
201
213
  "task_priority": "A",
202
214
  "scheduled_at": 1641600000,
203
215
  "deadline_at": 1641772800,
204
- "clean_text": "Cut v0.3.0 release"
216
+ "clean_text": "Cut v0.3.2 release"
205
217
  }
206
218
  ```
207
219
 
@@ -100,7 +100,7 @@ Logseq Matryca Parser is a deterministic **Stack-Machine engine** that acts as t
100
100
 
101
101
  ---
102
102
 
103
- ## ⚡ Recent superpowers (Waves 4–11)
103
+ ## ⚡ Recent superpowers (Waves 4–12)
104
104
 
105
105
  ### Obsidian-native export
106
106
  Compile an entire Logseq graph into an **Obsidian vault layout**: YAML frontmatter from page properties, list body preserved, Logseq `((uuid))` links rewritten to **`[[Page#^anchor]]`**, and trailing **`^block-id`** on referenced blocks. Namespace titles become nested folders (e.g. `Projects/AI/Demo.md`).
@@ -140,6 +140,16 @@ matryca-parse agent-read /path/to/graph --query "quantum"
140
140
 
141
141
  The agent reads cheap topology now; the registry resolves aliases back to sovereign UUIDs when you wire targeted writes.
142
142
 
143
+ ### Headless Write Engine & AST Linter (Wave 12)
144
+ The parser is **no longer read-only**. Wave 12 adds a **headless Markdown splicer** ([`agent_writer.py`](src/logseq_matryca_parser/agent_writer.py)): `append_child_to_node` uses AST line numbers and indentation (`(indent_level + 1) × tab_size`) to insert a new bullet **atomically** into the sovereign `.md` file—via `tempfile` + `os.replace`—without Logseq’s fragile HTTP API. Pair **`agent-read`** with **`agent-write`**: X-Ray persists its alias map to **`.matryca_xray_state.json`** at the graph root so stateless CLI invocations can **read, then write** in sequence.
145
+
146
+ ```bash
147
+ matryca-parse agent-read /path/to/graph --tag idea
148
+ matryca-parse agent-write /path/to/graph --alias 0 --content "Follow-up from the agent"
149
+ ```
150
+
151
+ For graph hygiene, **`LogseqGraph.get_broken_references()`** flags nodes whose `((uuid))` block refs point at missing registry targets—structural linting, not regex guessing.
152
+
143
153
  ---
144
154
 
145
155
  ## 🏗️ Core Capabilities
@@ -152,6 +162,8 @@ The agent reads cheap topology now; the registry resolves aliases back to sovere
152
162
  | **FORGE** | JSON, clean Markdown, and **Obsidian** vault serialization (`ObsidianForgeVisitor`, `ForgeExporter.to_obsidian_markdown`). |
153
163
  | **LENS Visualizer** | 60FPS interactive graph rendering (10k+ nodes) with Glassmorphism HUD. |
154
164
  | **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`). |
165
+ | **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; **`matryca-parse agent-write`** resolves aliases via **`.matryca_xray_state.json`**. |
166
+ | **AST Linters** | **`LogseqGraph.get_broken_references()`** returns originating nodes when `block_refs` target UUIDs absent from the global registry. |
155
167
  | **Sovereign AI** | 100% Local. Zero telemetry. Private by design. |
156
168
 
157
169
  ### Data model — `LogseqNode` task fields
@@ -165,7 +177,7 @@ Each AST block is a `LogseqNode`. Alongside `task_status`, the parser surfaces p
165
177
  "task_priority": "A",
166
178
  "scheduled_at": 1641600000,
167
179
  "deadline_at": 1641772800,
168
- "clean_text": "Cut v0.3.0 release"
180
+ "clean_text": "Cut v0.3.2 release"
169
181
  }
170
182
  ```
171
183
 
@@ -82,7 +82,7 @@ title Logseq Matryca Parser — C4 Level 2 (Containers)
82
82
  Person(knowledgeWorker, "Knowledge Worker", "Local operator of a sovereign Logseq graph.")
83
83
 
84
84
  System_Boundary(matrycaEcosystem, "Matryca.ai Ecosystem") {
85
- Container(kinetic, "KINETIC", "Typer / Rich CLI", "CLI — export (json, markdown, langchain, langchain-enriched, obsidian), visualize, demo, graph scans, `agent-read` (raw X-Ray stdout), append-only agent writes (`append`).")
85
+ Container(kinetic, "KINETIC", "Typer / Rich CLI", "CLI — export (json, markdown, langchain, langchain-enriched, obsidian), visualize, demo, graph scans, `agent-read` / `agent-write` (X-Ray + headless splice), weekly append (`append`).")
86
86
  Container(logos, "LOGOS", "Python / Pydantic", "Stack-Machine AST engine — LogseqPage and LogseqNode models.")
87
87
  Container(synapse, "SYNAPSE", "LangChain / LlamaIndex", "Framework-native exporters with parent-child metadata.")
88
88
  Container(lens, "LENS", "NetworkX / PyVis", "Reference-topology visualization to interactive HTML.")
@@ -258,9 +258,15 @@ The KINETIC **`export --format langchain-enriched`** path serializes these docum
258
258
 
259
259
  Visualization export uses **`pyvis`** with **`force_atlas_2based`** physics, fullscreen canvas, HUD filters, glassmorphism control chrome, and stabilized layout configuration suitable for **large graphs at interactive frame rates** in the browser (product positioning targets fluid exploration of graphs on the order of **10⁴ nodes**).
260
260
 
261
- ### 3.4 AGENT WRITER — Append-Only Sandboxing
261
+ ### 3.4 AGENT WRITER — Append-Only Sandboxing & Headless Splicer
262
262
 
263
- In the **LLM OS** metaphor, **LOGOS** is the **read path** into the hierarchical “disk”: it materializes Spatial Markdown into a **deterministic AST** that downstream adapters trust. **`agent_writer`** ([`logseq_matryca_parser.agent_writer`](../src/logseq_matryca_parser/agent_writer.py)) is the complementary **bounded write syscall**: a **deterministic**, **configuration-aware** channel that **dynamically reads `config.edn`** (for example **`:journal/page-title-format`**) so filenames and titles align with the vault’s own conventions. Writes use **`open(..., mode="a")`** **append-only** I/O — agents **append** new block material **after** existing bytes; they do **not** rewrite, merge, or re-indent prior content. That discipline keeps **existing topology intact** and avoids corrupting the graph in ways that would break a subsequent **LOGOS** parse or violate the **deterministic AST** contract. Surfaced through the **`append`** command in **KINETIC**, this yields an **enterprise-grade**, inspectable path for agent contributions while **read/export** flows remain the authoritative, topology-preserving contract with the vault.
263
+ In the **LLM OS** metaphor, **LOGOS** is the **read path** into the hierarchical “disk”: it materializes Spatial Markdown into a **deterministic AST** that downstream adapters trust. **`agent_writer`** ([`logseq_matryca_parser.agent_writer`](../src/logseq_matryca_parser/agent_writer.py)) is the complementary **bounded write syscall** with two surfaces:
264
+
265
+ 1. **Weekly append sandbox (`logseq_agent_write`).** A **deterministic**, **configuration-aware** channel that **dynamically reads `config.edn`** (for example **`:journal/page-title-format`**) so filenames and titles align with the vault’s own conventions. Writes use **`open(..., mode="a")`** **append-only** I/O — agents **append** new block material **after** existing bytes; they do **not** rewrite, merge, or re-indent prior content. Surfaced through the **`append`** command in **KINETIC**.
266
+
267
+ 2. **Headless Markdown splicer (`append_child_to_node`).** For **in-place graph mutation** under an existing parent block, the engine resolves `target_uuid` via `LogseqGraph.get_node_by_uuid`, walks to the **deepest last descendant** to obtain a **1-based `line_end` insertion index**, and computes child indentation as **`(target_node.indent_level + 1) × graph.tab_size`** spaces before the `- {content}` bullet prefix. The raw file is split into lines, the new row is inserted at that index, and the result is flushed through a **same-directory `tempfile.mkstemp` + `os.replace`** — an atomic swap that avoids torn reads during concurrent tooling. **KINETIC** exposes this as **`matryca-parse agent-write`**, resolving parent blocks by **`--alias`** (from the X-Ray state file) or **`--target-uuid`**.
268
+
269
+ Both paths keep **existing topology intact** relative to their contract: append-only journaling never truncates prior bytes; the splicer only inserts one new child line at an AST-derived coordinate so a subsequent **LOGOS** parse remains deterministic.
264
270
 
265
271
  ### 3.5 FORGE — multi-target serialization (JSON, Markdown, Obsidian)
266
272
 
@@ -293,13 +299,21 @@ This keeps **global indexes consistent** without rebuilding the entire graph.
293
299
 
294
300
  **`graph.query()`** seeds a [`GraphQuery`](../src/logseq_matryca_parser/graph.py) with **all registered nodes**, then applies chainable filters: **`has_tag`**, **`with_priority`**, **`under_parent(parent_uuid)`** (ancestor chain on `path`), **`is_task_state`**, and **`execute()`** returning a materialized list. This is the **programmatic complement** to SQL-less graph inspection — ideal for **batch exporters**, **lint rules**, and **agent planners** that need a typed slice of the outline without ad-hoc traversal code.
295
301
 
302
+ #### AST reference linter (`get_broken_references`)
303
+
304
+ **`LogseqGraph.get_broken_references()`** scans every node in **`_node_registry`**. When **`block_refs`** contains a `((uuid))` target absent from the registry, the **originating node** is collected. Downstream apps (MCP servers, CI, pre-embed hooks) get **structural link validation** aligned with LOGOS identity rules — not brittle regex over raw Markdown.
305
+
296
306
  ### 3.7 AGENT PRESS — Agent-native printing press & X-Ray mode
297
307
 
298
308
  Human-facing RAG (SYNAPSE enriched chunks, breadcrumbs, inherited properties) optimizes for **embedding geometry** and **retrieval filters**. Autonomous agents running tight **read → plan → write** loops need a different projection: **the fewest tokens per topological fact**. **`agent_press.py`** ([`logseq_matryca_parser.agent_press`](../src/logseq_matryca_parser/agent_press.py)) implements the **Printing Press** paradigm: compress the in-memory AST for machine consumption **without** sacrificing parent–child shape.
299
309
 
310
+ #### Stateful session registry (`.matryca_xray_state.json`)
311
+
312
+ X-Ray is designed for **stateless LLM toolchains**: each `agent-read` invocation is a fresh process, yet agents must still **write back** to blocks they only saw as `[n]` tokens. After **`generate_aliases`**, **KINETIC** persists the alias map to **`<graph_root>/.matryca_xray_state.json`** via **`SessionAliasRegistry.save_to_disk`**. A later **`matryca-parse agent-write --alias N`** loads that JSON with **`load_from_disk`**, resolves `N → uuid`, and hands off to **`append_child_to_node`**. The read/write pair therefore composes as **two independent CLI exits** without stuffing UUIDs into the model context — the filesystem holds session continuity.
313
+
300
314
  #### Session alias mechanics (`SessionAliasRegistry`)
301
315
 
302
- `SessionAliasRegistry` is a **session-scoped, in-RAM translation table** between lightweight aliases and sovereign block identities:
316
+ `SessionAliasRegistry` is a **session-scoped translation table** (in-RAM during a single command, rehydratable from disk across commands) between lightweight aliases and sovereign block identities:
303
317
 
304
318
  | Operation | Role |
305
319
  | --------- | ---- |
@@ -323,18 +337,26 @@ Heavy Logseq identifiers (`id:: 64a8b0c1-d33b-4448-a261-e4dc2bbe12d3`, synthetic
323
337
 
324
338
  No YAML, no JSON wrappers, no collapsed-state metadata, no blank separator lines — **pure topology + semantics** for the LLM “CPU” to load into its context window.
325
339
 
326
- #### KINETIC `agent-read` — Rich bypass for machine stdout
340
+ #### KINETIC `agent-read` / `agent-write` — Rich bypass for machine stdout
341
+
342
+ The compound CLI commands **`agent_read`** and **`agent_write`** in [`kinetic.py`](../src/logseq_matryca_parser/kinetic.py) are the operator surfaces for the **Headless CRUD** loop:
327
343
 
328
- The compound CLI command **`agent_read`** in [`kinetic.py`](../src/logseq_matryca_parser/kinetic.py) (`matryca-parse agent-read`) is the operator surface for X-Ray ingestion:
344
+ **`matryca-parse agent-read`**
329
345
 
330
346
  1. **`LogseqGraph.load_directory`** — materialize the RAM image of the vault.
331
347
  2. **Filter** — `graph.query().has_tag(tag).execute()` when `--tag` is set; otherwise `search_content(query)` when `--query` is set; otherwise all registered nodes.
332
- 3. **`SessionAliasRegistry.generate_aliases`** → **`to_xray_markdown`**.
348
+ 3. **`SessionAliasRegistry.generate_aliases`** → **`save_to_disk(.matryca_xray_state.json)`** → **`to_xray_markdown`**.
333
349
  4. **Emit via `sys.stdout.write`** — deliberately **not** Typer’s Rich `Console`.
334
350
 
335
- Rich styling injects **ANSI escape sequences** that waste tokens and can cause models to **hallucinate markup** as content. `agent-read` is **stdout-pure** so shell pipelines, MCP tools, and headless agents receive **unescaped plain text** only. Human-oriented commands (`scan`, `export`, `visualize`) keep Rich; the **machine-native read path** opts out.
351
+ **`matryca-parse agent-write`**
352
+
353
+ 1. Reload the graph (fresh AST coordinates after any external edits).
354
+ 2. Resolve **`--alias`** through the persisted registry (or accept **`--target-uuid`** directly).
355
+ 3. **`append_child_to_node`** — atomic Markdown splice at the parent’s AST line index.
356
+
357
+ Rich styling injects **ANSI escape sequences** that waste tokens and can cause models to **hallucinate markup** as content. `agent-read` is **stdout-pure** so shell pipelines, MCP tools, and headless agents receive **unescaped plain text** only. Human-oriented commands (`scan`, `export`, `visualize`) keep Rich; the **machine-native read/write paths** opt out where token fidelity matters.
336
358
 
337
- This complements §3.4 **AGENT WRITER** (append-only human/agent notes) and §3.2 **SYNAPSE** (human/RAG chunking): one stack, three projections — **enriched chunks for vectors**, **X-Ray for agent context**, **append for durable writes**.
359
+ This complements §3.4 **AGENT WRITER** (weekly append + headless splice) and §3.2 **SYNAPSE** (human/RAG chunking): one stack, multiple projections — **enriched chunks for vectors**, **X-Ray + alias state for agent context**, **append / splice for durable writes**.
338
360
 
339
361
  ---
340
362
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "logseq-matryca-parser"
7
- version = "0.3.0"
7
+ version = "0.3.2"
8
8
  description = "The Logos Protocol: Deterministic Logseq AST parsing for Matryca.ai."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import sys
6
6
 
7
- __version__ = "0.3.0"
7
+ __version__ = "0.3.2"
8
8
 
9
9
  from .agent_writer import LogseqConfigReader, logseq_agent_write
10
10
  from .exceptions import BlockReferenceError, LogseqIndentationError, LogseqParserError
@@ -10,6 +10,8 @@ from logseq_matryca_parser.logos_core import LogseqNode
10
10
 
11
11
  logger = logging.getLogger(__name__)
12
12
 
13
+ XRAY_STATE_FILENAME = ".matryca_xray_state.json"
14
+
13
15
 
14
16
  def _flatten_subtrees(nodes: list[LogseqNode]) -> list[LogseqNode]:
15
17
  """Depth-first list of nodes under each root, preserving outline order."""
@@ -503,7 +503,11 @@ def agent_read(
503
503
  query: str | None = typer.Option(None, "--query", help="Substring search on clean_text."),
504
504
  ) -> None:
505
505
  """Load a graph, filter nodes, and print ultra-dense X-Ray text to stdout (no Rich)."""
506
- from logseq_matryca_parser.agent_press import SessionAliasRegistry, to_xray_markdown
506
+ from logseq_matryca_parser.agent_press import (
507
+ SessionAliasRegistry,
508
+ XRAY_STATE_FILENAME,
509
+ to_xray_markdown,
510
+ )
507
511
  from logseq_matryca_parser.graph import LogseqGraph
508
512
 
509
513
  if not graph_path.exists() or not graph_path.is_dir():
@@ -520,6 +524,9 @@ def agent_read(
520
524
 
521
525
  registry = SessionAliasRegistry()
522
526
  registry.generate_aliases(nodes)
527
+ state_path = graph_path.resolve() / XRAY_STATE_FILENAME
528
+ registry.save_to_disk(state_path)
529
+
523
530
  output = to_xray_markdown(nodes, registry)
524
531
  if output:
525
532
  sys.stdout.write(output)
@@ -527,5 +534,70 @@ def agent_read(
527
534
  sys.stdout.write("\n")
528
535
 
529
536
 
537
+ @app.command("agent-write")
538
+ def agent_write(
539
+ graph_path: Path = typer.Argument(..., help="Path to the Logseq graph root."),
540
+ content: str = typer.Option(..., "--content", help="Markdown body for the new child bullet."),
541
+ alias: int | None = typer.Option(
542
+ None,
543
+ "--alias",
544
+ help="Session alias from a prior agent-read (resolved via .matryca_xray_state.json).",
545
+ ),
546
+ target_uuid: str | None = typer.Option(
547
+ None,
548
+ "--target-uuid",
549
+ help="Parent block UUID (bypasses alias registry).",
550
+ ),
551
+ state_file: Path | None = typer.Option(
552
+ None,
553
+ "--state-file",
554
+ help="Alias registry JSON (default: <graph>/.matryca_xray_state.json).",
555
+ ),
556
+ ) -> None:
557
+ """Append a child block under a parent via headless AST markdown splicing."""
558
+ from logseq_matryca_parser.agent_press import SessionAliasRegistry, XRAY_STATE_FILENAME
559
+ from logseq_matryca_parser.agent_writer import append_child_to_node
560
+ from logseq_matryca_parser.graph import LogseqGraph
561
+
562
+ if alias is None and target_uuid is None:
563
+ print("Provide --alias or --target-uuid.", file=sys.stderr)
564
+ raise typer.Exit(code=1)
565
+ if alias is not None and target_uuid is not None:
566
+ print("Use only one of --alias or --target-uuid.", file=sys.stderr)
567
+ raise typer.Exit(code=1)
568
+
569
+ if not graph_path.exists() or not graph_path.is_dir():
570
+ print(f"Invalid graph path: {graph_path}", file=sys.stderr)
571
+ raise typer.Exit(code=1)
572
+
573
+ graph = LogseqGraph.load_directory(graph_path.resolve())
574
+
575
+ parent_uuid = target_uuid
576
+ if alias is not None:
577
+ registry_path = state_file or (graph_path.resolve() / XRAY_STATE_FILENAME)
578
+ if not registry_path.is_file():
579
+ print(f"Alias state file not found: {registry_path}", file=sys.stderr)
580
+ raise typer.Exit(code=1)
581
+ registry = SessionAliasRegistry.load_from_disk(registry_path)
582
+ parent_uuid = registry.resolve_alias(alias)
583
+ if parent_uuid is None:
584
+ print(f"Unknown alias: {alias}", file=sys.stderr)
585
+ raise typer.Exit(code=1)
586
+
587
+ assert parent_uuid is not None
588
+ try:
589
+ append_child_to_node(graph, parent_uuid, content)
590
+ except ValueError as exc:
591
+ print(str(exc), file=sys.stderr)
592
+ raise typer.Exit(code=1) from exc
593
+
594
+ console.print(
595
+ f"[bold green]Appended child under[/] {parent_uuid}",
596
+ no_wrap=True,
597
+ overflow="ignore",
598
+ crop=False,
599
+ )
600
+
601
+
530
602
  if __name__ == "__main__":
531
603
  app()
@@ -115,3 +115,38 @@ def test_agent_read_cli_plain_stdout(tmp_path: Path) -> None:
115
115
  assert "\x1b[" not in result.output
116
116
  assert "[0]" in result.output
117
117
  assert "X-Ray target" in result.output
118
+
119
+ state_path = graph_root / ".matryca_xray_state.json"
120
+ assert state_path.is_file()
121
+ restored = SessionAliasRegistry.load_from_disk(state_path)
122
+ assert restored.resolve_alias(0) is not None
123
+
124
+
125
+ def test_agent_write_cli_splices_via_alias_state(tmp_path: Path) -> None:
126
+ graph_root = tmp_path / "graph"
127
+ pages = graph_root / "pages"
128
+ pages.mkdir(parents=True)
129
+ (graph_root / "journals").mkdir()
130
+ (pages / "Write.md").write_text("- Parent #parent-tag\n", encoding="utf-8")
131
+
132
+ read_result = runner.invoke(
133
+ app,
134
+ ["agent-read", str(graph_root), "--tag", "parent-tag"],
135
+ )
136
+ assert read_result.exit_code == 0
137
+
138
+ write_result = runner.invoke(
139
+ app,
140
+ [
141
+ "agent-write",
142
+ str(graph_root),
143
+ "--alias",
144
+ "0",
145
+ "--content",
146
+ "Child from agent-write",
147
+ ],
148
+ )
149
+ assert write_result.exit_code == 0
150
+
151
+ updated = (pages / "Write.md").read_text(encoding="utf-8")
152
+ assert "Child from agent-write" in updated
@@ -864,7 +864,7 @@ wheels = [
864
864
 
865
865
  [[package]]
866
866
  name = "logseq-matryca-parser"
867
- version = "0.3.0"
867
+ version = "0.3.2"
868
868
  source = { editable = "." }
869
869
  dependencies = [
870
870
  { name = "pydantic" },