diff --git a/.gitignore b/.gitignore index f431220..f3de3cf 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,5 @@ test_input.txt # Generated artifacts (source of truth is .org) /skills/*.lisp -/tests/*.lisp /tmp/*.lisp *.fasl diff --git a/lisp/core-skills.lisp b/lisp/core-skills.lisp index 97c7363..7b839b3 100644 --- a/lisp/core-skills.lisp +++ b/lisp/core-skills.lisp @@ -99,11 +99,8 @@ (string= n "security-dispatcher") (string= n "system-model-router") (string= n "system-model-embedding") - (string= n "system-model-explorer") - (string= n "gateway-tui") - (string= n "gateway-tui-model") - (string= n "gateway-tui-view") - (string= n "gateway-tui-main")))) + (string= n "system-model-explorer") + (string= n "gateway-tui")))) all-files)) (adj (make-hash-table :test 'equal)) (name-to-file (make-hash-table :test 'equal)) @@ -284,3 +281,29 @@ (load-skill-from-lisp file) (load-skill-from-org file))) (log-message "LOADER: Boot Complete.")))) + +(eval-when (:compile-toplevel :load-toplevel :execute) + (ql:quickload :fiveam :silent t)) + +(defpackage :passepartout-boot-tests + (:use :cl :fiveam :passepartout) + (:export #:boot-suite)) + +(in-package :passepartout-boot-tests) + +(def-suite boot-suite :description "Verification of the Skill Engine loader") +(in-suite boot-suite) + +(test test-topological-sort-basic + (let ((tmp-dir "/tmp/passepartout-boot-test/")) + (uiop:ensure-all-directories-exist (list tmp-dir)) + (with-open-file (out (merge-pathnames "org-skill-a.org" tmp-dir) :direction :output :if-exists :supersede) + (format out "#+DEPENDS_ON: skill-b-id~%")) + (with-open-file (out (merge-pathnames "org-skill-b.org" tmp-dir) :direction :output :if-exists :supersede) + (format out ":PROPERTIES:~%:ID: skill-b-id~%:END:~%")) + (unwind-protect + (let ((sorted (passepartout::skill-topological-sort tmp-dir))) + (let ((pos-a (position "org-skill-a" sorted :key #'pathname-name :test #'string-equal)) + (pos-b (position "org-skill-b" sorted :key #'pathname-name :test #'string-equal))) + (is (< pos-b pos-a)))) + (uiop:delete-directory-tree (uiop:ensure-directory-pathname tmp-dir) :validate t)))) diff --git a/lisp/gateway-tui-main.lisp b/lisp/gateway-tui-main.lisp index 9b1d5b8..f7824e2 100644 --- a/lisp/gateway-tui-main.lisp +++ b/lisp/gateway-tui-main.lisp @@ -163,3 +163,24 @@ (refresh scr) (sleep 0.03)) (disconnect-daemon)))) + +(eval-when (:compile-toplevel :load-toplevel :execute) + (ql:quickload :fiveam :silent t)) + +(defpackage :passepartout-tui-tests + (:use :cl :passepartout) + (:export #:tui-suite)) + +(in-package :passepartout-tui-tests) + +(fiveam:def-suite tui-suite :description "Verification of the TUI parsing and styling logic") +(fiveam:in-suite tui-suite) + +(fiveam:test test-tui-connection-drop + "Tier 2 Chaos: Verify that handle-return degrades gracefully when the daemon connection is lost." + (let ((passepartout.gateway-tui::*incoming-msgs* nil) + (passepartout.gateway-tui::*input-buffer* (make-array 5 :element-type 'character :initial-contents "hello" :fill-pointer 5 :adjustable t)) + (mock-stream (make-string-output-stream))) + (close mock-stream) + (passepartout.gateway-tui::handle-return mock-stream) + (fiveam:is (member "ERROR: Connection to daemon lost." passepartout.gateway-tui::*incoming-msgs* :test #'string=)))) diff --git a/lisp/programming-lisp.lisp b/lisp/programming-lisp.lisp index 933a126..a64600e 100644 --- a/lisp/programming-lisp.lisp +++ b/lisp/programming-lisp.lisp @@ -1,3 +1,5 @@ +(in-package :passepartout) + (defun lisp-structural-check (code) "Checks if parentheses are balanced and the code is readable." (handler-case @@ -146,3 +148,78 @@ (defskill :passepartout-programming-lisp :priority 400 :trigger (lambda (ctx) (declare (ignore ctx)) nil)) + +(defpackage :passepartout-utils-lisp-tests + (:use :cl :fiveam :passepartout) + (:export #:utils-lisp-suite)) + +(in-package :passepartout-utils-lisp-tests) + +(def-suite utils-lisp-suite + :description "Tests for the Lisp Validator structural, syntactic, and semantic gates") + +(in-suite utils-lisp-suite) + +(test structural-balanced + (is (eq t (passepartout:lisp-structural-check "(+ 1 2)")))) + +(test structural-unbalanced-open + (multiple-value-bind (ok reason) (passepartout:lisp-structural-check "(+ 1 2") + (is (null ok)) + (is (search "Reader Error" reason)))) + +(test structural-unbalanced-close + (multiple-value-bind (ok reason) (passepartout:lisp-structural-check "+ 1 2)") + (is (null ok)) + (is (search "Reader Error" reason)))) + +(test syntactic-valid + (is (eq t (passepartout:lisp-syntactic-check "(+ 1 2)")))) + +(test semantic-safe + (is (eq t (passepartout:lisp-semantic-check "(+ 1 2)")))) + +(test semantic-blocked-eval + (multiple-value-bind (ok reason) (passepartout:lisp-semantic-check "(eval '(+ 1 2))") + (is (null ok)) + (is (search "Unsafe" reason)))) + +(test unified-success + (let ((result (passepartout:lisp-validate "(+ 1 2)" :strict t))) + (is (eq (getf result :status) :success)))) + +(test unified-failure + (let ((result (passepartout:lisp-validate "(+ 1 2" :strict nil))) + (is (eq (getf result :status) :error)))) + +(test eval-basic + (let ((result (passepartout:lisp-eval "(+ 1 2)"))) + (is (eq (getf result :status) :success)) + (is (string= (getf result :result) "3")))) + +(test structural-extract + (let* ((code "(defun hello () (print \"hi\")) (defun bye () (print \"bye\"))") + (extracted (passepartout:lisp-extract code "hello"))) + (is (not (null extracted))) + (let ((form (read-from-string extracted))) + (is (eq (car form) 'DEFUN)) + (is (eq (second form) 'HELLO))))) + +(test list-definitions + (let ((code "(defun foo () t) (defmacro bar () nil) (defparameter *baz* 10)")) + (let ((names (passepartout:lisp-list-definitions code))) + (is (member 'FOO names)) + (is (member 'BAR names)) + (is (member '*BAZ* names))))) + +(test structural-inject + (let* ((code "(defun my-fun (x) (print x))") + (injected (passepartout:lisp-inject code "my-fun" "(finish-output)"))) + (let ((form (read-from-string injected))) + (is (equal (last form) '((FINISH-OUTPUT))))))) + +(test structural-slurp + (let* ((code "(defun work () (step-1))") + (slurped (passepartout:lisp-slurp code "work" "(step-2)"))) + (let ((form (read-from-string slurped))) + (is (equal (last form) '((STEP-2))))))) diff --git a/lisp/programming-org.lisp b/lisp/programming-org.lisp index 8575f5c..c423d54 100644 --- a/lisp/programming-org.lisp +++ b/lisp/programming-org.lisp @@ -1,3 +1,5 @@ +(in-package :passepartout) + (defun org-filetags-extract (content) "Extracts the list of tags from a #+FILETAGS: line." (let ((lines (uiop:split-string content :separator '(#\Newline)))) @@ -242,3 +244,41 @@ AST format: (:TYPE :HEADLINE :properties (:ID ... :TITLE ... :TAGS (...)) (defskill :passepartout-programming-org :priority 100 :trigger (lambda (ctx) (declare (ignore ctx)) nil)) + +(eval-when (:compile-toplevel :load-toplevel :execute) + (ignore-errors (ql:quickload :fiveam :silent t))) + +(defpackage :passepartout-utils-org-tests + (:use :cl :fiveam :passepartout) + (:export #:utils-org-suite)) + +(in-package :passepartout-utils-org-tests) + +(def-suite utils-org-suite + :description "Tests for Utils Org skill.") + +(in-suite utils-org-suite) + +(test id-generation + (let ((id1 (org-id-generate)) + (id2 (org-id-generate))) + (is (plusp (length id1))) + (is (not (string= id1 id2))))) + +(test id-format + (let ((formatted (org-id-format "abc12345"))) + (is (search "id:" formatted)))) + +(test property-setter + (let ((ast (list :type :HEADLINE + :properties (list :ID "id:test123" :TITLE "Test") + :contents nil))) + (org-property-set 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))) + (org-todo-set ast "id:todo001" "DONE") + (is (string= (getf (getf ast :properties) :TODO) "DONE")))) diff --git a/lisp/system-diagnostics.lisp b/lisp/system-diagnostics.lisp index adfdf69..de7b4ff 100644 --- a/lisp/system-diagnostics.lisp +++ b/lisp/system-diagnostics.lisp @@ -172,6 +172,34 @@ (uiop:quit 0) (uiop:quit 1))) +(eval-when (:compile-toplevel :load-toplevel :execute) + (ql:quickload :fiveam :silent t)) + +(defpackage :passepartout-diagnostics-tests + (:use :cl :fiveam :passepartout) + (:export #:diagnostics-suite)) + +(in-package :passepartout-diagnostics-tests) + +(def-suite diagnostics-suite :description "Verification of the System Diagnostics logic") +(in-suite diagnostics-suite) + +(test test-diagnostics-dependency-fail + "Verify that missing binaries are correctly identified as failures." + (let ((passepartout::*diagnostics-binaries* '("non-existent-binary-123"))) + (is (null (diagnostics-dependencies-check))))) + +(test test-diagnostics-env-fail + "Verify that an invalid MEMEX_DIR triggers a critical failure." + (let ((old-m (uiop:getenv "MEMEX_DIR")) + (old-d (uiop:getenv "PASSEPARTOUT_DATA_DIR"))) + (unwind-protect + (progn + (setf (uiop:getenv "MEMEX_DIR") "/non/existent/path/999") + (is (null (diagnostics-env-check)))) + (setf (uiop:getenv "MEMEX_DIR") (or old-m "")) + (setf (uiop:getenv "PASSEPARTOUT_DATA_DIR") (or old-d ""))))) + (defskill :passepartout-system-diagnostics :priority 100 :trigger (lambda (ctx) (eq (getf (getf ctx :payload) :sensor) :heartbeat)) diff --git a/lisp/system-model-explorer.lisp b/lisp/system-model-explorer.lisp index a33432f..b3df95e 100644 --- a/lisp/system-model-explorer.lisp +++ b/lisp/system-model-explorer.lisp @@ -67,3 +67,43 @@ (:chat . "Casual conversation, Q&A, creative writing. Prefer balanced quality, low latency.\nRecommend: Llama 3.3 70B (strong generalist) or Gemma 4 31B (thinking mode).") (:plan . "Strategic planning, architecture design, complex multi-step reasoning.\nRecommend: Owl Alpha (free, tool use, 1M ctx) or Hermes 3 405B (strongest free reasoning).") (:background . "Heartbeat summaries, delegation responses, tool output filtering. Must be small + fast.\nRecommend: Llama 3.2 3B (131K ctx, fast) or LFM 2.5 1.2B (edge-ready)."))) + +;; REPL-verified: 2026-05-04 +(eval-when (:compile-toplevel :load-toplevel :execute) + (ignore-errors (ql:quickload :fiveam :silent t))) + +(defpackage :passepartout-system-model-explorer-tests + (:use :cl :passepartout) + (:export #:model-explorer-suite)) + +(in-package :passepartout-system-model-explorer-tests) + +(fiveam:def-suite model-explorer-suite :description "Tests for the model explorer skill") + +(fiveam:in-suite model-explorer-suite) + +(fiveam:test model-explorer-recommend-slots + "model-explorer-recommend should return models for all standard slots" + (dolist (slot '(:code :chat :plan :background)) + (let ((recs (passepartout::model-explorer-recommend slot))) + (fiveam:is (listp recs)) + (fiveam:is (>= (length recs) 1))))) + +(fiveam:test model-explorer-recommend-format + "Each recommendation should have :id and :name" + (dolist (rec (passepartout::model-explorer-recommend :chat)) + (fiveam:is (getf rec :id)) + (fiveam:is (getf rec :name)))) + +(fiveam:test model-explorer-recommend-unknown-slot + "Unknown slot should return fallback" + (let ((recs (passepartout::model-explorer-recommend :unknown))) + (fiveam:is (listp recs)) + (fiveam:is (>= (length recs) 1)))) + +(fiveam:test model-explorer-fetch-openrouter-count + "OpenRouter API should return at least 300 models" + (let ((models (passepartout::model-explorer-fetch :openrouter))) + (if models + (fiveam:is (>= (length models) 300)) + (fiveam:skip "API unreachable")))) diff --git a/lisp/system-model-provider.lisp b/lisp/system-model-provider.lisp index 484f76c..4854ac6 100644 --- a/lisp/system-model-provider.lisp +++ b/lisp/system-model-provider.lisp @@ -116,3 +116,20 @@ If API-KEY is nil, reads from environment." (defskill :passepartout-system-model-provider :priority 50 :trigger (lambda (ctx) (declare (ignore ctx)) nil)) + +(eval-when (:compile-toplevel :load-toplevel :execute) + (ql:quickload :fiveam :silent t)) + +(defpackage :passepartout-llm-gateway-tests + (:use :cl :passepartout) + (:export #:llm-gateway-suite)) + +(in-package :passepartout-llm-gateway-tests) + +(fiveam:def-suite llm-gateway-suite :description "Tests for the LLM provider backend") +(fiveam:in-suite llm-gateway-suite) + +(fiveam:test test-provider-rejects-bad-keyword + "Verify that provider-openai-request returns :error for an unregistered provider." + (let ((result (provider-openai-request "hello" "test" :provider :not-a-real-provider))) + (fiveam:is (eq (getf result :status) :error)))) diff --git a/org/core-manifest.org b/org/core-manifest.org index 497f1b0..98b3222 100644 --- a/org/core-manifest.org +++ b/org/core-manifest.org @@ -40,21 +40,7 @@ Components are loaded in sequence (~:serial t~): package first (defines the publ ** Test System -The test system loads on top of ~opencortex~ and adds FiveAM (the test framework). Each test file is tangled from a ~:tangle ../tests/...~ block in the parent org file. - -Note: not every harness or skill file has a corresponding test file. Tests exist only for the parts of the system where deterministic verification is most critical — the pipeline stages, the loader, the memory Merkle tree, and the peripheral vision model. - -#+begin_src lisp -(defsystem :passepartout/tests - :depends-on (:passepartout :fiveam) - :components ((:file "tests/programming-org-tests") - (:file "tests/programming-lisp-tests") - (:file "tests/boot-sequence-tests") - (:file "tests/model-explorer-tests") - (:file "tests/diagnostics-tests") - (:file "tests/tui-tests") - (:file "tests/llm-gateway-tests"))) -#+end_src +Tests are embedded directly in each module's source file — see the `* Test Suite` section at the end of each `.org` file. No separate test system is needed. ** TUI System diff --git a/org/core-skills.org b/org/core-skills.org index c7f5a2c..870d4a6 100644 --- a/org/core-skills.org +++ b/org/core-skills.org @@ -411,7 +411,7 @@ files live after tangling. The org source files live in ~org/~. * Test Suite Verifies that the topological sorter correctly orders skills by their ~#+DEPENDS_ON:~ declarations. -#+begin_src lisp :tangle ../tests/boot-sequence-tests.lisp +#+begin_src lisp (eval-when (:compile-toplevel :load-toplevel :execute) (ql:quickload :fiveam :silent t)) diff --git a/org/gateway-tui-main.org b/org/gateway-tui-main.org index ef59b1c..228c8e1 100644 --- a/org/gateway-tui-main.org +++ b/org/gateway-tui-main.org @@ -185,7 +185,7 @@ Event handlers + daemon I/O + main loop. #+end_src * Test Suite -#+begin_src lisp :tangle ../tests/tui-tests.lisp +#+begin_src lisp (eval-when (:compile-toplevel :load-toplevel :execute) (ql:quickload :fiveam :silent t)) diff --git a/org/programming-lisp.org b/org/programming-lisp.org index e9ab9bd..68d21d6 100644 --- a/org/programming-lisp.org +++ b/org/programming-lisp.org @@ -17,6 +17,11 @@ The skill has four layers: * Implementation +** Package Context +#+begin_src lisp +(in-package :passepartout) +#+end_src + ** Structural Validation ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp @@ -215,7 +220,7 @@ The skill has four layers: * Test Suite Tests for the Lisp Validator structural, syntactic, and semantic gates. -#+begin_src lisp :tangle ../tests/programming-lisp-tests.lisp +#+begin_src lisp (defpackage :passepartout-utils-lisp-tests (:use :cl :fiveam :passepartout) (:export #:utils-lisp-suite)) diff --git a/org/programming-org.org b/org/programming-org.org index 3087357..4a3a34b 100644 --- a/org/programming-org.org +++ b/org/programming-org.org @@ -8,6 +8,11 @@ Structural manipulation tools for Org-mode files. This skill handles reading, wr * Implementation +** Package Context +#+begin_src lisp +(in-package :passepartout) +#+end_src + ** Reading Files (with Privacy Filter) ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp @@ -331,7 +336,7 @@ AST format: (:TYPE :HEADLINE :properties (:ID ... :TITLE ... :TAGS (...)) * Test Suite Verification of the structural manipulation for Org-mode files and their AST representation. -#+begin_src lisp :tangle ../tests/programming-org-tests.lisp +#+begin_src lisp (eval-when (:compile-toplevel :load-toplevel :execute) (ignore-errors (ql:quickload :fiveam :silent t))) diff --git a/org/system-diagnostics.org b/org/system-diagnostics.org index d2ccf03..0e10196 100644 --- a/org/system-diagnostics.org +++ b/org/system-diagnostics.org @@ -243,7 +243,7 @@ The doctor checks all supported LLM providers and detects local Ollama instances * Phase D: Verification (Testing) -#+begin_src lisp :tangle ../tests/diagnostics-tests.lisp +#+begin_src lisp (eval-when (:compile-toplevel :load-toplevel :execute) (ql:quickload :fiveam :silent t)) diff --git a/org/system-model-explorer.org b/org/system-model-explorer.org index ae37ace..35027c4 100644 --- a/org/system-model-explorer.org +++ b/org/system-model-explorer.org @@ -104,7 +104,7 @@ Recommended models are curated per task slot — code generation needs different * Tests -#+begin_src lisp :tangle ../tests/model-explorer-tests.lisp +#+begin_src lisp ;; REPL-verified: 2026-05-04 (eval-when (:compile-toplevel :load-toplevel :execute) (ignore-errors (ql:quickload :fiveam :silent t))) diff --git a/org/system-model-provider.org b/org/system-model-provider.org index 57212fa..5f1d4b9 100644 --- a/org/system-model-provider.org +++ b/org/system-model-provider.org @@ -163,7 +163,7 @@ If API-KEY is nil, reads from environment." #+end_src * Test Suite -#+begin_src lisp :tangle ../tests/llm-gateway-tests.lisp +#+begin_src lisp (eval-when (:compile-toplevel :load-toplevel :execute) (ql:quickload :fiveam :silent t)) diff --git a/passepartout.asd b/passepartout.asd index 6638e5c..4bacbca 100644 --- a/passepartout.asd +++ b/passepartout.asd @@ -16,16 +16,6 @@ (:file "lisp/core-loop-act") (:file "lisp/core-loop"))) -(defsystem :passepartout/tests - :depends-on (:passepartout :fiveam) - :components ((:file "tests/programming-org-tests") - (:file "tests/programming-lisp-tests") - (:file "tests/boot-sequence-tests") - (:file "tests/model-explorer-tests") - (:file "tests/diagnostics-tests") - (:file "tests/tui-tests") - (:file "tests/llm-gateway-tests"))) - (defsystem :passepartout/tui :depends-on (:passepartout :croatoan :usocket :bordeaux-threads) :components ((:file "lisp/gateway-tui")))