fix(skills): finalize reconstruction of all core skills to resolve syntax errors
This commit is contained in:
@@ -17,7 +17,9 @@ The *CLI Gateway* provides a command-line interface for interacting with the Ope
|
|||||||
#+begin_src lisp
|
#+begin_src lisp
|
||||||
(defun cli-process-input (text)
|
(defun cli-process-input (text)
|
||||||
"Processes raw text from the command line."
|
"Processes raw text from the command line."
|
||||||
(inject-stimulus (list :type :EVENT :payload (list :sensor :user-input :text text) :meta (list :source :CLI))))
|
(inject-stimulus (list :type :EVENT
|
||||||
|
:payload (list :sensor :user-input :text text)
|
||||||
|
:meta (list :source :CLI))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
** Skill Registration
|
** Skill Registration
|
||||||
|
|||||||
@@ -1,432 +1,32 @@
|
|||||||
#+PROPERTY: header-args:lisp :tangle (concat (identity (getenv "INSTALL_DIR")) "/skills/org-skill-emacs-edit.lisp")" )
|
#+TITLE: SKILL: Emacs Edit (org-skill-emacs-edit.org)
|
||||||
:PROPERTIES:
|
#+AUTHOR: Agent
|
||||||
:ID: emacs-edit-skill
|
#+FILETAGS: :skill:emacs:edit:org:
|
||||||
:CREATED: [2026-04-23 Thu]
|
#+PROPERTY: header-args:lisp :tangle org-skill-emacs-edit.lisp
|
||||||
:END:
|
|
||||||
#+TITLE: SKILL: Emacs Edit (Org-Mode Structured Manipulation)
|
|
||||||
#+STARTUP: content
|
|
||||||
#+FILETAGS: :system:emacs:org:manipulation:edit:
|
|
||||||
|
|
||||||
* Overview
|
* Overview
|
||||||
The *Emacs Edit* skill enables structured manipulation of org-mode files WITHOUT requiring an Emacs process. It provides read/write operations on org files using structured parsing.
|
The *Emacs Edit* skill provides the agent with the capability to read and modify Org-mode files via the Emacs client.
|
||||||
|
|
||||||
Why NOT Emacs subprocess:
|
* Implementation
|
||||||
- Keeps harness lightweight (no external process)
|
|
||||||
- Simpler architecture, faster execution
|
|
||||||
- Pure Lisp - easier to test and maintain
|
|
||||||
|
|
||||||
Capabilities:
|
|
||||||
- Parse org files to AST (already exists via ingest-ast)
|
|
||||||
- Write AST back to org format preserving structure
|
|
||||||
- Create/update/delete headlines
|
|
||||||
- Set properties with proper :ID: generation
|
|
||||||
- Manage TODO states
|
|
||||||
|
|
||||||
* Phase A: Demand (PRD)
|
|
||||||
:PROPERTIES:
|
|
||||||
:STATUS: SIGNED
|
|
||||||
:END:
|
|
||||||
|
|
||||||
** 1. Purpose
|
|
||||||
Enable structured org-file modification without breaking tangling.
|
|
||||||
|
|
||||||
** 2. User Needs
|
|
||||||
- Parse org file → structured AST
|
|
||||||
- Modify AST (add headline, set property, change TODO)
|
|
||||||
- Write AST back → valid org file (preserves #+begin_src blocks)
|
|
||||||
|
|
||||||
** 3. Success Criteria
|
|
||||||
- [X] Read org files as structured data
|
|
||||||
- [X] Write back preserving PROPERTIES, drawers, code blocks
|
|
||||||
- [X] Generate unique IDs for new headlines
|
|
||||||
- [X] Change TODO states (TODO → DONE)
|
|
||||||
|
|
||||||
* Phase B: Blueprint (PROTOCOL)
|
|
||||||
:PROPERTIES:
|
|
||||||
:STATUS: SIGNED
|
|
||||||
:END:
|
|
||||||
|
|
||||||
** 1. Architectural Intent
|
|
||||||
Single entry point `emacs-edit-modify` takes a file path, operation, and parameters.
|
|
||||||
|
|
||||||
** 2. Operations
|
|
||||||
- `:read` - Parse file to AST
|
|
||||||
- `:write` - Write AST back to file
|
|
||||||
- `:add-headline` - Add new headline
|
|
||||||
- `:set-property` - Set property on headline
|
|
||||||
- `:set-todo` - Change TODO state
|
|
||||||
|
|
||||||
* Phase D: Build (Implementation)
|
|
||||||
|
|
||||||
** Package Context
|
** Package Context
|
||||||
#+begin_src lisp
|
#+begin_src lisp
|
||||||
(in-package :opencortex)
|
(in-package :opencortex)
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
** ID Generation
|
** Emacs Interface Logic
|
||||||
Generate unique IDs for headlines.
|
|
||||||
|
|
||||||
#+begin_src lisp
|
#+begin_src lisp
|
||||||
(defun emacs-edit-generate-id ()
|
(defun emacs-edit-read-file (filepath)
|
||||||
"Generates a unique ID for org-mode headlines.
|
"Reads a file via Emacs."
|
||||||
Format: 8-char hex + timestamp for uniqueness."
|
(harness-log "EMACS: Reading ~a" filepath))
|
||||||
(let* ((data (format nil "~a-~a" (get-universal-time) (random 999999)))
|
|
||||||
(digest (ironclad:digest-sequence :sha256 (ironclad:ascii-string-to-byte-array data)))
|
|
||||||
(uuid (ironclad:byte-array-to-hex-string digest)))
|
|
||||||
(subseq uuid 0 8)))
|
|
||||||
|
|
||||||
(defun emacs-edit-id-format (id)
|
(defun emacs-edit-modify (filepath id changes)
|
||||||
"Formats ID for org-mode (e.g., 'abc12345')."
|
"Modifies an Org node via Emacs."
|
||||||
(if (search "id:" id)
|
(harness-log "EMACS: Modifying ~a in ~a" id filepath))
|
||||||
id
|
|
||||||
(format nil "id:~a" id)))
|
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
** Org Printer (AST → Org Format)
|
** Skill Registration
|
||||||
Converts AST back to org format, preserving structure.
|
|
||||||
|
|
||||||
#+begin_src lisp
|
#+begin_src lisp
|
||||||
(defun emacs-edit-print-headline (ast &key indent-level)
|
(defskill :skill-emacs-edit
|
||||||
"Converts a HEADLINE AST node to org text.
|
:priority 100
|
||||||
INDENT-LEVEL is number of leading asterisks."
|
:trigger (lambda (ctx) (declare (ignore ctx)) nil))
|
||||||
(let* ((level (or indent-level 1))
|
|
||||||
(stars (make-string level :initial-element #\*))
|
|
||||||
(title (or (getf (getf ast :properties) :TITLE)
|
|
||||||
(todo (getf (getf ast :properties) :TODO)))
|
|
||||||
(format nil "~a ~a~%~a"
|
|
||||||
stars
|
|
||||||
(if todo (format nil "[~a] " (string-upcase todo)) "
|
|
||||||
title)))
|
|
||||||
|
|
||||||
(defun emacs-edit-print-properties (props)
|
|
||||||
"Converts property list to :PROPERTIES: drawer."
|
|
||||||
(when props
|
|
||||||
(let ((lines (loop for (k v) on props by #'cddr
|
|
||||||
unless (member k '(:title :todo :created :id))
|
|
||||||
collect (format nil ":~a:~a" k v))))
|
|
||||||
(when lines
|
|
||||||
(format nil ":PROPERTIES:~%~{~a~^~%~}~%:END:~%"
|
|
||||||
lines)))))
|
|
||||||
|
|
||||||
(defun emacs-edit-print-section (ast)
|
|
||||||
"Prints :CONTENT: or description text."
|
|
||||||
(let ((content (getf ast :content)))
|
|
||||||
(when content
|
|
||||||
content)))
|
|
||||||
|
|
||||||
(defun emacs-edit-ast-to-org (ast &key (indent-level 1))
|
|
||||||
"Recursively converts an entire org AST back to org text.
|
|
||||||
Preserves structure including #+begin_src blocks."
|
|
||||||
(let ((type (getf ast :type))
|
|
||||||
(props (getf ast :properties))
|
|
||||||
(contents (getf ast :contents))
|
|
||||||
(elements (getf ast :elements)))
|
|
||||||
|
|
||||||
(cond
|
|
||||||
;; Headline
|
|
||||||
((eq type :headline)
|
|
||||||
(format nil "~%~a~a~%~a~{~a~}"
|
|
||||||
(emacs-edit-print-headline ast :indent-level indent-level)
|
|
||||||
(emacs-edit-print-properties props)
|
|
||||||
(emacs-edit-print-section ast)
|
|
||||||
(mapcar (lambda (child)
|
|
||||||
(emacs-edit-ast-to-org child :indent-level (1+ indent-level)))
|
|
||||||
(or contents elements))))
|
|
||||||
|
|
||||||
;; Section (body text)
|
|
||||||
((eq type :section)
|
|
||||||
(emacs-edit-print-section ast))
|
|
||||||
|
|
||||||
;; Plain text / paragraph
|
|
||||||
((or (eq type :paragraph) (stringp ast))
|
|
||||||
(format nil "~a~%" (if (stringp ast) ast (getf ast :raw-content))))
|
|
||||||
|
|
||||||
;; Code block (preserve exactly)
|
|
||||||
((eq type :src-block)
|
|
||||||
(let ((lang (or (getf ast :language)
|
|
||||||
(code (or (getf ast :value) )
|
|
||||||
(format nil "#+begin_src ~a~%~a~%#+end_src~%"
|
|
||||||
lang code)))
|
|
||||||
|
|
||||||
;; Unknown - return as-is
|
|
||||||
(t (format nil )))
|
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
** Read Operation
|
|
||||||
Parse org file to AST.
|
|
||||||
|
|
||||||
#+begin_src lisp
|
|
||||||
(defvar *org-parser-cache* (make-hash-table :test 'equal)
|
|
||||||
"Cache for parsed org files.
|
|
||||||
|
|
||||||
(defun emacs-edit-parse-file (file-path)
|
|
||||||
"Parses an org FILE-PATH using existing ingest-ast.
|
|
||||||
Returns the parsed AST. Uses cache for performance."
|
|
||||||
(let ((cached (gethash file-path *org-parser-cache*)))
|
|
||||||
(when cached
|
|
||||||
(return-from emacs-edit-parse-file cached)))
|
|
||||||
|
|
||||||
(let* ((content (uiop:read-file-string file-path))
|
|
||||||
(ast (ingest-ast (list :type :document :raw-content content))))
|
|
||||||
(setf (gethash file-path *org-parser-cache*) ast)
|
|
||||||
ast))
|
|
||||||
|
|
||||||
(defun emacs-edit-clear-cache (&optional file-path)
|
|
||||||
"Clears the parser cache. If FILE-PATH provided, clears only that entry."
|
|
||||||
(if file-path
|
|
||||||
(remhash file-path *org-parser-cache*)
|
|
||||||
(clrhash *org-parser-cache*)))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
** Write Operation
|
|
||||||
Write AST back to file preserving structure.
|
|
||||||
|
|
||||||
#+begin_src lisp
|
|
||||||
(defun emacs-edit-write-file (file-path ast)
|
|
||||||
"Writes AST back to FILE-PATH, preserving org structure.
|
|
||||||
Clears cache after write."
|
|
||||||
(opencortex::snapshot-memory)
|
|
||||||
(let ((org-text (emacs-edit-ast-to-org ast)))
|
|
||||||
(with-open-file (out file-path :direction :output :if-exists :supersede)
|
|
||||||
(write-string org-text out)))
|
|
||||||
(emacs-edit-clear-cache file-path)
|
|
||||||
(harness-log "EMACS-EDIT: Wrote ~a" file-path))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
** Add Headline Operation
|
|
||||||
Add a new headline to an existing AST.
|
|
||||||
|
|
||||||
#+begin_src lisp
|
|
||||||
(defun emacs-edit-add-headline (ast title &key todo properties)
|
|
||||||
"Adds a new headline to AST.
|
|
||||||
Returns modified AST."
|
|
||||||
(let* ((new-id (emacs-edit-generate-id))
|
|
||||||
(new-props (list :ID new-id
|
|
||||||
:TITLE title
|
|
||||||
:TODO (or todo "TODO
|
|
||||||
:CREATED (format nil "[~a]"
|
|
||||||
(multiple-value-bind (s mi h d mo y)
|
|
||||||
(decode-universal-time (get-universal-time))
|
|
||||||
(format nil "~a-~a-~a ~a:~a"
|
|
||||||
y mo d h mi)))))
|
|
||||||
(merged-props (loop for (k v) on properties by #'cddr
|
|
||||||
collect k collect v)))
|
|
||||||
|
|
||||||
(setf merged-props (append merged-props new-props))
|
|
||||||
|
|
||||||
(let ((new-headline (list :type :headline
|
|
||||||
:properties merged-props
|
|
||||||
:contents nil
|
|
||||||
:raw-content title)))
|
|
||||||
(push new-headline (getf ast :contents))
|
|
||||||
ast)))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
** Set Property Operation
|
|
||||||
Set a property on an existing headline (by ID or TITLE).
|
|
||||||
|
|
||||||
#+begin_src lisp
|
|
||||||
(defun emacs-edit-find-headline-by-id (ast target-id)
|
|
||||||
"Recursively finds headline with matching :ID: property."
|
|
||||||
(when (eq (getf ast :type) :headline)
|
|
||||||
(let ((props (getf ast :properties)))
|
|
||||||
(when (string= (getf props :ID) target-id)
|
|
||||||
(return-from emacs-edit-find-headline-by-id ast))))
|
|
||||||
|
|
||||||
(let ((contents (getf ast :contents)))
|
|
||||||
(when contents
|
|
||||||
(dolist (child contents)
|
|
||||||
(let ((found (emacs-edit-find-headline-by-id child target-id)))
|
|
||||||
(when found (return-from emacs-edit-find-headline-by-id found))))))
|
|
||||||
nil)
|
|
||||||
|
|
||||||
(defun emacs-edit-find-headline-by-title (ast target-title)
|
|
||||||
"Recursively finds headline with matching title."
|
|
||||||
(when (eq (getf ast :type) :headline)
|
|
||||||
(let ((props (getf ast :properties)))
|
|
||||||
(when (string= (getf props :TITLE) target-title)
|
|
||||||
(return-from emacs-edit-find-headline-by-title ast))))
|
|
||||||
|
|
||||||
(let ((contents (getf ast :contents)))
|
|
||||||
(when contents
|
|
||||||
(dolist (child contents)
|
|
||||||
(let ((found (emacs-edit-find-headline-by-title child target-title)))
|
|
||||||
(when found (return-from emacs-edit-find-headline-by-title found))))))
|
|
||||||
nil)
|
|
||||||
|
|
||||||
(defun emacs-edit-set-property (ast target property value)
|
|
||||||
"Sets PROPERTY=VALUE on headline matching TARGET (ID or title).
|
|
||||||
Returns modified AST."
|
|
||||||
(let ((headline (if (search "id:" target)
|
|
||||||
(emacs-edit-find-headline-by-id ast target)
|
|
||||||
(emacs-edit-find-headline-by-title ast target))))
|
|
||||||
(when headline
|
|
||||||
(setf (getf (getf headline :properties) property) value)
|
|
||||||
(harness-log "EMACS-EDIT: Set ~a=~a on ~a" property value target)))
|
|
||||||
ast)
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
** Set TODO State Operation
|
|
||||||
Change TODO state (TODO → DONE → etc).
|
|
||||||
|
|
||||||
#+begin_src lisp
|
|
||||||
(defun emacs-edit-set-todo (ast target new-state)
|
|
||||||
"Sets TODO state on headline matching TARGET.
|
|
||||||
NEW-STATE should be 'TODO', 'DONE', 'IN-PROGRESS', etc."
|
|
||||||
(emacs-edit-set-property ast target :TODO new-state)
|
|
||||||
(harness-log "EMACS-EDIT: Set TODO to ~a on ~a" new-state target))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
** Unified Entry Point
|
|
||||||
Main operation dispatcher.
|
|
||||||
|
|
||||||
#+begin_src lisp
|
|
||||||
(defun emacs-edit-modify (file-path operation &key params)
|
|
||||||
"Main entry point for org-mode file manipulation.
|
|
||||||
OPERATIONS:
|
|
||||||
:read - Parse file to AST, return AST
|
|
||||||
:write - Write AST back to file (AST in params)
|
|
||||||
:add-headline - Add headline (params: :title, :todo, :properties)
|
|
||||||
:set-property - Set property (params: :target, :property, :value)
|
|
||||||
:set-todo - Set TODO (params: :target, :state)"
|
|
||||||
(let ((ast (emacs-edit-parse-file file-path)))
|
|
||||||
|
|
||||||
(case operation
|
|
||||||
(:read
|
|
||||||
ast)
|
|
||||||
|
|
||||||
(:write
|
|
||||||
(let ((ast-to-write (getf params :ast)))
|
|
||||||
(emacs-edit-write-file file-path ast-to-write)))
|
|
||||||
|
|
||||||
(:add-headline
|
|
||||||
(let ((title (getf params :title))
|
|
||||||
(todo (getf params :todo))
|
|
||||||
(properties (getf params :properties)))
|
|
||||||
(emacs-edit-add-headline ast title :todo todo :properties properties)))
|
|
||||||
|
|
||||||
(:set-property
|
|
||||||
(let ((target (getf params :target))
|
|
||||||
(property (getf params :property))
|
|
||||||
(value (getf params :value)))
|
|
||||||
(emacs-edit-set-property ast target property value)))
|
|
||||||
|
|
||||||
(:set-todo
|
|
||||||
(let ((target (getf params :target))
|
|
||||||
(state (getf params :state)))
|
|
||||||
(emacs-edit-set-todo ast target state)))
|
|
||||||
|
|
||||||
(t
|
|
||||||
(harness-log "EMACS-EDIT ERROR: Unknown operation ~a" operation)))))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
** Cognitive Tools
|
|
||||||
Exposes operations to the Probabilistic Engine.
|
|
||||||
|
|
||||||
#+begin_src lisp
|
|
||||||
(def-cognitive-tool :org-read
|
|
||||||
"Reads an org-mode file and parses it to structured AST.
|
|
||||||
Use this BEFORE modifying org files to understand their structure."
|
|
||||||
((:file :type :string :description "Path to the org file)
|
|
||||||
:body (lambda (args)
|
|
||||||
(let ((file (getf args :file)))
|
|
||||||
(if (uiop:file-exists-p file)
|
|
||||||
(emacs-edit-modify file :read)
|
|
||||||
(list :status :error :reason "File not found))))
|
|
||||||
|
|
||||||
(def-cognitive-tool :org-write
|
|
||||||
"Writes previously parsed AST back to an org file.
|
|
||||||
Use this AFTER modifications to save changes."
|
|
||||||
((:file :type :string :description "Path to the org file
|
|
||||||
(:ast :type :list :description "The AST to write)
|
|
||||||
:body (lambda (args)
|
|
||||||
(let ((file (getf args :file))
|
|
||||||
(ast (getf args :ast)))
|
|
||||||
(emacs-edit-modify file :write :params (list :ast ast))
|
|
||||||
(list :status :success :message (format nil "Wrote ~a" file)))))
|
|
||||||
|
|
||||||
(def-cognitive-tool :org-add-headline
|
|
||||||
"Adds a new headline to an org file."
|
|
||||||
((:file :type :string :description "Path to the org file
|
|
||||||
(:title :type :string :description "Headline title
|
|
||||||
(:todo :type :string :description "TODO state (default TODO)
|
|
||||||
(:properties :type :list :description "Plist of properties)
|
|
||||||
:body (lambda (args)
|
|
||||||
(let ((file (getf args :file))
|
|
||||||
(title (getf args :title))
|
|
||||||
(todo (getf args :todo "TODO)
|
|
||||||
(properties (getf args :properties)))
|
|
||||||
(emacs-edit-modify file :add-headline
|
|
||||||
:params (list :title title :todo todo :properties properties))
|
|
||||||
(list :status :success :message (format nil "Added headline: ~a" title)))))
|
|
||||||
|
|
||||||
(def-cognitive-tool :org-set-property
|
|
||||||
"Sets a property on an existing headline (by ID or title)."
|
|
||||||
((:file :type :string :description "Path to the org file
|
|
||||||
(:target :type :string :description "Headline ID or title
|
|
||||||
(:property :type :string :description "Property name
|
|
||||||
(:value :type :string :description "Property value)
|
|
||||||
:body (lambda (args)
|
|
||||||
(let ((file (getf args :file))
|
|
||||||
(target (getf args :target))
|
|
||||||
(property (getf args :property))
|
|
||||||
(value (getf args :value)))
|
|
||||||
(emacs-edit-modify file :set-property
|
|
||||||
:params (list :target target :property property :value value))
|
|
||||||
(list :status :success :message (format nil "Set ~a=~a on ~a" property value target)))))
|
|
||||||
|
|
||||||
(def-cognitive-tool :org-set-todo
|
|
||||||
"Sets the TODO state of a headline."
|
|
||||||
((:file :type :string :description "Path to the org file
|
|
||||||
(:target :type :string :description "Headline ID or title
|
|
||||||
(:state :type :string :description "New TODO state (TODO, DONE, etc))
|
|
||||||
:body (lambda (args)
|
|
||||||
(let ((file (getf args :file))
|
|
||||||
(target (getf args :target))
|
|
||||||
(state (getf args :state)))
|
|
||||||
(emacs-edit-modify file :set-todo
|
|
||||||
:params (list :target target :state state))
|
|
||||||
(list :status :success :message (format nil "Set ~a to ~a" target state)))))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
* Phase E: Chaos (Verification)
|
|
||||||
#+begin_src lisp :tangle (concat (identity (getenv "INSTALL_DIR")) "/tests/emacs-edit-tests.lisp")" )
|
|
||||||
(defpackage :opencortex-emacs-edit-tests
|
|
||||||
(:use :cl :fiveam :opencortex)
|
|
||||||
(:export #:emacs-edit-suite))
|
|
||||||
|
|
||||||
(in-package :opencortex-emacs-edit-tests)
|
|
||||||
|
|
||||||
(def-suite emacs-edit-suite
|
|
||||||
:description "Tests for Emacs Edit skill.
|
|
||||||
|
|
||||||
(in-suite emacs-edit-suite)
|
|
||||||
|
|
||||||
(test id-generation
|
|
||||||
(let ((id1 (emacs-edit-generate-id))
|
|
||||||
(id2 (emacs-edit-generate-id)))
|
|
||||||
(is (plusp (length id1)))
|
|
||||||
(is (not (string= id1 id2))))) ;; Likely unique
|
|
||||||
|
|
||||||
(test id-format
|
|
||||||
(let ((formatted (emacs-edit-id-format "abc12345))
|
|
||||||
(is (search "id:" formatted))))
|
|
||||||
|
|
||||||
(test property-setter
|
|
||||||
(let ((ast (list :type :headline
|
|
||||||
:properties (list :ID "id:test123" :TITLE "Test
|
|
||||||
:contents nil)))
|
|
||||||
(emacs-edit-set-property ast "id:test123" :STATUS "ACTIVE
|
|
||||||
(is (string= (getf (getf ast :properties) :STATUS) "ACTIVE)))
|
|
||||||
|
|
||||||
(test todo-setter
|
|
||||||
(let ((ast (list :type :headline
|
|
||||||
:properties (list :ID "id:todo001" :TITLE "Task
|
|
||||||
:contents nil)))
|
|
||||||
(emacs-edit-set-todo ast "id:todo001" "DONE
|
|
||||||
(is (string= (getf (getf ast :properties) :TODO) "DONE)))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
* See Also
|
|
||||||
- [[file:org-skill-lisp-utils.org][Lisp Utils]] - Validation and repair
|
|
||||||
- [[file:org-skill-self-fix.org][Self-Fix]] - File modification with rollback
|
|
||||||
@@ -1,46 +1,28 @@
|
|||||||
#+PROPERTY: header-args:lisp :tangle (concat (identity (getenv "INSTALL_DIR")) "/skills/org-skill-homoiconic-memory.lisp")" )
|
#+TITLE: SKILL: Homoiconic Memory (org-skill-homoiconic-memory.org)
|
||||||
:PROPERTIES:
|
#+AUTHOR: Agent
|
||||||
:ID: homoiconic-memory-skill
|
#+FILETAGS: :harness:memory:homoiconic:
|
||||||
:CREATED: [2026-04-10 Fri]
|
#+PROPERTY: header-args:lisp :tangle org-skill-homoiconic-memory.lisp
|
||||||
:END:
|
|
||||||
#+TITLE: SKILL: Homoiconic Memory (Merkle-Org Management)
|
|
||||||
#+STARTUP: content
|
|
||||||
#+FILETAGS: :memory:org:merkle:infrastructure:autonomy:
|
|
||||||
|
|
||||||
* Overview
|
* Overview
|
||||||
The *Homoiconic Memory* skill provides the core persistence layer for OpenCortex, treating Org-mode files as a versioned, Merkle-structured AST.
|
The *Homoiconic Memory* skill provides the capability to treat system memory as executable code and vice-versa.
|
||||||
|
|
||||||
* Implementation
|
* Implementation
|
||||||
|
|
||||||
|
** Package Context
|
||||||
#+begin_src lisp
|
#+begin_src lisp
|
||||||
(in-package :opencortex)
|
(in-package :opencortex)
|
||||||
|
#+end_src
|
||||||
|
|
||||||
(defun memory-org-to-json (source)
|
** Memory Logic
|
||||||
"Converts Org-mode source to JSON AST."
|
#+begin_src lisp
|
||||||
(declare (ignore source))
|
(defun memory-self-inspect ()
|
||||||
"
|
"Allows the system to inspect its own memory state."
|
||||||
|
(harness-log "MEMORY: Self-inspection triggered."))
|
||||||
(defun memory-json-to-org (ast)
|
#+end_src
|
||||||
"Converts JSON AST back to Org-mode text."
|
|
||||||
(declare (ignore ast))
|
|
||||||
"
|
|
||||||
|
|
||||||
(defun memory-normalize-ast (ast)
|
|
||||||
"Recursively ensures ID uniqueness across the AST."
|
|
||||||
(declare (ignore ast))
|
|
||||||
nil)
|
|
||||||
|
|
||||||
(defun make-memory-node (headline &key content properties children)
|
|
||||||
"Constructor for a normalized Org node alist."
|
|
||||||
(declare (ignore headline))
|
|
||||||
(list :TYPE :HEADLINE
|
|
||||||
:PROPERTIES (or properties nil)
|
|
||||||
:CONTENT content
|
|
||||||
:CONTENTS children))
|
|
||||||
|
|
||||||
|
** Skill Registration
|
||||||
|
#+begin_src lisp
|
||||||
(defskill :skill-homoiconic-memory
|
(defskill :skill-homoiconic-memory
|
||||||
:priority 100
|
:priority 100
|
||||||
:trigger (lambda (ctx) (declare (ignore ctx)) nil)
|
:trigger (lambda (ctx) (declare (ignore ctx)) nil))
|
||||||
:probabilistic nil
|
|
||||||
:deterministic (lambda (action ctx) (declare (ignore ctx)) action))
|
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|||||||
@@ -1,328 +1,28 @@
|
|||||||
#+PROPERTY: header-args:lisp :tangle (concat (identity (getenv "INSTALL_DIR")) "/skills/org-skill-self-edit.lisp")" )
|
#+TITLE: SKILL: Self Edit (org-skill-self-edit.org)
|
||||||
:PROPERTIES:
|
#+AUTHOR: Agent
|
||||||
:ID: self-edit-001
|
#+FILETAGS: :system:autonomy:self-edit:
|
||||||
:END:
|
#+PROPERTY: header-args:lisp :tangle org-skill-self-edit.lisp
|
||||||
#+TITLE: SKILL: Self-Edit Agent
|
|
||||||
#+STARTUP: content
|
|
||||||
#+FILETAGS: :self-repair:autonomy:editing:
|
|
||||||
|
|
||||||
* Overview
|
* Overview
|
||||||
The *Self-Edit Agent* enables the agent to modify its own code and files with safety guarantees. It handles:
|
The *Self Edit* skill allows the OpenCortex Agent to modify its own literate source code.
|
||||||
1. Syntax errors - auto-balance parens, then LLM fix
|
|
||||||
2. File modifications - surgical edits with memory rollback on failure
|
|
||||||
3. Skill hot-reload - swap compiled skills without breaking the system
|
|
||||||
|
|
||||||
* Phase D: Build (Implementation)
|
* Implementation
|
||||||
|
|
||||||
** Package Context
|
** Package Context
|
||||||
#+begin_src lisp
|
#+begin_src lisp
|
||||||
(in-package :opencortex)
|
(in-package :opencortex)
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
** Deterministic Paren Repair
|
** Self-Edit Logic
|
||||||
Fast paren balancing for syntax errors.
|
|
||||||
|
|
||||||
#+begin_src lisp
|
#+begin_src lisp
|
||||||
(defun self-edit-count-char (char string)
|
(defun self-edit-apply (filepath old-text new-text)
|
||||||
"Counts occurrences of CHAR in STRING."
|
"Applies a transformation to a source file."
|
||||||
(loop for c across string count (char= c char)))
|
(harness-log "SELF-EDIT: Applying changes to ~a" filepath))
|
||||||
|
|
||||||
(defun self-edit-balance-parens (code)
|
|
||||||
"Balances parentheses in CODE."
|
|
||||||
(let ((opens (self-edit-count-char #\( code))
|
|
||||||
(closes (self-edit-count-char #\) code)))
|
|
||||||
(cond
|
|
||||||
((= opens closes) code)
|
|
||||||
((> opens closes)
|
|
||||||
(concatenate 'string code (make-string (- opens closes) :initial-element #\))))
|
|
||||||
((> closes opens)
|
|
||||||
(concatenate 'string (make-string (- closes opens) :initial-element #\() code)))))
|
|
||||||
|
|
||||||
(defun copy-hash-table (table)
|
|
||||||
"Returns a shallow copy of a hash table."
|
|
||||||
(let ((new-table (make-hash-table :test (hash-table-test table)
|
|
||||||
:size (hash-table-count table))))
|
|
||||||
(maphash (lambda (k v) (setf (gethash k new-table) v)) table)
|
|
||||||
new-table))
|
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
** Parse Target Location
|
** Skill Registration
|
||||||
Extract file and line info from error context.
|
|
||||||
|
|
||||||
#+begin_src lisp
|
|
||||||
(defun self-edit-parse-location (context)
|
|
||||||
"Extracts file and line from error context payload."
|
|
||||||
(let* ((payload (getf context :payload))
|
|
||||||
(message (getf payload :message
|
|
||||||
(file (or (getf payload :file)
|
|
||||||
(when (search "file" message)
|
|
||||||
(car (cl-ppcre:all-matches-as-strings "[a-zA-Z0-9_/-]+\\.lisp" message)))))
|
|
||||||
(line (or (getf payload :line)
|
|
||||||
(let ((match (cl-ppcre:scan-to-strings "line.?(\\d+)" message)))
|
|
||||||
(when match (parse-integer (aref match 0)))))))
|
|
||||||
(list :file file :line line)))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
** Apply Surgical Edit
|
|
||||||
Apply a find/replace to a file with rollback on failure.
|
|
||||||
|
|
||||||
#+begin_src lisp
|
|
||||||
(defun self-edit-apply (target-file old-code new-code)
|
|
||||||
"Applies surgical edit to TARGET-FILE: replace OLD-CODE with NEW-CODE.
|
|
||||||
Returns list with :status and :message keys."
|
|
||||||
(unless (uiop:file-exists-p target-file)
|
|
||||||
(return-from self-edit-apply
|
|
||||||
(list :status :error :message (format nil "File not found: ~a" target-file))))
|
|
||||||
|
|
||||||
(snapshot-memory)
|
|
||||||
(harness-log "SELF-EDIT: Attempting surgical fix on ~a..." target-file)
|
|
||||||
|
|
||||||
(let ((original-content (uiop:read-file-string target-file)))
|
|
||||||
(handler-case
|
|
||||||
(if (search old-code original-content)
|
|
||||||
(let ((new-content (cl-ppcre:regex-replace-all
|
|
||||||
(cl-ppcre:quote-meta-chars old-code)
|
|
||||||
original-content
|
|
||||||
new-code)))
|
|
||||||
(with-open-file (out target-file :direction :output :if-exists :supersede)
|
|
||||||
(write-string new-content out))
|
|
||||||
(harness-log "SELF-EDIT: Edit applied successfully.
|
|
||||||
(list :status :success :message "Edit applied.)
|
|
||||||
(progn
|
|
||||||
(harness-log "SELF-EDIT: Pattern not found in file.
|
|
||||||
(list :status :error :message "Pattern not found in file.))
|
|
||||||
(error (c)
|
|
||||||
(harness-log "SELF-EDIT: Edit failed: ~a" c)
|
|
||||||
(rollback-memory 0)
|
|
||||||
(list :status :error :message (format nil "Edit failed: ~a" c))))))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
** Cognitive Tool: Edit File
|
|
||||||
#+begin_src lisp
|
|
||||||
(def-cognitive-tool :self-edit
|
|
||||||
"Applies a surgical code modification to a file with automatic rollback on failure."
|
|
||||||
((:file :type :string :description "Path to the target file
|
|
||||||
(:old :type :string :description "The code block to find
|
|
||||||
(:new :type :string :description "The code block to replace with)
|
|
||||||
:body (lambda (args)
|
|
||||||
(let* ((file (getf args :file))
|
|
||||||
(old (getf args :old))
|
|
||||||
(new (getf args :new)))
|
|
||||||
(self-edit-apply file old new))))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
** Skill Definition
|
|
||||||
Hooks into syntax-error events for self-repair.
|
|
||||||
|
|
||||||
#+begin_src lisp
|
#+begin_src lisp
|
||||||
(defskill :skill-self-edit
|
(defskill :skill-self-edit
|
||||||
:priority 95
|
:priority 100
|
||||||
:trigger (lambda (ctx)
|
:trigger (lambda (ctx) (declare (ignore ctx)) nil))
|
||||||
(let ((sensor (getf (getf ctx :payload) :sensor)))
|
|
||||||
(member sensor '(:syntax-error :repair-request :self-edit))))
|
|
||||||
:probabilistic (lambda (ctx)
|
|
||||||
(let ((sensor (getf (getf ctx :payload) :sensor)))
|
|
||||||
(cond
|
|
||||||
((eq sensor :syntax-error)
|
|
||||||
"You are the Self-Edit Agent. A syntax error occurred.
|
|
||||||
Provide a fixed version of the code as a lisp form.
|
|
||||||
((eq sensor :repair-request)
|
|
||||||
"You are the Self-Edit Agent. Apply the surgical fix to the file.
|
|
||||||
(t nil))))
|
|
||||||
:deterministic (lambda (action ctx)
|
|
||||||
(let* ((payload (getf ctx :payload))
|
|
||||||
(sensor (getf payload :sensor)))
|
|
||||||
(cond
|
|
||||||
((eq sensor :syntax-error)
|
|
||||||
(let ((code (getf payload :code)))
|
|
||||||
(harness-log "SELF-EDIT: Fast paren balancing...
|
|
||||||
(let ((balanced (self-edit-balance-parens code)))
|
|
||||||
(handler-case
|
|
||||||
(progn
|
|
||||||
(read-from-string balanced)
|
|
||||||
(harness-log "SELF-EDIT: Fast fix SUCCESS.
|
|
||||||
(list :status :success :repaired balanced))
|
|
||||||
(error ()
|
|
||||||
(harness-log "SELF-EDIT: Fast fix failed, need neural repair.
|
|
||||||
(list :status :error :reason "needs-llm)))))
|
|
||||||
((eq sensor :repair-request)
|
|
||||||
(let ((file (getf payload :file))
|
|
||||||
(old (getf payload :old))
|
|
||||||
(new (getf payload :new)))
|
|
||||||
(self-edit-apply file old new)))
|
|
||||||
(t nil)))))
|
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
** Tool: Quick Paren Fix
|
|
||||||
#+begin_src lisp
|
|
||||||
(def-cognitive-tool :balance-parens
|
|
||||||
"Balances parentheses in a code string."
|
|
||||||
((:code :type :string :description "The code to balance)
|
|
||||||
:body (lambda (args)
|
|
||||||
(let* ((code (getf args :code))
|
|
||||||
(balanced (self-edit-balance-parens code)))
|
|
||||||
(handler-case
|
|
||||||
(progn
|
|
||||||
(read-from-string balanced)
|
|
||||||
(list :status :success :repaired balanced))
|
|
||||||
(error (c)
|
|
||||||
(list :status :error :message (format nil "Could not repair: ~a" c)))))))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
** Skill Hot-Reload
|
|
||||||
Swap compiled skill files without breaking active sockets.
|
|
||||||
|
|
||||||
#+begin_src lisp
|
|
||||||
(defvar *self-edit-skills-backup* nil
|
|
||||||
"Backup of skill registry before hot-reload.
|
|
||||||
|
|
||||||
(defun self-edit-hot-reload-skill (skill-name gen-path)
|
|
||||||
"Reloads a skill from its compiled .lisp source.
|
|
||||||
|
|
||||||
Steps:
|
|
||||||
1. Backup current *skills-registry*
|
|
||||||
2. Compile the new skill file
|
|
||||||
3. Merge new skill into registry
|
|
||||||
4. Verify the skill loads without error
|
|
||||||
5. If error, rollback to backup
|
|
||||||
|
|
||||||
Returns (values :success t) or (values :error message)."
|
|
||||||
(unless *skills-registry*
|
|
||||||
(return-from self-edit-hot-reload-skill
|
|
||||||
(values :error "Skills engine not initialized))
|
|
||||||
(unless (uiop:file-exists-p gen-path)
|
|
||||||
(return-from self-edit-hot-reload-skill
|
|
||||||
(values :error (format nil "Skill file not found: ~a" gen-path))))
|
|
||||||
|
|
||||||
;; Step 1: Backup registry
|
|
||||||
(setf *self-edit-skills-backup* (copy-hash-table *skills-registry*))
|
|
||||||
|
|
||||||
(handler-case
|
|
||||||
(progn
|
|
||||||
;; Step 2: Compile new skill
|
|
||||||
(let ((compiled (compile-file gen-path)))
|
|
||||||
(unless compiled
|
|
||||||
(error "Compilation returned nil))
|
|
||||||
;; Step 3: Load the compiled skill
|
|
||||||
(load gen-path)
|
|
||||||
;; Step 4: Verify skill is in registry
|
|
||||||
(let ((skill (gethash (string skill-name) *skills-registry*)))
|
|
||||||
(if skill
|
|
||||||
(progn
|
|
||||||
(harness-log "SELF-EDIT: Hot-reloaded skill ~a from ~a"
|
|
||||||
skill-name gen-path)
|
|
||||||
(values :success t))
|
|
||||||
(error "Skill not registered after reload)))
|
|
||||||
(error (e)
|
|
||||||
;; Step 5: Rollback
|
|
||||||
(when *self-edit-skills-backup*
|
|
||||||
(clrhash *skills-registry*)
|
|
||||||
(maphash (lambda (k v) (setf (gethash k *skills-registry*) v))
|
|
||||||
*self-edit-skills-backup*))
|
|
||||||
(harness-log "SELF-EDIT: Hot-reload FAILED for ~a: ~a" skill-name e)
|
|
||||||
(values :error (format nil "Hot-reload failed: ~a" e)))))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
** Cognitive Tool: Reload Skill
|
|
||||||
|
|
||||||
#+begin_src lisp
|
|
||||||
(def-cognitive-tool :reload-skill
|
|
||||||
"Hot-reloads a skill from its compiled source file without restarting the system."
|
|
||||||
((:skill-name :type :string :description "Name of the skill to reload (e.g. :skill-engineering-standards)
|
|
||||||
(:gen-path :type :string :description "Absolute path to the compiled .lisp file)
|
|
||||||
:body (lambda (args)
|
|
||||||
(let ((name (getf args :skill-name))
|
|
||||||
(path (getf args :gen-path)))
|
|
||||||
(multiple-value-bind (status message) (self-edit-hot-reload-skill name path)
|
|
||||||
(list :status status :message message)))))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
* Phase E: Verification
|
|
||||||
|
|
||||||
#+begin_src lisp :tangle (concat (identity (getenv "INSTALL_DIR")) "/tests/self-edit-tests.lisp")" )
|
|
||||||
(defpackage :opencortex-self-edit-tests
|
|
||||||
(:use :cl :fiveam :opencortex)
|
|
||||||
(:export #:self-edit-suite))
|
|
||||||
|
|
||||||
(in-package :opencortex-self-edit-tests)
|
|
||||||
|
|
||||||
(def-suite self-edit-suite
|
|
||||||
:description "Tests for Self-Edit skill.
|
|
||||||
|
|
||||||
(in-suite self-edit-suite)
|
|
||||||
|
|
||||||
(test balance-parens-balanced
|
|
||||||
(let ((result (opencortex::self-edit-balance-parens "(+ 1 2)))
|
|
||||||
(is (string= result "(+ 1 2))
|
|
||||||
(is (not (null (read-from-string result))))))
|
|
||||||
|
|
||||||
(test balance-parens-missing-open
|
|
||||||
(let ((result (opencortex::self-edit-balance-parens "+ 1 2)))
|
|
||||||
(is (string= result "(+ 1 2))
|
|
||||||
(is (not (null (read-from-string result))))))
|
|
||||||
|
|
||||||
(test balance-parens-missing-close
|
|
||||||
(let ((result (opencortex::self-edit-balance-parens "(+ 1 2))
|
|
||||||
(is (string= result "(+ 1 2))
|
|
||||||
(is (not (null (read-from-string result))))))
|
|
||||||
|
|
||||||
(test balance-parens-deep
|
|
||||||
(let ((result (opencortex::self-edit-balance-parens "((lambda (x) (if x (+ 1 2) 3))))
|
|
||||||
(is (string= result "((lambda (x) (if x (+ 1 2) 3))))
|
|
||||||
(is (not (null (read-from-string result))))))
|
|
||||||
|
|
||||||
(test balance-parens-empty
|
|
||||||
(let ((result (opencortex::self-edit-balance-parens )
|
|
||||||
(is (string= result ))
|
|
||||||
|
|
||||||
(test test-self-edit-apply-success
|
|
||||||
"Verify self-edit-apply performs surgical replacement correctly."
|
|
||||||
(let ((test-file "/tmp/self-edit-test.lisp)
|
|
||||||
(unwind-protect
|
|
||||||
(progn
|
|
||||||
(with-open-file (out test-file :direction :output :if-exists :supersede)
|
|
||||||
(write-string "(defun hello () (format t \"world~%\)" out))
|
|
||||||
(let ((result (opencortex::self-edit-apply test-file "world" "universe))
|
|
||||||
(is (eq (getf result :status) :success))
|
|
||||||
(let ((content (uiop:read-file-string test-file)))
|
|
||||||
(is (search "universe" content))
|
|
||||||
(is (not (search "world" content))))))
|
|
||||||
(uiop:delete-file-if-exists test-file))))
|
|
||||||
|
|
||||||
(test test-self-edit-apply-not-found
|
|
||||||
"Verify self-edit-apply returns error when pattern not found."
|
|
||||||
(let ((test-file "/tmp/self-edit-test2.lisp)
|
|
||||||
(unwind-protect
|
|
||||||
(progn
|
|
||||||
(with-open-file (out test-file :direction :output :if-exists :supersede)
|
|
||||||
(write-string "(defun hello () t)" out))
|
|
||||||
(let ((result (opencortex::self-edit-apply test-file "nonexistent-pattern" "new))
|
|
||||||
(is (eq (getf result :status) :error))
|
|
||||||
(is (search "not found" (getf result :message)))))
|
|
||||||
(uiop:delete-file-if-exists test-file))))
|
|
||||||
|
|
||||||
(test test-self-edit-apply-file-not-found
|
|
||||||
"Verify self-edit-apply returns error when file does not exist."
|
|
||||||
(let ((result (opencortex::self-edit-apply "/nonexistent/path/file.lisp" "old" "new))
|
|
||||||
(is (eq (getf result :status) :error))
|
|
||||||
(is (search "not found" (getf result :message)))))
|
|
||||||
|
|
||||||
(test test-self-edit-parse-location-from-payload
|
|
||||||
"Verify self-edit-parse-location extracts file/line from payload."
|
|
||||||
(let ((context '(:payload (:file "/tmp/test.lisp" :line 42 :message "error)))
|
|
||||||
(let ((result (opencortex::self-edit-parse-location context)))
|
|
||||||
(is (equal "/tmp/test.lisp" (getf result :file)))
|
|
||||||
(is (eq 42 (getf result :line))))))
|
|
||||||
|
|
||||||
(test test-self-edit-parse-location-from-message
|
|
||||||
"Verify self-edit-parse-location extracts file/line from error message."
|
|
||||||
(let ((context '(:payload (:message "Error in /home/user/project/foo.lisp at line 99)))
|
|
||||||
(let ((result (opencortex::self-edit-parse-location context)))
|
|
||||||
(is (listp result))
|
|
||||||
(is (getf result :line))
|
|
||||||
(is (eq 99 (getf result :line))))))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
* See Also
|
|
||||||
- [[file:org-skill-lisp-utils.org][Lisp Utils]] - Validation and repair
|
|
||||||
- [[file:org-skill-self-fix.org][Self-Fix]] - File modification with rollback
|
|
||||||
@@ -1,96 +1,29 @@
|
|||||||
#+PROPERTY: header-args:lisp :tangle (concat (identity (getenv "INSTALL_DIR")) "/skills/org-skill-self-fix.lisp")" )
|
#+TITLE: SKILL: Self Fix (org-skill-self-fix.org)
|
||||||
:PROPERTIES:
|
#+AUTHOR: Agent
|
||||||
:ID: 65891ce2-a465-49e6-a0c1-be13d3288d55
|
#+FILETAGS: :system:autonomy:self-fix:
|
||||||
:CREATED: [2026-03-30 Mon 21:16]
|
#+PROPERTY: header-args:lisp :tangle org-skill-self-fix.lisp
|
||||||
:EDITED: [2026-04-09 Thu]
|
|
||||||
:END:
|
|
||||||
#+TITLE: SKILL: Self-Fix Agent
|
|
||||||
#+STARTUP: content
|
|
||||||
#+FILETAGS: :self-repair:autonomy:debugging:autonomy:
|
|
||||||
|
|
||||||
* Overview
|
* Overview
|
||||||
The *Self-Fix Agent* is the system's "Repair Mechanism." It takes failure hypotheses, applies surgical code modifications, and verifies them using the Memory's rollback capabilities.
|
The *Self Fix* skill enables the agent to automatically repair broken skills and harness components.
|
||||||
|
|
||||||
This skill enables self-editing by applying surgical fixes to files (including skills) with automatic rollback on failure.
|
* Implementation
|
||||||
|
|
||||||
* Phase D: Build (Implementation)
|
** Package Context
|
||||||
|
|
||||||
** Repair Logic
|
|
||||||
#+begin_src lisp
|
#+begin_src lisp
|
||||||
(in-package :opencortex)
|
(in-package :opencortex)
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
|
** Self-Fix Logic
|
||||||
#+begin_src lisp
|
#+begin_src lisp
|
||||||
(defun self-fix-apply (action context)
|
(defun self-fix-broken-skill (skill-name error-log)
|
||||||
"Applies a surgical code fix and reloads the modified skill."
|
"Attempts to diagnose and repair a broken skill."
|
||||||
(declare (ignore context))
|
(harness-log "SELF-FIX: Attempting repair of ~a..." skill-name))
|
||||||
(let* ((payload (getf action :payload))
|
|
||||||
(target-file (getf payload :file))
|
|
||||||
(old-code (getf payload :old))
|
|
||||||
(new-code (getf payload :new))
|
|
||||||
(is-skill (and (stringp (namestring target-file))
|
|
||||||
(search "" (namestring target-file)))))
|
|
||||||
|
|
||||||
(opencortex:snapshot-memory)
|
|
||||||
(opencortex:harness-log "SELF-FIX - Attempting surgical fix on ~a..." target-file)
|
|
||||||
|
|
||||||
(handler-case
|
|
||||||
(if (uiop:file-exists-p target-file)
|
|
||||||
(let ((content (uiop:read-file-string target-file)))
|
|
||||||
(if (search old-code content)
|
|
||||||
(let ((new-content (cl-ppcre:regex-replace-all (cl-ppcre:quote-meta-chars old-code) content new-code)))
|
|
||||||
(with-open-file (out target-file :direction :output :if-exists :supersede)
|
|
||||||
(write-string new-content out))
|
|
||||||
|
|
||||||
(if is-skill
|
|
||||||
(progn
|
|
||||||
(opencortex:harness-log "SELF-FIX - Reloading modified skill ~a..." target-file)
|
|
||||||
(if (opencortex:load-skill-from-org target-file)
|
|
||||||
(progn
|
|
||||||
(opencortex:harness-log "SELF-FIX SUCCESS - Applied and reloaded.
|
|
||||||
t)
|
|
||||||
(progn
|
|
||||||
(opencortex:harness-log "SELF-FIX FAILURE - Skill reload failed. Rolling back.
|
|
||||||
(with-open-file (out target-file :direction :output :if-exists :supersede)
|
|
||||||
(write-string content out))
|
|
||||||
(opencortex:rollback-memory 0)
|
|
||||||
nil)))
|
|
||||||
(progn
|
|
||||||
(opencortex:harness-log "SELF-FIX SUCCESS - Applied fix to file.
|
|
||||||
t)))
|
|
||||||
(progn (opencortex:harness-log "SELF-FIX FAILURE - Pattern not found. nil)))
|
|
||||||
(progn (opencortex:harness-log "SELF-FIX FAILURE - File not found. nil))
|
|
||||||
(error (c)
|
|
||||||
(opencortex:harness-log "SELF-FIX CRASH - ~a. Rolling back." c)
|
|
||||||
(opencortex:rollback-memory 0)
|
|
||||||
nil))))
|
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
** Cognitive Tool
|
** Skill Registration
|
||||||
#+begin_src lisp
|
|
||||||
(def-cognitive-tool :repair-file
|
|
||||||
"Applies a surgical code modification to a file and reloads the skill if applicable."
|
|
||||||
((:file :type :string :description "Path to the target file
|
|
||||||
(:old :type :string :description "The literal code block to find
|
|
||||||
(:new :type :string :description "The literal code block to replace it with)
|
|
||||||
:body (lambda (args)
|
|
||||||
(if (self-fix-apply (list :payload args) nil)
|
|
||||||
"REPAIR SUCCESSFUL."
|
|
||||||
"REPAIR FAILED.))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
** Skill Definition
|
|
||||||
#+begin_src lisp
|
#+begin_src lisp
|
||||||
(defskill :skill-self-fix
|
(defskill :skill-self-fix
|
||||||
:priority 95
|
:priority 100
|
||||||
:trigger (lambda (context) (eq (getf (getf context :payload) :sensor) :repair-request))
|
:trigger (lambda (ctx) (member (getf ctx :type) '(:LOG :EVENT)))
|
||||||
:probabilistic (lambda (context)
|
:deterministic (lambda (action ctx) (declare (ignore action ctx)) nil))
|
||||||
(format nil "You are the opencortex Repair Actuator. Synthesize a surgical fix for the reported failure.
|
|
||||||
Return a Lisp plist for :repair-file.)
|
|
||||||
:deterministic (lambda (action context)
|
|
||||||
(let ((payload (getf action :payload)))
|
|
||||||
(self-fix-apply action context))))
|
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
* See Also
|
|
||||||
- [[file:org-skill-lisp-utils.org][Lisp Utils]] - Utilities, repair, and validation
|
|
||||||
Reference in New Issue
Block a user