v0.3.0 deferred: tab completion, multi-line, /help, activity indicator, context persistence, theming
Some checks failed
Deploy (Gitea) / deploy (push) Failing after 3s
Some checks failed
Deploy (Gitea) / deploy (push) Failing after 3s
- Tab completion: Tab key autocompletes / commands (Tab handler in on-key) - Multi-line input: backslash + Enter inserts literal newline instead of sending - /help command: displays full command listing with descriptions - Activity indicator: :busy flag shows "...thinking" in status bar during LLM wait - Context persistence: context-save/context-load persist *context-stack* to disk (~/.cache/passepartout/context.lisp). Auto-restores on skill load. Added push-context, pop-context, focus-*, unfocus, context-save/load exports. - Theming: *tui-theme* plist with semantic color roles, /theme command View functions (view-chat, view-status, view-input) use theme-color - TUI test suite: 19 tests, 53 checks (100% pass) - Context test suite: 2 tests, 6 checks (100% pass) - Total: 5 suites, 81 checks, 0 failures
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
(in-package :passepartout)
|
||||
|
||||
(defvar *context-stack* nil
|
||||
"Stack of context plists. Each plist has :project, :base-path, :scope.
|
||||
Top of stack (car) is the current context.")
|
||||
@@ -39,6 +41,7 @@ Returns the new context plist."
|
||||
:base-path base-path
|
||||
:scope scope)))
|
||||
(push context *context-stack*)
|
||||
(context-save)
|
||||
(log-message "CONTEXT: Pushed ~a (depth ~d)" project (context-stack-depth))
|
||||
context))
|
||||
|
||||
@@ -47,6 +50,7 @@ Returns the new context plist."
|
||||
Returns the restored context or nil if stack becomes empty."
|
||||
(if *context-stack*
|
||||
(let ((popped (pop *context-stack*)))
|
||||
(context-save)
|
||||
(log-message "CONTEXT: Popped ~a (depth ~d)"
|
||||
(getf popped :project) (context-stack-depth))
|
||||
(current-context))
|
||||
@@ -107,6 +111,46 @@ until stack is empty or :memex context is reached."
|
||||
"Pop the top context and return to the previous one."
|
||||
(pop-context))
|
||||
|
||||
(defvar *context-persistence-file* nil
|
||||
"Path to the context stack persistence file.")
|
||||
|
||||
(defun context-persist-file ()
|
||||
"Returns the full path to the context persistence file."
|
||||
(or *context-persistence-file*
|
||||
(setf *context-persistence-file*
|
||||
(merge-pathnames ".cache/passepartout/context.lisp"
|
||||
(user-homedir-pathname)))))
|
||||
|
||||
(defun context-save ()
|
||||
"Writes *context-stack* to the persistence file."
|
||||
(handler-case
|
||||
(let ((path (context-persist-file)))
|
||||
(ensure-directories-exist (make-pathname :directory (pathname-directory path)))
|
||||
(with-open-file (s path :direction :output :if-exists :supersede
|
||||
:if-does-not-exist :create)
|
||||
(prin1 *context-stack* s))
|
||||
(log-message "CONTEXT: Saved stack (depth ~d) to ~a"
|
||||
(length *context-stack*) path))
|
||||
(error (c)
|
||||
(log-message "CONTEXT: Failed to save: ~a" c))))
|
||||
|
||||
(defun context-load ()
|
||||
"Restores *context-stack* from the persistence file."
|
||||
(handler-case
|
||||
(let ((path (context-persist-file)))
|
||||
(when (probe-file path)
|
||||
(with-open-file (s path :direction :input)
|
||||
(let ((*read-eval* nil)
|
||||
(data (read s nil nil)))
|
||||
(when (listp data)
|
||||
(setf *context-stack* data)
|
||||
(log-message "CONTEXT: Restored stack (depth ~d) from ~a"
|
||||
(length *context-stack*) path))
|
||||
t))))
|
||||
(error (c)
|
||||
(log-message "CONTEXT: Failed to load: ~a" c)
|
||||
nil)))
|
||||
|
||||
(defskill :passepartout-system-context-manager
|
||||
:priority 90
|
||||
:trigger (lambda (ctx) (declare (ignore ctx)) nil)
|
||||
@@ -119,3 +163,40 @@ until stack is empty or :memex context is reached."
|
||||
|
||||
(when (boundp '*scope-resolver*)
|
||||
(setf *scope-resolver* #'current-scope))
|
||||
|
||||
;; Restore persisted context on load
|
||||
(context-load)
|
||||
|
||||
(eval-when (:compile-toplevel :load-toplevel :execute)
|
||||
(ql:quickload :fiveam :silent t))
|
||||
|
||||
(defpackage :passepartout-context-tests
|
||||
(:use :cl :passepartout)
|
||||
(:export #:context-suite))
|
||||
|
||||
(in-package :passepartout-context-tests)
|
||||
|
||||
(fiveam:def-suite context-suite :description "Context manager verification")
|
||||
(fiveam:in-suite context-suite)
|
||||
|
||||
(fiveam:test test-push-pop-context
|
||||
"Contract 1-2: push-context and pop-context maintain stack order."
|
||||
(let ((passepartout::*context-stack* nil))
|
||||
(push-context :project "testapp" :base-path "/tmp" :scope :project)
|
||||
(fiveam:is (= 1 (length passepartout::*context-stack*)))
|
||||
(fiveam:is (string= "testapp" (getf (car passepartout::*context-stack*) :project)))
|
||||
(pop-context)
|
||||
(fiveam:is (null passepartout::*context-stack*))))
|
||||
|
||||
(fiveam:test test-context-save-load
|
||||
"Contract 3-4: context-save and context-load round-trip."
|
||||
(let* ((tmpfile (merge-pathnames "test-context.lisp" (uiop:temporary-directory)))
|
||||
(passepartout::*context-persistence-file* tmpfile)
|
||||
(passepartout::*context-stack* (list '(:project "test" :base-path "/tmp" :scope :project))))
|
||||
(context-save)
|
||||
(fiveam:is (probe-file tmpfile))
|
||||
(setf passepartout::*context-stack* nil)
|
||||
(context-load)
|
||||
(fiveam:is (= 1 (length passepartout::*context-stack*)))
|
||||
(fiveam:is (string= "test" (getf (car passepartout::*context-stack*) :project)))
|
||||
(delete-file tmpfile)))
|
||||
|
||||
Reference in New Issue
Block a user