Tangle emacs-edit and lisp-utils skills, update package exports
- Add vector search exports (get-embedding, cosine-similarity, semantic-search) - Add tool permissions exports to package.lisp - Add tool-permissions-tests to asdf test system - Tangled org-skill-emacs-edit.org and org-skill-lisp-utils.org - Fixed emacs-edit-tests.lisp (was missing closing paren) - Now 77/77 tests passing
This commit is contained in:
281
library/gen/org-skill-emacs-edit.lisp
Normal file
281
library/gen/org-skill-emacs-edit.lisp
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
(in-package :opencortex)
|
||||||
|
|
||||||
|
(defun emacs-edit-generate-id ()
|
||||||
|
"Generates a unique ID for org-mode headlines.
|
||||||
|
Format: 8-char hex + timestamp for uniqueness."
|
||||||
|
(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)
|
||||||
|
"Formats ID for org-mode (e.g., 'abc12345')."
|
||||||
|
(if (search "id:" id)
|
||||||
|
id
|
||||||
|
(format nil "id:~a" id)))
|
||||||
|
|
||||||
|
(defun emacs-edit-print-headline (ast &key indent-level)
|
||||||
|
"Converts a HEADLINE AST node to org text.
|
||||||
|
INDENT-LEVEL is number of leading asterisks."
|
||||||
|
(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 "")))))
|
||||||
|
|
||||||
|
(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*)))
|
||||||
|
|
||||||
|
(defun emacs-edit-write-file (file-path ast)
|
||||||
|
"Writes AST back to FILE-PATH, preserving org structure.
|
||||||
|
Clears cache after write."
|
||||||
|
(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))
|
||||||
|
|
||||||
|
(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)))
|
||||||
|
|
||||||
|
(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)
|
||||||
|
|
||||||
|
(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))
|
||||||
|
|
||||||
|
(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)))))
|
||||||
|
|
||||||
|
(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)))))
|
||||||
289
library/gen/org-skill-lisp-utils.lisp
Normal file
289
library/gen/org-skill-lisp-utils.lisp
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
(in-package :opencortex)
|
||||||
|
|
||||||
|
(defun count-char (char string)
|
||||||
|
"Counts occurrences of CHAR in STRING.
|
||||||
|
Returns an integer count."
|
||||||
|
(let ((count 0))
|
||||||
|
(loop for c across string
|
||||||
|
when (char= c char)
|
||||||
|
do (incf count))
|
||||||
|
count))
|
||||||
|
|
||||||
|
(defun deterministic-repair (code)
|
||||||
|
"Attempts instant fixes on broken Lisp code (e.g., balancing parens).
|
||||||
|
Returns the fixed code string."
|
||||||
|
(let* ((open-parens (count-char #\( code))
|
||||||
|
(close-parens (count-char #\) code))
|
||||||
|
(diff (- open-parens close-parens)))
|
||||||
|
(if (> diff 0)
|
||||||
|
(concatenate 'string code (make-string diff :initial-element #\)))
|
||||||
|
code)))
|
||||||
|
|
||||||
|
(defun neural-repair (code error-message)
|
||||||
|
"Uses the Probabilistic Engine to deeply repair the syntax structure.
|
||||||
|
Returns the fixed code string."
|
||||||
|
(let ((prompt (format nil "The following Lisp code failed to parse.
|
||||||
|
ERROR: ~a
|
||||||
|
CODE: ~a
|
||||||
|
MANDATE: Output EXACTLY ONE valid Common Lisp list. Do not explain. Do not use markdown blocks."
|
||||||
|
error-message code))
|
||||||
|
(system-prompt "You are a Lisp Syntax Repair Actuator. Return only valid, balanced Lisp code."))
|
||||||
|
(let ((repaired (ask-probabilistic prompt :system-prompt system-prompt)))
|
||||||
|
(string-trim '(#\Space #\Newline #\Tab) repaired))))
|
||||||
|
|
||||||
|
(defun lisp-utils-check-structural (code-string)
|
||||||
|
"Checks for balanced parens, brackets, and terminated strings.
|
||||||
|
Returns (VALUES t nil) if clean, or (VALUES nil reason-string line col)."
|
||||||
|
(let ((stack nil)
|
||||||
|
(in-string nil)
|
||||||
|
(escaped nil)
|
||||||
|
(line 1)
|
||||||
|
(col 0)
|
||||||
|
(last-open-line 1)
|
||||||
|
(last-open-col 0))
|
||||||
|
(dotimes (i (length code-string))
|
||||||
|
(let ((ch (char code-string i)))
|
||||||
|
(cond (escaped (setf escaped nil))
|
||||||
|
((char= ch #\\) (setf escaped t))
|
||||||
|
(in-string
|
||||||
|
(when (char= ch #\") (setf in-string nil)))
|
||||||
|
((char= ch #\;)
|
||||||
|
(loop while (and (< i (1- (length code-string)))
|
||||||
|
(not (char= (char code-string (1+ i)) #\Newline)))
|
||||||
|
do (incf i))
|
||||||
|
(incf line) (setf col 0))
|
||||||
|
((char= ch #\")
|
||||||
|
(setf in-string t))
|
||||||
|
((member ch '(#\( #\[))
|
||||||
|
(push (list (string ch) line col) stack)
|
||||||
|
(setf last-open-line line last-open-col col))
|
||||||
|
((char= ch #\))
|
||||||
|
(cond ((null stack)
|
||||||
|
(return-from lisp-utils-check-structural
|
||||||
|
(values nil (format nil "Unexpected ')' at line ~a, col ~a" line col) line col)))
|
||||||
|
((string= (caar stack) "[")
|
||||||
|
(return-from lisp-utils-check-structural
|
||||||
|
(values nil (format nil "Mismatched ']' expected at line ~a, col ~a" line col) line col)))
|
||||||
|
(t (pop stack))))
|
||||||
|
((char= ch #\])
|
||||||
|
(cond ((null stack)
|
||||||
|
(return-from lisp-utils-check-structural
|
||||||
|
(values nil (format nil "Unexpected ']' at line ~a, col ~a" line col) line col)))
|
||||||
|
((string= (caar stack) "(")
|
||||||
|
(return-from lisp-utils-check-structural
|
||||||
|
(values nil (format nil "Mismatched ')' expected at line ~a, col ~a" line col) line col)))
|
||||||
|
(t (pop stack))))
|
||||||
|
((char= ch #\Newline)
|
||||||
|
(incf line) (setf col 0)))
|
||||||
|
(unless (char= ch #\Newline) (incf col))))
|
||||||
|
(if (null stack)
|
||||||
|
(values t nil nil nil)
|
||||||
|
(values nil (format nil "Unbalanced '~a' opened at line ~a, col ~a"
|
||||||
|
(caar stack) last-open-line last-open-col)
|
||||||
|
last-open-line last-open-col))))
|
||||||
|
|
||||||
|
(defun lisp-utils-check-syntactic (code-string)
|
||||||
|
"Checks if the code can be read by SBCL with *read-eval* nil.
|
||||||
|
Returns (VALUES t nil) if clean, or (VALUES nil error-message nil nil)."
|
||||||
|
(handler-case
|
||||||
|
(let ((*read-eval* nil))
|
||||||
|
(with-input-from-string (stream (format nil "(progn ~a)" code-string))
|
||||||
|
(loop for form = (read stream nil :eof) until (eq form :eof)))
|
||||||
|
(values t nil nil nil))
|
||||||
|
(error (c)
|
||||||
|
(let ((msg (format nil "~a" c)))
|
||||||
|
(values nil msg nil nil)))))
|
||||||
|
|
||||||
|
(defparameter *lisp-utils-whitelist*
|
||||||
|
'(;; Math & Logic
|
||||||
|
+ - * / = < > <= >= 1+ 1- min max mod abs floor ceiling round
|
||||||
|
and or not null eq eql equal string= string-equal char= char-equal
|
||||||
|
;; List Manipulation
|
||||||
|
list cons car cdr cadr cddr cdar caar caddr cdddr append mapcar remove-if remove-if-not
|
||||||
|
length reverse sort nth nthcdr push pop last butlast subseq
|
||||||
|
;; Plists, Alists, and Hash Tables
|
||||||
|
getf gethash assoc acons pairlis rassoc
|
||||||
|
;; Control Flow
|
||||||
|
let let* if cond when unless case typecase prog1 progn
|
||||||
|
;; Strings
|
||||||
|
format concatenate string-downcase string-upcase search subseq replace
|
||||||
|
;; Type predicates
|
||||||
|
stringp numberp integerp listp symbolp keywordp null
|
||||||
|
;; Kernel safe symbols
|
||||||
|
opencortex::harness-log
|
||||||
|
opencortex::snapshot-memory opencortex::rollback-memory
|
||||||
|
opencortex::lookup-object opencortex::list-objects-by-type
|
||||||
|
opencortex::ingest-ast opencortex::find-headline-missing-id
|
||||||
|
opencortex::context-query-store opencortex::context-get-active-projects
|
||||||
|
opencortex::context-get-recent-completed-tasks opencortex::context-list-all-skills
|
||||||
|
opencortex::context-get-system-logs opencortex::context-assemble-global-awareness
|
||||||
|
opencortex::org-object-id opencortex::org-object-type opencortex::org-object-attributes
|
||||||
|
opencortex::org-object-content opencortex::org-object-parent-id
|
||||||
|
opencortex::org-object-children opencortex::org-object-version
|
||||||
|
opencortex::org-object-last-sync opencortex::org-object-hash
|
||||||
|
opencortex::org-object-vector
|
||||||
|
;; Essential macros and special operators
|
||||||
|
declare ignore quote function lambda defun defvar defparameter defmacro
|
||||||
|
;; Safe I/O
|
||||||
|
with-open-file write-string read-line
|
||||||
|
;; Package introspection
|
||||||
|
find-package make-package in-package do-external-symbols find-symbol
|
||||||
|
;; Safe system interaction
|
||||||
|
uiop:run-program uiop:getenv uiop:merge-pathnames* uiop:file-exists-p
|
||||||
|
uiop:directory-exists-p uiop:read-file-string uiop:split-string
|
||||||
|
;; Time
|
||||||
|
get-universal-time get-internal-real-time sleep
|
||||||
|
;; Equality
|
||||||
|
equalp = equal eq eql)
|
||||||
|
"Static whitelist of symbols permitted in the Lisp Utils sandbox.")
|
||||||
|
|
||||||
|
(defun lisp-utils-ast-walk (form)
|
||||||
|
"Recursively walks the Lisp AST. Returns T if safe, NIL if unsafe."
|
||||||
|
(cond
|
||||||
|
((or (stringp form) (numberp form) (keywordp form) (characterp form)) t)
|
||||||
|
((symbolp form)
|
||||||
|
(or (member form *lisp-utils-whitelist* :test #'string-equal)
|
||||||
|
(member (format nil "~a" form) *lisp-utils-whitelist* :test #'string-equal)))
|
||||||
|
((listp form)
|
||||||
|
(let ((head (car form)))
|
||||||
|
(cond
|
||||||
|
((eq head 'quote) t)
|
||||||
|
((not (symbolp head)) nil)
|
||||||
|
((member head *lisp-utils-whitelist* :test #'string-equal)
|
||||||
|
(every #'lisp-utils-ast-walk (cdr form)))
|
||||||
|
(t
|
||||||
|
(harness-log "LISP UTILS: Blocked call to non-whitelisted function ~a" head)
|
||||||
|
nil))))
|
||||||
|
(t nil)))
|
||||||
|
|
||||||
|
(defun lisp-utils-check-semantic (code-string)
|
||||||
|
"Checks if all symbols in CODE-STRING are whitelisted.
|
||||||
|
Returns (VALUES t nil) if clean, or (VALUES nil reason-string nil nil)."
|
||||||
|
(handler-case
|
||||||
|
(let ((*read-eval* nil))
|
||||||
|
(with-input-from-string (stream (format nil "(progn ~a)" code-string))
|
||||||
|
(loop for form = (read stream nil :eof)
|
||||||
|
until (eq form :eof)
|
||||||
|
do (unless (lisp-utils-ast-walk form)
|
||||||
|
(return-from lisp-utils-check-semantic
|
||||||
|
(values nil "Code contains non-whitelisted symbols." nil nil)))))
|
||||||
|
(values t nil nil nil))
|
||||||
|
(error (c)
|
||||||
|
(values nil (format nil "Semantic check failed: ~a" c) nil nil))))
|
||||||
|
|
||||||
|
(defun lisp-utils-validate (code-string &key strict)
|
||||||
|
"Validates Lisp code through structural, syntactic, and optional semantic checks.
|
||||||
|
Returns a plist:
|
||||||
|
(:status :success :checks (:structural t :syntactic t :semantic t))
|
||||||
|
or
|
||||||
|
(:status :error :failed <check-key> :reason <string> :line <n> :col <n>)
|
||||||
|
|
||||||
|
When STRICT is non-nil, the semantic whitelist check is enforced."
|
||||||
|
(let ((structural-ok nil) (syntactic-ok nil) (semantic-ok nil)
|
||||||
|
(reason nil) (line nil) (col nil))
|
||||||
|
;; Phase 1: Structural
|
||||||
|
(multiple-value-setq (structural-ok reason line col)
|
||||||
|
(lisp-utils-check-structural code-string))
|
||||||
|
(unless structural-ok
|
||||||
|
(return-from lisp-utils-validate
|
||||||
|
(list :status :error :failed :structural :reason reason :line line :col col)))
|
||||||
|
;; Phase 2: Syntactic
|
||||||
|
(multiple-value-setq (syntactic-ok reason line col)
|
||||||
|
(lisp-utils-check-syntactic code-string))
|
||||||
|
(unless syntactic-ok
|
||||||
|
(return-from lisp-utils-validate
|
||||||
|
(list :status :error :failed :syntactic :reason reason :line line :col col)))
|
||||||
|
;; Phase 3: Semantic (only when strict)
|
||||||
|
(when strict
|
||||||
|
(multiple-value-setq (semantic-ok reason line col)
|
||||||
|
(lisp-utils-check-semantic code-string))
|
||||||
|
(unless semantic-ok
|
||||||
|
(return-from lisp-utils-validate
|
||||||
|
(list :status :error :failed :semantic :reason reason :line line :col col))))
|
||||||
|
;; All clear
|
||||||
|
(list :status :success
|
||||||
|
:checks (list :structural t :syntactic t :semantic (or (not strict) semantic-ok)))))
|
||||||
|
|
||||||
|
(def-cognitive-tool :validate-lisp
|
||||||
|
"Deterministically validates Lisp code for structural, syntactic, and semantic correctness.
|
||||||
|
Use this BEFORE declaring any Lisp code edit complete."
|
||||||
|
((:code :type :string :description "The Lisp code string to validate.")
|
||||||
|
(:strict :type :boolean :description "If non-nil, enforces the semantic whitelist."))
|
||||||
|
:body (lambda (args)
|
||||||
|
(let ((code (getf args :code))
|
||||||
|
(strict (getf args :strict)))
|
||||||
|
(if (and code (stringp code))
|
||||||
|
(lisp-utils-validate code :strict strict)
|
||||||
|
(list :status :error :reason "Missing :code argument.")))))
|
||||||
|
|
||||||
|
(def-cognitive-tool :repair-lisp
|
||||||
|
"Repairs broken Lisp code using deterministic first, then neural escalation."
|
||||||
|
((:code :type :string :description "The broken Lisp code string")
|
||||||
|
(:error :type :string :description "The error message from parsing failure"))
|
||||||
|
:body (lambda (args)
|
||||||
|
(let ((code (getf args :code))
|
||||||
|
(error-msg (getf args :error)))
|
||||||
|
(if (and code error-msg)
|
||||||
|
(let ((fast-fix (deterministic-repair code)))
|
||||||
|
(handler-case
|
||||||
|
(let ((repaired (read-from-string fast-fix)))
|
||||||
|
(format nil "~a" repaired))
|
||||||
|
(error ()
|
||||||
|
(let ((deep-fix (neural-repair code error-msg)))
|
||||||
|
(handler-case
|
||||||
|
(let ((repaired (read-from-string deep-fix)))
|
||||||
|
(format nil "~a" repaired))
|
||||||
|
(error ()
|
||||||
|
"REPAIR FAILED"))))))
|
||||||
|
(list :status :error :reason "Missing :code or :error argument.")))))
|
||||||
|
|
||||||
|
(defskill :skill-lisp-repair
|
||||||
|
:priority 90
|
||||||
|
:trigger (lambda (ctx) (eq (getf (getf ctx :payload) :sensor) :syntax-error))
|
||||||
|
:probabilistic nil
|
||||||
|
:deterministic (lambda (action context)
|
||||||
|
(declare (ignore action))
|
||||||
|
(let* ((payload (getf context :payload))
|
||||||
|
(code (getf payload :code))
|
||||||
|
(error-msg (getf payload :error)))
|
||||||
|
(harness-log "LISP REPAIR: Reacting to syntax error...")
|
||||||
|
(let ((fast-fix (deterministic-repair code)))
|
||||||
|
(handler-case
|
||||||
|
(let ((repaired (read-from-string fast-fix)))
|
||||||
|
(harness-log "LISP REPAIR: Deterministic repair SUCCESS.")
|
||||||
|
repaired)
|
||||||
|
(error ()
|
||||||
|
(harness-log "LISP REPAIR: Deterministic failed. Escalating to neural...")
|
||||||
|
(let ((deep-fix (neural-repair code error-msg)))
|
||||||
|
(handler-case
|
||||||
|
(let ((repaired (read-from-string deep-fix)))
|
||||||
|
(harness-log "LISP REPAIR: Neural repair SUCCESS.")
|
||||||
|
repaired)
|
||||||
|
(error ()
|
||||||
|
(harness-log "LISP REPAIR: Neural repair failed.")
|
||||||
|
(list :type :LOG :payload (list :text "Lisp Repair Failed.")))))))))))
|
||||||
|
|
||||||
|
(defskill :skill-lisp-validator
|
||||||
|
:priority 900
|
||||||
|
:trigger (lambda (ctx)
|
||||||
|
(let ((candidate (getf ctx :approved-action)))
|
||||||
|
(when candidate
|
||||||
|
(let ((payload (getf candidate :payload)))
|
||||||
|
(member (getf payload :action) '(:eval :shell))))))
|
||||||
|
:probabilistic nil
|
||||||
|
:deterministic (lambda (action context)
|
||||||
|
(declare (ignore context))
|
||||||
|
(let ((payload (getf action :payload)))
|
||||||
|
(if (eq (getf payload :action) :eval)
|
||||||
|
(let* ((code (getf payload :code))
|
||||||
|
(result (lisp-utils-validate code :strict t)))
|
||||||
|
(if (eq (getf result :status) :error)
|
||||||
|
(progn
|
||||||
|
(harness-log "LISP VALIDATOR: Blocked unsafe :eval action. ~a"
|
||||||
|
(getf result :reason))
|
||||||
|
(list :type :LOG
|
||||||
|
:payload (list :level :error
|
||||||
|
:text (format nil "LISP VALIDATOR: Blocked unsafe eval. ~a"
|
||||||
|
(getf result :reason)))))
|
||||||
|
action))
|
||||||
|
action))))
|
||||||
@@ -101,13 +101,24 @@
|
|||||||
#:register-emacs-client
|
#:register-emacs-client
|
||||||
#:unregister-emacs-client
|
#:unregister-emacs-client
|
||||||
|
|
||||||
;; --- Probabilistic Engine ---
|
;; --- Probabilistic Engine ---
|
||||||
#:ask-probabilistic
|
#:ask-probabilistic
|
||||||
#:register-probabilistic-backend
|
#:register-probabilistic-backend
|
||||||
#:distill-prompt
|
#:distill-prompt
|
||||||
#:*provider-cascade*
|
#:*provider-cascade*
|
||||||
|
|
||||||
;; --- Security Vault ---
|
;; --- Vector Search ---
|
||||||
|
#:get-embedding
|
||||||
|
#:cosine-similarity
|
||||||
|
#:semantic-search
|
||||||
|
|
||||||
|
;; --- Tool Permissions ---
|
||||||
|
#:get-tool-permission
|
||||||
|
#:set-tool-permission
|
||||||
|
#:check-tool-permission-gate
|
||||||
|
|
||||||
|
|
||||||
|
;; --- Security Vault ---
|
||||||
#:vault-get-secret
|
#:vault-get-secret
|
||||||
#:vault-set-secret
|
#:vault-set-secret
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,8 @@
|
|||||||
(:file "tests/memory-tests")
|
(:file "tests/memory-tests")
|
||||||
(:file "tests/immune-system-tests")
|
(:file "tests/immune-system-tests")
|
||||||
(:file "tests/emacs-edit-tests")
|
(:file "tests/emacs-edit-tests")
|
||||||
(:file "tests/lisp-utils-tests"))
|
(:file "tests/lisp-utils-tests")
|
||||||
|
(:file "tests/tool-permissions-tests"))
|
||||||
|
|
||||||
:perform (test-op (o s)
|
:perform (test-op (o s)
|
||||||
(uiop:symbol-call :fiveam :run!
|
(uiop:symbol-call :fiveam :run!
|
||||||
|
|||||||
@@ -5,38 +5,30 @@
|
|||||||
(in-package :opencortex-emacs-edit-tests)
|
(in-package :opencortex-emacs-edit-tests)
|
||||||
|
|
||||||
(def-suite emacs-edit-suite
|
(def-suite emacs-edit-suite
|
||||||
:description "Tests for the Emacs Edit skill - ID generation, property setting, and AST manipulation.")
|
:description "Tests for Emacs Edit skill.")
|
||||||
|
|
||||||
(in-suite emacs-edit-suite)
|
(in-suite emacs-edit-suite)
|
||||||
|
|
||||||
(test id-generation
|
(test id-generation
|
||||||
(let ((id1 (opencortex::emacs-edit-generate-id))
|
(let ((id1 (emacs-edit-generate-id))
|
||||||
(id2 (opencortex::emacs-edit-generate-id)))
|
(id2 (emacs-edit-generate-id)))
|
||||||
(is (plusp (length id1)))
|
(is (plusp (length id1)))
|
||||||
(is (not (string= id1 id2)))))
|
(is (not (string= id1 id2)))))
|
||||||
|
|
||||||
(test id-format
|
(test id-format
|
||||||
(let ((formatted (opencortex::emacs-edit-id-format "abc12345")))
|
(let ((formatted (emacs-edit-id-format "abc12345")))
|
||||||
(is (search "id:" formatted))))
|
(is (search "id:" formatted))))
|
||||||
|
|
||||||
(test property-setter
|
(test property-setter
|
||||||
(let ((ast (list :type :headline
|
(let ((ast (list :type :headline
|
||||||
:properties (list :ID "id:test123" :TITLE "Test")
|
:properties (list :ID "id:test123" :TITLE "Test")
|
||||||
:contents nil)))
|
:contents nil)))
|
||||||
(opencortex::emacs-edit-set-property ast "id:test123" :STATUS "ACTIVE")
|
(emacs-edit-set-property ast "id:test123" :STATUS "ACTIVE")
|
||||||
(is (string= (getf (getf ast :properties) :STATUS) "ACTIVE"))))
|
(is (string= (getf (getf ast :properties) :STATUS) "ACTIVE"))))
|
||||||
|
|
||||||
(test todo-setter
|
(test todo-setter
|
||||||
(let ((ast (list :type :headline
|
(let ((ast (list :type :headline
|
||||||
:properties (list :ID "id:todo001" :TITLE "Task")
|
:properties (list :ID "id:todo001" :TITLE "Task")
|
||||||
:contents nil)))
|
:contents nil)))
|
||||||
(opencortex::emacs-edit-set-todo ast "id:todo001" "DONE")
|
(emacs-edit-set-todo ast "id:todo001" "DONE")
|
||||||
(is (string= (getf (getf ast :properties) :TODO) "DONE"))))
|
(is (string= (getf (getf ast :properties) :TODO) "DONE"))))
|
||||||
|
|
||||||
(test find-headline-by-id
|
|
||||||
(let ((ast (list :type :headline
|
|
||||||
:properties (list :ID "id:findme" :TITLE "Found")
|
|
||||||
:contents nil)))
|
|
||||||
(let ((found (opencortex::emacs-edit-find-headline-by-id ast "id:findme")))
|
|
||||||
(is (not (null found)))
|
|
||||||
(is (string= (getf (getf found :properties) :ID) "id:findme")))))
|
|
||||||
Reference in New Issue
Block a user