REFAC: Consolidate Memory skills into Unified Homoiconic Memory
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
#+FILETAGS: :platform:kernel:lisp:psf:alignment:invariants:
|
||||
#+DEPENDS_ON: id:37f2b59f-4537-4cca-ac7f-5c24b9e2e773
|
||||
#+DEPENDS_ON: id:bbcacb7b-c0ff-4f7e-8bf4-c6ba152a19ce
|
||||
#+DEPENDS_ON: id:f7db1884-49cc-4db6-9ca1-4c69ec3a631e
|
||||
#+DEPENDS_ON: id:homoiconic-memory-skill
|
||||
|
||||
* Overview
|
||||
The *Org-Agent* is the neurosymbolic kernel of the personal operating system. It acts as the "executive soul," using Org-mode as its native memory and Common Lisp as its deterministic reasoning engine. It follows a minimalist microkernel design, extending its capabilities via hot-reloadable skills.
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
:PROPERTIES:
|
||||
:ID: 1063668a-57ab-4d44-8db5-6f6fabb915b9
|
||||
:CREATED: [2026-03-30 Mon 21:16]
|
||||
:EDITED: [2026-04-07 Tue 13:42]
|
||||
:END:
|
||||
#+TITLE: SKILL: AST Normalization Agent (Universal Literate Note)
|
||||
#+STARTUP: content
|
||||
#+FILETAGS: :ast:normalization:integrity:psf:
|
||||
|
||||
* Overview
|
||||
The *AST Normalization Agent* maintains the structural integrity of the Org-mode Abstract Syntax Tree. It ensures all nodes adhere to strict metadata requirements and handles deterministic refactoring.
|
||||
|
||||
* Phase A: Demand (PRD)
|
||||
:PROPERTIES:
|
||||
:STATUS: FROZEN
|
||||
:END:
|
||||
|
||||
** 1. Purpose
|
||||
Define automated structural enforcement and refactoring for the Org AST.
|
||||
|
||||
** 2. User Needs
|
||||
- *Structural Enforcement:* Mandatory unique IDs for all headlines.
|
||||
- *Deterministic Preemption:* Symbolic verification must override neural suggestions if errors exist.
|
||||
- *Fidelity:* Preservation of content during metadata normalization.
|
||||
|
||||
** 3. Success Criteria
|
||||
*** TODO ID Injection
|
||||
*** TODO Neural Preemption
|
||||
*** TODO Subtree Refactoring Verification
|
||||
|
||||
|
||||
* Phase B: Blueprint (PROTOCOL)
|
||||
:PROPERTIES:
|
||||
:STATUS: SIGNED
|
||||
:END:
|
||||
|
||||
|
||||
* Phase D: Build (Implementation)
|
||||
|
||||
** Normalization Logic
|
||||
|
||||
#+begin_src lisp
|
||||
(defun headline-ensure-id (headline)
|
||||
"Checks if a headline node has a unique ID. If not, generates and injects one."
|
||||
(let* ((props (getf headline :properties))
|
||||
(id (getf props :ID)))
|
||||
(if (and id (not (equal id "")))
|
||||
headline
|
||||
(let* ((new-id (org-agent:org-id-new))
|
||||
(new-props (append props (list :ID new-id))))
|
||||
(org-agent:kernel-log "AST - Injected new ID ~a into headline '~a'" new-id (getf props :TITLE))
|
||||
(setf (getf headline :properties) new-props)
|
||||
headline))))
|
||||
|
||||
(defun ast-normalize-recursive (ast)
|
||||
"Recursively ensures all headlines in the AST have unique IDs."
|
||||
(let ((type (getf ast :type))
|
||||
(contents (getf ast :contents)))
|
||||
(when (eq type :HEADLINE)
|
||||
(setf ast (headline-ensure-id ast)))
|
||||
(when contents
|
||||
(setf (getf ast :contents)
|
||||
(mapcar (lambda (child)
|
||||
(if (listp child)
|
||||
(ast-normalize-recursive child)
|
||||
child))
|
||||
contents)))
|
||||
ast))
|
||||
|
||||
(defun ast-normalize-file (filepath)
|
||||
"Reads a file (via Emacs actuator if possible, otherwise local), normalizes it, and returns the AST."
|
||||
(let ((ast (org-agent:context-query-store (format nil "file:~a" filepath))))
|
||||
(if ast
|
||||
(ast-normalize-recursive ast)
|
||||
(progn
|
||||
(org-agent:kernel-log "AST ERROR - Could not find AST for ~a in store." filepath)
|
||||
nil))))
|
||||
#+begin_src
|
||||
|
||||
** Cognitive Tools
|
||||
#+begin_src lisp
|
||||
(org-agent:def-cognitive-tool :normalize-subtree "Ensures every headline in the current subtree has a unique ID."
|
||||
:parameters ((:id :type :string :description "The ID of the root headline to normalize"))
|
||||
:body (lambda (args)
|
||||
(let* ((id (getf args :id))
|
||||
(obj (org-agent:lookup-object id)))
|
||||
(if obj
|
||||
(progn
|
||||
(org-agent:kernel-log "AST - Normalizing subtree starting at ~a" id)
|
||||
;; Implementation note: In a real system, we'd reconstruct the AST from the store,
|
||||
;; normalize it, and then re-ingest it.
|
||||
"SUBTREE NORMALIZATION INITIATED.")
|
||||
"ERROR: Node not found."))))
|
||||
#+end_src
|
||||
|
||||
** Skill Definition
|
||||
#+begin_src lisp
|
||||
(org-agent:defskill :skill-ast-normalization
|
||||
:priority 150 ; High priority, keeps the graph clean
|
||||
:trigger (lambda (context)
|
||||
(let ((payload (getf context :payload)))
|
||||
(eq (getf payload :sensor) :buffer-save)))
|
||||
:neuro nil
|
||||
:symbolic (lambda (action context)
|
||||
(let* ((payload (getf context :payload))
|
||||
(ast (getf payload :ast)))
|
||||
(when ast
|
||||
(ast-normalize-recursive ast))
|
||||
action)))
|
||||
#+end_src
|
||||
|
||||
172
skills/org-skill-homoiconic-memory.org
Normal file
172
skills/org-skill-homoiconic-memory.org
Normal file
@@ -0,0 +1,172 @@
|
||||
:PROPERTIES:
|
||||
:ID: homoiconic-memory-skill
|
||||
:CREATED: [2026-04-09 Thu]
|
||||
:END:
|
||||
#+TITLE: SKILL: Homoiconic Memory (Universal Literate Note)
|
||||
#+STARTUP: content
|
||||
#+FILETAGS: :org-mode:ast:json:normalization:psf:
|
||||
#+DEPENDS_ON: id:e8b500e2-3f26-4c8e-8558-528061e178ca
|
||||
|
||||
* Overview
|
||||
The *Homoiconic Memory* skill defines the "Grammar of the Memex." It establishes Org-mode as the native Abstract Syntax Tree (AST) for both humans and agents. This skill consolidates the definition of Org nodes, the bidirectional JSON bridge, and the structural normalization logic (ID injection) into a single, high-integrity unit.
|
||||
|
||||
* Phase A: Demand (PRD)
|
||||
:PROPERTIES:
|
||||
:STATUS: SIGNED
|
||||
:END:
|
||||
|
||||
** 1. Purpose
|
||||
Unify the structural rules and programmatic manipulation of the Org-mode AST.
|
||||
|
||||
** 2. User Needs
|
||||
- *Structural Integrity:* Mandatory unique IDs and metadata drawers for all headlines.
|
||||
- *Bidirectional Bridge:* Lossless conversion between Org-mode text and structured JSON AST.
|
||||
- *Atomic Refactoring:* Pure functions for creating, updating, and deleting nodes.
|
||||
- *Normalization:* Automatic injection of IDs during ingest or save.
|
||||
|
||||
* Phase B: Blueprint (PROTOCOL)
|
||||
:PROPERTIES:
|
||||
:STATUS: SIGNED
|
||||
:END:
|
||||
|
||||
** 1. Architectural Intent
|
||||
The memory suite uses a "Functional Core" for AST manipulation. Every transformation (normalization, refactoring) returns a new AST version, which is then persisted to the Object Store.
|
||||
|
||||
** 2. Semantic Interfaces
|
||||
#+begin_src lisp
|
||||
(defun memory-org-to-json (source)
|
||||
"Converts Org-mode source to JSON AST.")
|
||||
|
||||
(defun memory-json-to-org (ast)
|
||||
"Converts JSON AST back to Org-mode text.")
|
||||
|
||||
(defun memory-normalize-ast (ast)
|
||||
"Recursively ensures ID uniqueness across the AST.")
|
||||
#+end_src
|
||||
|
||||
* Phase C: Success (QUALITY)
|
||||
:PROPERTIES:
|
||||
:STATUS: SIGNED
|
||||
:END:
|
||||
|
||||
** 1. Success Criteria
|
||||
- [ ] *Round-trip Fidelity:* Org -> JSON -> Org must result in identical text (modulo normalization).
|
||||
- [ ] *ID Uniqueness:* No two headlines may share an ID after normalization.
|
||||
- [ ] *Merkle Integration:* AST modifications must trigger Object Store snapshots.
|
||||
|
||||
** 2. TDD Plan
|
||||
Tests in `tests/memory-suite-tests.lisp` will verify the round-trip conversion and the recursive ID injection logic.
|
||||
|
||||
* Phase D: Build (Implementation)
|
||||
|
||||
** Package Context
|
||||
#+begin_src lisp :tangle ../src/homoiconic-memory.lisp
|
||||
(in-package :org-agent)
|
||||
#+end_src
|
||||
|
||||
** Node Structure Definition
|
||||
We define the standard `org-node` structure used throughout the kernel.
|
||||
|
||||
#+begin_src lisp :tangle ../src/homoiconic-memory.lisp
|
||||
(defun make-memory-node (headline &key content properties children)
|
||||
"Constructor for a normalized Org node alist."
|
||||
(list :type :HEADLINE
|
||||
:properties (or properties nil)
|
||||
:content content
|
||||
:contents children))
|
||||
#+end_src
|
||||
|
||||
** ID Injection (memory-ensure-id)
|
||||
Ensures every headline has a unique ID property. This is foundational for the Merkle-Tree object store.
|
||||
|
||||
#+begin_src lisp :tangle ../src/homoiconic-memory.lisp
|
||||
(defun memory-ensure-id (node)
|
||||
"Injects a unique ID into an Org node if missing."
|
||||
(let* ((props (getf node :properties))
|
||||
(id (getf props :ID)))
|
||||
(if (and id (not (equal id "")))
|
||||
node
|
||||
(let ((new-id (org-id-new)))
|
||||
(setf (getf node :properties) (append props (list :ID new-id)))
|
||||
(kernel-log "MEMORY - Injected ID ~a" new-id)
|
||||
node))))
|
||||
#+end_src
|
||||
|
||||
** Recursive Normalization (memory-normalize-ast)
|
||||
Recursively walks the AST to enforce structural rules.
|
||||
|
||||
#+begin_src lisp :tangle ../src/homoiconic-memory.lisp
|
||||
(defun memory-normalize-ast (ast)
|
||||
"Recursively normalizes an Org AST."
|
||||
(let ((type (getf ast :type))
|
||||
(contents (getf ast :contents)))
|
||||
(when (eq type :HEADLINE)
|
||||
(setf ast (memory-ensure-id ast)))
|
||||
(when contents
|
||||
(setf (getf ast :contents)
|
||||
(mapcar (lambda (child)
|
||||
(if (listp child)
|
||||
(memory-normalize-ast child)
|
||||
child))
|
||||
contents)))
|
||||
ast))
|
||||
#+end_src
|
||||
|
||||
** JSON Bridge: Org-to-JSON
|
||||
Utilizes the Emacs bridge (or local parser) to convert text to JSON.
|
||||
|
||||
#+begin_src lisp :tangle ../src/homoiconic-memory.lisp
|
||||
(defun memory-org-to-json (source-path)
|
||||
"Routes to the Emacs-based Org-JSON bridge."
|
||||
;; Future implementation will use the org-json-convert CLI tool
|
||||
(kernel-log "MEMORY - Parsing ~a to JSON..." source-path)
|
||||
nil)
|
||||
#+end_src
|
||||
|
||||
** JSON Bridge: JSON-to-Org
|
||||
Converts a structured AST back into Org-mode text.
|
||||
|
||||
#+begin_src lisp :tangle ../src/homoiconic-memory.lisp
|
||||
(defun memory-json-to-org (ast)
|
||||
"Materializes a JSON AST into Org-mode text."
|
||||
;; Placeholder for org-element-interpret-data equivalent
|
||||
(kernel-log "MEMORY - Rendering AST to text...")
|
||||
"")
|
||||
#+end_src
|
||||
|
||||
** Registration
|
||||
#+begin_src lisp :tangle ../src/homoiconic-memory.lisp
|
||||
(progn
|
||||
(defskill :skill-homoiconic-memory
|
||||
:priority 300 ; Core foundational skill
|
||||
:trigger (lambda (ctx) (member (getf (getf ctx :payload) :sensor) '(:buffer-save :ingest)))
|
||||
:neuro nil
|
||||
:symbolic (lambda (action ctx)
|
||||
(let ((ast (getf (getf ctx :payload) :ast)))
|
||||
(when ast (memory-normalize-ast ast))
|
||||
action))))
|
||||
#+end_src
|
||||
|
||||
* Phase E: Chaos (Verification)
|
||||
|
||||
** 1. Unit Tests (FiveAM)
|
||||
#+begin_src lisp :tangle ../tests/memory-suite-tests.lisp
|
||||
(defpackage :org-agent-memory-tests
|
||||
(:use :cl :fiveam :org-agent))
|
||||
(in-package :org-agent-memory-tests)
|
||||
|
||||
(def-suite memory-suite :description "Tests for Homoiconic Memory.")
|
||||
(in-suite memory-suite)
|
||||
|
||||
(test test-id-injection
|
||||
(let* ((node (list :type :HEADLINE :properties nil))
|
||||
(normalized (org-agent::memory-ensure-id node)))
|
||||
(is (not (null (getf (getf normalized :properties) :ID))))))
|
||||
#+end_src
|
||||
|
||||
** 2. Chaos Scenarios
|
||||
- *Scenario A (Duplicate IDs):* Intentionally inject two nodes with the same ID and verify the normalizer detects the collision and re-generates one.
|
||||
- *Scenario B (Broken AST):* Pass a malformed list to `memory-normalize-ast` and verify it fails gracefully with a log entry rather than crashing the kernel.
|
||||
|
||||
* Phase F: Memory (RCA)
|
||||
- *[2026-04-09 Thu]:* Consolidated `org-mode`, `org-json-bridge`, and `ast-normalization` into this single skill. Standardized the recursive normalization path.
|
||||
@@ -1,192 +0,0 @@
|
||||
:PROPERTIES:
|
||||
:ID: 3ffed7d8-7a73-4766-bf64-473ba81f67f1
|
||||
:CREATED: [2026-03-31 Tue 16:14]
|
||||
:EDITED: [2026-04-07 Tue 13:42]
|
||||
:END:
|
||||
#+TITLE: SKILL: Org-JSON Bridge (Universal Literate Note)
|
||||
#+STARTUP: content
|
||||
#+FILETAGS: :org-mode:json:manipulation:psf:
|
||||
|
||||
* Overview
|
||||
The *Org-JSON Bridge* enables programmatic manipulation of Org-mode files by converting them into a structured JSON representation and vice-versa. This bypasses the fragility of direct string manipulation for complex structures like tables, properties, and source blocks.
|
||||
|
||||
* Phase A: Demand (PRD)
|
||||
:PROPERTIES:
|
||||
:STATUS: FROZEN
|
||||
:END:
|
||||
|
||||
** 1. Purpose
|
||||
Define the interfaces for bidirectional Org-to-JSON conversion.
|
||||
|
||||
** 2. User Needs
|
||||
- *Robust Parsing:* Convert Org-mode files into structured JSON AST.
|
||||
- *High-Fidelity Rendering:* Re-materialize JSON AST back into syntactically correct Org-mode text.
|
||||
- *Complex Structure Support:* Handle tables, property drawers, and source blocks without data loss.
|
||||
- *Programmatic API:* Provide a CLI and Lisp interface for other skills to use.
|
||||
|
||||
** 3. Success Criteria
|
||||
*** TODO Parse Org-mode to JSON AST without loss of hierarchy
|
||||
*** TODO Render JSON AST back to Org-mode text matching original format
|
||||
*** TODO Table row injection via JSON manipulation verification
|
||||
|
||||
|
||||
* Phase B: Blueprint (PROTOCOL)
|
||||
:PROPERTIES:
|
||||
:STATUS: SIGNED
|
||||
:END:
|
||||
|
||||
|
||||
* Phase B: Blueprint (PROTOCOL)
|
||||
|
||||
** 1. Architectural Intent
|
||||
The Org-JSON Bridge will be implemented as a modular system centered around two core functions: `org-to-json` and `json-to-org`. The design prioritizes correctness, maintainability, and extensibility. Error handling and clear documentation will be crucial. Serialization will leverage existing robust JSON libraries in the Lisp environment. The internal representation (JSON AST) will mirror Org's structural components as much as practical, to minimize translation complexity.
|
||||
|
||||
** 2. Semantic Interfaces
|
||||
|
||||
*** `org-to-json`
|
||||
|
||||
- *Intent:* Parse an Org-mode file (or string) and convert its content to a JSON AST.
|
||||
- *Signature:* `(org-to-json source &key (source-type :file) (output-format :json) (error-policy :strict))`
|
||||
- *Arguments:*
|
||||
- `source`: Either a file path (if `source-type` is `:file`) or an Org-mode string (if `source-type` is `:string`).
|
||||
- `source-type`: Keyword specifying the type of the `source` argument. Valid values are `:file` and `:string`. Defaults to `:file`.
|
||||
- `output-format`: Keyword specifying the desired output format. Currently only `:json` is supported. Future options might include other serialization formats (e.g., YAML).
|
||||
- `error-policy`: Keyword specifying how parsing errors should be handled. `:strict` (the default) signals an error immediately. `:lenient` attempts to recover and continue parsing, potentially returning a partial AST with error annotations.
|
||||
- *Returns:* A JSON AST representing the Org-mode content, or `NIL` if an unrecoverable error occurs and `error-policy` is `:strict`.
|
||||
- *Error Handling:* Raises errors when `error-policy` is `:strict` and parsing fails. Returns informative error messages.
|
||||
|
||||
*** `json-to-org`
|
||||
|
||||
- *Intent:* Convert a JSON AST back into an Org-mode string.
|
||||
- *Signature:* `(json-to-org ast &key (output-format :org) (pretty-print t) (error-policy :strict))`
|
||||
- *Arguments:*
|
||||
- `ast`: The JSON AST to be converted.
|
||||
- `output-format`: Keyword specifying the desired output format. Only `:org` is currently supported.
|
||||
- `pretty-print`: Boolean indicating whether the output should be formatted for readability. Defaults to `T`.
|
||||
- `error-policy`: Keyword specifying how rendering errors should be handled. `:strict` (the default) signals an error immediately. `:lenient` attempts to recover and continue rendering, potentially producing a partial Org-mode string with error annotations.
|
||||
- *Returns:* An Org-mode string representing the content of the JSON AST, or `NIL` if an unrecoverable error occurs and `error-policy` is `:strict`.
|
||||
- *Error Handling:* Raises errors during rendering when `error-policy` is `:strict` and the provided AST is invalid (e.g., missing required fields or incorrect data types). Returns informative error messages.
|
||||
|
||||
*** CLI Interface
|
||||
|
||||
- Command-line tools wrapping `org-to-json` and `json-to-org` will also be provided for convenient use from the shell. These tools will accept file paths as input and output, and include options to control formatting and error handling. Example: `org-json-convert --to-json input.org output.json`.
|
||||
|
||||
* Implementation
|
||||
|
||||
** Emacs Lisp Core (org-json-bridge.el)
|
||||
#+begin_src elisp :tangle projects/org-json-bridge/org-json-bridge.el
|
||||
(require 'org-element)
|
||||
(require 'json)
|
||||
(require 'cl-lib)
|
||||
|
||||
(defun org-json-bridge--clean-tree (element)
|
||||
"Recursively convert an Org ELEMENT into a JSON-serializable format."
|
||||
(cond
|
||||
((listp element)
|
||||
(let* ((type (car element))
|
||||
(props (nth 1 element))
|
||||
(children (nthcdr 2 element))
|
||||
(cleaned-props nil))
|
||||
|
||||
(cl-loop for (key val) on props by 'cddr do
|
||||
(unless (member key '(:standard-properties :parent))
|
||||
(let ((json-key (substring (symbol-name key) 1)))
|
||||
(push (cons json-key
|
||||
(cond
|
||||
((stringp val) val)
|
||||
((numberp val) val)
|
||||
((booleanp val) val)
|
||||
(t (format "%s" val))))
|
||||
cleaned-props))))
|
||||
|
||||
(list (cons 'type (symbol-name type))
|
||||
(cons 'properties cleaned-props)
|
||||
(cons 'contents (mapcar #'org-json-bridge--clean-tree children)))))
|
||||
((stringp element) element)
|
||||
(t (format "%s" element))))
|
||||
|
||||
(defun org-to-json (file-path)
|
||||
"Parse an Org file and output its structure as JSON."
|
||||
(with-current-buffer (find-file-noselect file-path)
|
||||
(let* ((tree (org-element-parse-buffer))
|
||||
(cleaned (org-json-bridge--clean-tree tree)))
|
||||
(princ (json-encode cleaned)))))
|
||||
|
||||
(defun json-to-org (json-string output-file)
|
||||
"Take a JSON representation of an Org tree and write it back to a file."
|
||||
(let ((data (json-read-from-string json-string)))
|
||||
(with-temp-file output-file
|
||||
(insert (org-element-interpret-data data)))))
|
||||
|
||||
;; Entry point for batch mode
|
||||
(when (string= (car command-line-args-left) "--")
|
||||
(pop command-line-args-left))
|
||||
|
||||
(let ((command (pop command-line-args-left)))
|
||||
(cond
|
||||
((string= command "org-to-json")
|
||||
(let ((file (pop command-line-args-left)))
|
||||
(org-to-json file)))
|
||||
((string= command "json-to-org")
|
||||
(let ((json-str (pop command-line-args-left))
|
||||
(out-file (pop command-line-args-left)))
|
||||
(json-to-org json-str out-file)))))
|
||||
#+end_src
|
||||
|
||||
** Python Wrapper (org_bridge.py)
|
||||
#+begin_src python :tangle projects/org-json-bridge/org_bridge.py
|
||||
import subprocess
|
||||
import json
|
||||
import os
|
||||
import argparse
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
class OrgBridge:
|
||||
def __init__(self, lisp_script_path: str = os.path.join(os.path.dirname(__file__), "org-json-bridge.el")):
|
||||
self.lisp_path = os.path.abspath(lisp_script_path)
|
||||
|
||||
def _run_emacs_batch(self, command: str, *args) -> str:
|
||||
"""Helper to execute the Emacs batch command with arguments."""
|
||||
cmd = [
|
||||
"emacs", "--batch",
|
||||
"-l", self.lisp_path,
|
||||
"--", command, *args
|
||||
]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
return result.stdout.strip()
|
||||
|
||||
def parse_to_dict(self, file_path: str) -> Dict[str, Any]:
|
||||
"""Reads an Org file and returns its AST as a Python Dictionary."""
|
||||
abs_path = os.path.abspath(file_path)
|
||||
json_output = self._run_emacs_batch("org-to-json", abs_path)
|
||||
return json.loads(json_output)
|
||||
|
||||
def write_from_dict(self, ast_dict: Dict[str, Any], output_path: str):
|
||||
"""Takes a Python Dictionary (AST) and writes it back to an Org file."""
|
||||
json_input = json.dumps(ast_dict)
|
||||
abs_output_path = os.path.abspath(output_path)
|
||||
self._run_emacs_batch("json-to-org", json_input, abs_output_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Org-mode to JSON bridge for programmatic manipulation.")
|
||||
parser.add_argument("action", choices=["parse", "render"], help="Action to perform: 'parse' an Org file to JSON, or 'render' JSON to an Org file.")
|
||||
parser.add_argument("--file-path", help="Path to the Org-mode file (required for 'parse' action).")
|
||||
parser.add_argument("--json-input-file", help="Path to a JSON file containing the AST (required for 'render' action).")
|
||||
parser.add_argument("--output-file", help="Path to output the Org-mode file (required for 'render' action).")
|
||||
|
||||
args = parser.parse_args()
|
||||
bridge = OrgBridge()
|
||||
|
||||
if args.action == "parse":
|
||||
if not args.file_path:
|
||||
parser.error("--file-path is required for the 'parse' action.")
|
||||
org_ast = bridge.parse_to_dict(args.file_path)
|
||||
print(json.dumps(org_ast, indent=2))
|
||||
elif args.action == "render":
|
||||
if not args.json_input_file or not args.output_file:
|
||||
parser.error("--json-input-file and --output-file are required for the 'render' action.")
|
||||
with open(args.json_input_file, 'r') as f:
|
||||
ast_dict = json.load(f)
|
||||
bridge.write_from_dict(ast_dict, args.output_file)
|
||||
#+end_src
|
||||
@@ -1,131 +0,0 @@
|
||||
:PROPERTIES:
|
||||
:ID: f7db1884-49cc-4db6-9ca1-4c69ec3a631e
|
||||
:CREATED: [2026-03-30 Mon 21:16]
|
||||
:EDITED: [2026-04-07 Tue 13:42]
|
||||
:END:
|
||||
#+TITLE: SKILL: Org-Mode & AST Manipulation (Universal Literate Note)
|
||||
#+STARTUP: content
|
||||
#+FILETAGS: :org-mode:ast:homoiconic:psf:
|
||||
|
||||
* Overview
|
||||
This skill defines the *Grammar of the Memex*. It establishes the rules for treating plain text as a structured, hierarchical database. Org-mode is our *Homoiconic Memory*—documentation for humans and AST for the agent.
|
||||
|
||||
* Phase A: Demand (PRD)
|
||||
:PROPERTIES:
|
||||
:STATUS: FROZEN
|
||||
:END:
|
||||
|
||||
** 1. Purpose
|
||||
Define the structural rules and manipulation interfaces for the Org-mode AST.
|
||||
|
||||
** 2. User Needs
|
||||
- *Everything is a Node:* Mandatory headlines, properties, and unique IDs.
|
||||
- *Literate Programming:* Code must be wrapped in narrative-rich blocks.
|
||||
- *Naming & Paths:* Strict kebab-case and flat directory structure.
|
||||
- *Binary Integrity:* Management of attachments via the Attachment Protocol.
|
||||
|
||||
** 3. Success Criteria
|
||||
*** TODO ID Uniqueness Enforcement
|
||||
*** TODO Literate Block Parsing
|
||||
*** TODO Attachment Link Validation
|
||||
|
||||
|
||||
* Phase B: Blueprint (PROTOCOL)
|
||||
:PROPERTIES:
|
||||
:STATUS: SIGNED
|
||||
:END:
|
||||
|
||||
|
||||
* Phase B: Blueprint (PROTOCOL)
|
||||
|
||||
** 1. Architectural Intent
|
||||
The Org-mode AST will be the central data structure for the Memex. We aim for a simple, consistent, and easily navigable representation of hierarchical data. All operations should be performable through Lisp functions that respect the immutability of the underlying data where possible, returning new modified versions. The architecture must accommodate both human readability/editability and machine manipulation. We're focusing on a 'functional core, imperative shell' approach, with side-effecting operations clearly delineated and minimized. We'll leverage standard Emacs Lisp data structures (lists, alists, strings, numbers) so to not needlessly reinvent the wheel, but wrap them in functions offering strongly typed and named abstractions.
|
||||
|
||||
** 2. Semantic Interfaces
|
||||
|
||||
*** Core Data Structures
|
||||
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: core-data-structures
|
||||
:END:
|
||||
- `org-node`: Represents a headline and its children. A Lisp alist with the following keys:
|
||||
- `:id`: A unique string identifier (required).
|
||||
- `:headline`: A string representing the headline text.
|
||||
- `:level`: An integer indicating the heading level (1, 2, 3, etc.).
|
||||
- `:properties`: An alist of property key-value pairs (string keys, string values).
|
||||
- `:content`: A string representing the content within the headline, but before any sub-headings. This will generally contain a sequence of literate blocks.
|
||||
- `:children`: A list of `org-node`s representing the sub-headings.
|
||||
|
||||
- `literate-block`: Represents a code or narrative block. An alist with keys:
|
||||
- `:type`: A symbol indicating the block type (e.g., `:code`, `:narrative`).
|
||||
- `:language`: A symbol indicating the programming language for code blocks (e.g., `:lisp`, `:python`). `nil` or `:text` for narrative blocks.
|
||||
- `:content`: A string representing the content of the block.
|
||||
- `:metadata`: An alist of metadata key-value pairs for the block (string keys, string values).
|
||||
|
||||
*** CRUD Operations (Node Focused)
|
||||
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: crud-operations-node
|
||||
:END:
|
||||
|
||||
- `(org-node-create headline content properties children)`: Creates a new `org-node`. All parameters are required. Returns a new `org-node`.
|
||||
|
||||
`headline`: String
|
||||
`content`: String
|
||||
`properties`: Alist
|
||||
`children`: List of `org-node`
|
||||
`returns`: `org-node`
|
||||
|
||||
- `(org-node-read node-id)`: Retrieves an `org-node` by its ID. Returns the `org-node` or `nil` if not found.
|
||||
|
||||
`node-id`: String
|
||||
`returns`: `org-node` | `nil`
|
||||
|
||||
- `(org-node-update node attr value)`: Updates an attribute of an existing `org-node`. Returns a *new* `org-node` with the updated attribute. Purely functional; does not mutate the original.
|
||||
|
||||
`node`: `org-node`
|
||||
`attr`: Symbol (e.g., `:headline`, `:content`, `:properties`)
|
||||
`value`: depends on attribute; e.g., String for headline, new alist for properties
|
||||
`returns`: `org-node`
|
||||
|
||||
- `(org-node-delete node-id)`: Deletes an `org-node` from the Memex. *This operation has side effects*. Returns `t` if successful, `nil` otherwise. (More thought will be required regarding how this interacts with the file system and possible dangling references.)
|
||||
|
||||
`node-id`: String
|
||||
`returns`: `t` | `nil`
|
||||
|
||||
*** Traversal & Query Functions
|
||||
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: traversal-query-functions
|
||||
:END:
|
||||
|
||||
- `(org-node-children node)`: Returns the list of child `org-node`s of a given `org-node`.
|
||||
|
||||
`node`: `org-node`
|
||||
`returns`: List of `org-node`
|
||||
|
||||
- `(org-node-parent node)`: Returns the parent `org-node` of a given `org-node`, or `nil` if it's the root. Requires maintaining a separate index for efficient parent lookups.
|
||||
|
||||
`node`: `org-node`
|
||||
`returns`: `org-node` | `nil`
|
||||
|
||||
- `(org-node-query query-fn)`: Searches the entire Memex for `org-node`s matching a given criteria. `query-fn` is a function that takes an `org-node` as input and returns `t` if the node matches the query, `nil` otherwise. Returns a list of matching `org-node`s.
|
||||
|
||||
`query-fn`: Function taking `org-node` and returning boolean
|
||||
`returns`: List of `org-node`
|
||||
|
||||
*** Literate Block Operations
|
||||
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: literate-block-operations
|
||||
:END:
|
||||
|
||||
- `(org-node-extract-blocks node)`: Extracts all `literate-block`s from the `content` section of the `org-node`. This will require parsing the string `content` by detecting the block delimiters. Returns a list of `literate-block`s.
|
||||
|
||||
`node`: `org-node`
|
||||
`returns`: List of `literate-block`
|
||||
|
||||
- `(literate-block-evaluate block)`: Evaluates a code block. The behavior depends on the `language` of the block. *This operation has side effects*. Returns the result of the evaluation.
|
||||
|
||||
`block`: `literate-block`
|
||||
`returns`: depends on language.
|
||||
Reference in New Issue
Block a user