diff --git a/library/gen/org-skill-cli-gateway.lisp b/library/gen/org-skill-cli-gateway.lisp index e3b1631..0a1a889 100644 --- a/library/gen/org-skill-cli-gateway.lisp +++ b/library/gen/org-skill-cli-gateway.lisp @@ -56,13 +56,13 @@ "Starts the TCP listener for local CLI clients." (setf *cli-server-socket* (usocket:socket-listen "0.0.0.0" port :reuse-address t)) (setf *cli-server-thread* - (bordeaux-threads:make-thread + (bt:make-thread (lambda () (unwind-protect (loop (let* ((socket (usocket:socket-accept *cli-server-socket*)) (stream (usocket:socket-stream socket))) - (bordeaux-threads:make-thread (lambda () + (bt:make-thread (lambda () (unwind-protect (handle-cli-client stream) (usocket:socket-close socket))) :name "opencortex-cli-client-handler"))) diff --git a/library/gen/org-skill-engineering-standards.lisp b/library/gen/org-skill-engineering-standards.lisp new file mode 100644 index 0000000..8f19475 --- /dev/null +++ b/library/gen/org-skill-engineering-standards.lisp @@ -0,0 +1,19 @@ +(in-package :opencortex) + +(defun verify-git-clean-p (&optional (dir *project-root*)) + "Returns T if the git repository at DIR has no uncommitted changes." + (let ((status (uiop:run-program (list "git" "-C" (namestring dir) "status" "--porcelain") + :output :string + :ignore-error-status t))) + (string= "" (string-trim '(#\Space #\Newline #\Tab) status)))) + +(defskill :skill-engineering-standards + :priority 1000 + :trigger (lambda (ctx) t) + :probabilistic nil + :deterministic (lambda (action context) + (declare (ignore action)) + (let ((dirty (verify-git-clean-p))) + (unless dirty + (harness-log "ENGINEERING STANDARDS: Warning - Working tree is dirty. Commit before modifying files."))) + nil)) diff --git a/library/gen/org-skill-llm-gateway.lisp b/library/gen/org-skill-llm-gateway.lisp index ff79ff3..f7d3c9b 100644 --- a/library/gen/org-skill-llm-gateway.lisp +++ b/library/gen/org-skill-llm-gateway.lisp @@ -92,28 +92,22 @@ (execute-llm-request prompt system-prompt :provider p :model model)))) (def-cognitive-tool :get-ollama-embedding - "Generates vector embeddings via Ollama API." - ((text :type :string :description "Text to embed.")) - :body (lambda (args) - (let* ((text (getf args :text)) - (host (or (uiop:getenv "OLLAMA_HOST") "localhost:11434")) - (url (format nil "http://~a/api/embeddings" host)) - (model (or (uiop:getenv "OLLAMA_EMBEDDING_MODEL") "nomic-embed-text")) - (body (cl-json:encode-json-to-string `((model . ,model) (prompt . ,text))))) - (handler-case - (let* ((response (dex:post url :headers '(("Content-Type" . "application/json")) :content body :connect-timeout 5 :read-timeout 30)) - (json (cl-json:decode-json-from-string response))) - (let ((embedding (cdr (assoc :embedding json)))) - (if embedding - (list :status :success :vector embedding) - (list :status :error :message "No embedding in response")))) - (error (c) (list :status :error :message (format nil "Ollama Embedding Failure: ~a" c))))))) - -(defun get-embedding (text) - "Generates a vector embedding for the given text via Ollama. Returns nil on failure." - (let ((result (funcall (get-cognitive-tool-body :get-ollama-embedding) (list :text text)))) - (when (eq (getf result :status) :success) - (getf result :vector)))) + "Generates vector embeddings via Ollama API for semantic search." + ((text :type :string :description "Text to embed.")) + :body (lambda (args) + (let* ((text (getf args :text)) + (host (or (uiop:getenv "OLLAMA_HOST") "localhost:11434")) + (url (format nil "http://~a/api/embeddings" host)) + (model (or (uiop:getenv "OLLAMA_EMBEDDING_MODEL") "nomic-embed-text")) + (body (cl-json:encode-json-to-string `((model . ,model) (prompt . ,text))))) + (handler-case + (let* ((response (dex:post url :headers '(("Content-Type" . "application/json")) :content body :connect-timeout 5 :read-timeout 30)) + (json (cl-json:decode-json-from-string response))) + (let ((embedding (cdr (assoc :embedding json)))) + (if embedding + (list :status :success :vector embedding) + (list :status :error :message "No embedding in response")))) + (error (c) (list :status :error :message (format nil "Ollama Embedding Failure: ~a" c))))))) (def-cognitive-tool :ask-llm "Queries an LLM provider via the unified gateway." diff --git a/library/gen/org-skill-self-fix.lisp b/library/gen/org-skill-self-fix.lisp new file mode 100644 index 0000000..681be81 --- /dev/null +++ b/library/gen/org-skill-self-fix.lisp @@ -0,0 +1,65 @@ +(in-package :opencortex) + +(defun self-fix-apply (action context) + "Applies a surgical code fix and reloads the modified skill." + (declare (ignore context)) + (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 "skills/" (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)))) + +(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."))) + +(defskill :skill-self-fix + :priority 95 + :trigger (lambda (context) (eq (getf (getf context :payload) :sensor) :repair-request)) + :probabilistic (lambda (context) + (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)))) diff --git a/library/gen/org-skill-tool-permissions.lisp b/library/gen/org-skill-tool-permissions.lisp index 0eb3777..a972f0e 100644 --- a/library/gen/org-skill-tool-permissions.lisp +++ b/library/gen/org-skill-tool-permissions.lisp @@ -22,15 +22,11 @@ (def-cognitive-tool :get-embedding "Generates vector embeddings via Ollama or llama.cpp API." - ((text :type :string :description "Text to embed.")) + ((:text :type :string :description "Text to embed.")) :body (lambda (args) (let* ((text (getf args :text)) (provider (or (uiop:getenv "EMBEDDING_PROVIDER") "ollama")) - (model (or (uiop:getenv "EMBEDDING_MODEL") - (case (intern (string-upcase provider) :keyword) - (:NOMIC-EMBED-TEXT "nomic-embed-text") - (:LLAMA-CPP "llama.cpp") - (t "nomic-embed-text")))) + (model (or (uiop:getenv "EMBEDDING_MODEL") "nomic-embed-text")) (embedding nil)) (cond ((string= provider "ollama") @@ -88,4 +84,4 @@ :trigger (lambda (c) (declare (ignore c)) nil) :deterministic (lambda (a c) (let ((tool (getf (getf a :payload) :tool))) - (when tool (check-tool-permission-gate tool c))))) \ No newline at end of file + (when tool (check-tool-permission-gate tool c))))) diff --git a/run-all-tests.lisp b/run-all-tests.lisp new file mode 100644 index 0000000..da2df6c --- /dev/null +++ b/run-all-tests.lisp @@ -0,0 +1,21 @@ +(load "/home/user/quicklisp/setup.lisp") +(push #p"./" asdf:*central-registry*) +(ql:quickload :fiveam :verbose nil) +(asdf:load-system :opencortex/tests :verbose nil) + +;; Load tool permissions skill +(load "library/gen/org-skill-tool-permissions.lisp") +(load "tests/tool-permissions-tests.lisp") + +(format t "~%=== Running ALL Test Suites ===~%") + +(fiveam:run! 'opencortex-tests::communication-protocol-suite) +(fiveam:run! 'opencortex-pipeline-tests::pipeline-suite) +(fiveam:run! 'opencortex-boot-tests::boot-suite) +(fiveam:run! 'opencortex-memory-tests::memory-suite) +(fiveam:run! 'opencortex-immune-system-tests::immune-suite) +(fiveam:run! 'opencortex-emacs-edit-tests::emacs-edit-suite) +(fiveam:run! 'opencortex-lisp-utils-tests::lisp-utils-suite) +(fiveam:run! 'opencortex-tool-permissions-tests::tool-permissions-suite) + +(format t "~%=== ALL TESTS COMPLETE ===~%") \ No newline at end of file diff --git a/run-tool-tests.lisp b/run-tool-tests.lisp new file mode 100644 index 0000000..e5c424b --- /dev/null +++ b/run-tool-tests.lisp @@ -0,0 +1,10 @@ +(load "/home/user/quicklisp/setup.lisp") +(push #p"./" asdf:*central-registry*) +(ql:quickload :fiveam :verbose nil) +(asdf:load-system :opencortex :verbose nil) +(load "library/gen/org-skill-tool-permissions.lisp") +(load "tests/tool-permissions-tests.lisp") + +(format t "~%=== Tool Permissions Tests ===~%") +(fiveam:run! 'opencortex-tool-permissions-tests::tool-permissions-suite) +(format t "~%=== DONE ===~%") \ No newline at end of file diff --git a/skills/org-skill-policy.org b/skills/org-skill-policy.org index 2eb466e..112119f 100644 --- a/skills/org-skill-policy.org +++ b/skills/org-skill-policy.org @@ -128,7 +128,7 @@ At the gate: :payload (list :level :error :text "POLICY [Transparency]: User-facing action missing :explanation. Blocked."))))) - action) + action)) #+end_src ** 2. Autonomy Above All @@ -195,7 +195,7 @@ Every action should increase the user's independence from centralized, proprieta :text (format nil "Autonomy Debt: Action references proprietary domain '~a'. Consider a local alternative." domain) :original-action action))) - action)) + action))) #+end_src ** 3. Zero-Bloat Mandate @@ -241,7 +241,7 @@ The system harness must remain minimalist. "Just-in-case" code is a security vul (length content) *policy-max-skill-size-chars*) :original-action action)))) - action) + action)) #+end_src ** 4. Modularity @@ -313,7 +313,7 @@ This is the most important invariant for system stability. If the harness grows :text "POLICY [Modularity]: Modification to protected core path blocked. Provide :modularity-justification explaining why this cannot be a skill." :blocked-path target-file)))) - action) + action)) #+end_src ** 5. Technical Mastery & Mentorship @@ -365,7 +365,7 @@ The agent's goal is not to "do it for the user," but to "empower the user." Ever :payload (list :level :error :text "POLICY [Mentorship]: High-impact action missing :mentorship-note. Explain what you are doing and why. Blocked."))))) - action) + action)) #+end_src ** 6. Long-Term Sustainability @@ -395,7 +395,7 @@ This means preferring local, energy-efficient architectures over cloud-dependent (let* ((payload (getf context :payload)) (backend (getf payload :backend)) - (provider (getf payload :provider)) + (provider (getf payload :provider))) (when (or (member backend *cloud-only-backends*) (member provider *cloud-only-backends*)) @@ -409,7 +409,7 @@ This means preferring local, energy-efficient architectures over cloud-dependent :text (format nil "Sustainability Debt: Reliance on cloud provider '~a'. Consider Ollama or local inference." (or backend provider)))))) - action) + action))) #+end_src * Policy Explanation Engine @@ -487,7 +487,7 @@ When the policy gate blocks or modifies an action, it must tell the user *why*. (t (harness-log "~a" (getf (getf result :payload) :text))))))))) - action) + action)) #+end_src ** Finding Engineering Standards diff --git a/tests/emacs-edit-tests.lisp b/tests/emacs-edit-tests.lisp index dc5e795..4be3fc0 100644 --- a/tests/emacs-edit-tests.lisp +++ b/tests/emacs-edit-tests.lisp @@ -10,25 +10,25 @@ (in-suite emacs-edit-suite) (test id-generation - (let ((id1 (opencortex:emacs-edit-generate-id)) - (id2 (opencortex:emacs-edit-generate-id))) + (let ((id1 (emacs-edit-generate-id)) + (id2 (emacs-edit-generate-id))) (is (plusp (length id1))) - (is (not (string= id1 id2))))) + (is (not (string= id1 id2)))) ;; Likely unique (test id-format - (let ((formatted (opencortex:emacs-edit-id-format "abc12345"))) + (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))) - (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")))) (test todo-setter (let ((ast (list :type :headline :properties (list :ID "id:todo001" :TITLE "Task") :contents nil))) - (opencortex:emacs-edit-set-todo ast "id:todo001" "DONE") - (is (string= (getf (getf ast :properties) :TODO) "DONE")))) \ No newline at end of file + (emacs-edit-set-todo ast "id:todo001" "DONE") + (is (string= (getf (getf ast :properties) :TODO) "DONE")))) diff --git a/tests/tool-permissions-tests.lisp b/tests/tool-permissions-tests.lisp new file mode 100644 index 0000000..d31b704 --- /dev/null +++ b/tests/tool-permissions-tests.lisp @@ -0,0 +1,40 @@ +(defpackage :opencortex-tool-permissions-tests + (:use :cl :fiveam :opencortex) + (:export #:tool-permissions-suite)) + +(in-package :opencortex-tool-permissions-tests) + +(def-suite tool-permissions-suite + :description "Tests for Tool Permission Tiers.") + +(in-suite tool-permissions-suite) + +(test default-permission-is-allow + "Unknown tools default to :allow." + (is (eq (get-tool-permission :unknown-tool-xyz) :allow))) + +(test set-and-get-permission + "Verify :allow, :deny, :ask persist correctly." + (set-tool-permission :test-tool-abc :deny) + (is (eq (get-tool-permission :test-tool-abc) :deny)) + (set-tool-permission :test-tool-abc :ask) + (is (eq (get-tool-permission :test-tool-abc) :ask)) + (set-tool-permission :test-tool-abc :allow) + (is (eq (get-tool-permission :test-tool-abc) :allow))) + +(test permission-gate-allow + ":allow returns :allow." + (set-tool-permission :gate-allow-tool :allow) + (is (eq (check-tool-permission-gate :gate-allow-tool nil) :allow))) + +(test permission-gate-deny + ":deny returns :deny." + (set-tool-permission :gate-deny-tool :deny) + (is (eq (check-tool-permission-gate :gate-deny-tool nil) :deny))) + +(test permission-gate-ask + ":ask returns a signal list." + (set-tool-permission :gate-ask-tool :ask) + (let ((result (check-tool-permission-gate :gate-ask-tool nil))) + (is (listp result)) + (is (eq (car result) :ask)))) \ No newline at end of file