From 545d968a2a595f707d66cb291602ea0cdce0f40d Mon Sep 17 00:00:00 2001 From: Yoshitsugu Sekine Date: Mon, 11 May 2026 07:33:38 +0900 Subject: [PATCH 1/5] chore: start setup registry regression fix From 91ae4c6d46706bfda98f0b623af04f65378f0754 Mon Sep 17 00:00:00 2001 From: Yoshitsugu Sekine Date: Mon, 11 May 2026 07:23:42 +0900 Subject: [PATCH 2/5] fix: preserve target registry across workspace loads --- enkan-repl.el | 28 +++++++++++++++++--- test/enkan-repl-workspace-create-test.el | 20 +++++++++++++- test/enkan-repl-workspace-state-test.el | 33 ++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 5 deletions(-) diff --git a/enkan-repl.el b/enkan-repl.el index 42f1cda..8c60245 100644 --- a/enkan-repl.el +++ b/enkan-repl.el @@ -411,6 +411,16 @@ ALIASES may be a list of strings or an alist of (alias . project-name)." (or (cdr (assoc alias project-aliases)) alias)) +(defun enkan-repl--merge-target-directories (primary fallback) + "Return target directories from PRIMARY merged with FALLBACK. +Entries in PRIMARY win when both lists contain the same alias. This keeps +workspace-local imported paths authoritative while preserving the user's wider +target directory registry across workspace loads." + (append primary + (cl-remove-if (lambda (entry) + (assoc (car entry) primary)) + fallback))) + (defun enkan-repl--projects-with-current-aliases (projects current-project project-aliases) "Return PROJECTS augmented with CURRENT-PROJECT's PROJECT-ALIASES. Merge the current workspace's aliases into CURRENT-PROJECT's project @@ -460,7 +470,9 @@ This function restores workspace state from the given plist." (setq enkan-repl-project-aliases (plist-get state :project-aliases))) (when (plist-member state :target-directories) (setq enkan-repl-target-directories - (plist-get state :target-directories)))) + (enkan-repl--merge-target-directories + (plist-get state :target-directories) + enkan-repl-target-directories)))) (defun enkan-repl--save-workspace-state (&optional workspace-id) "Save current globals into `enkan-repl--workspaces' under WORKSPACE-ID. @@ -1509,7 +1521,8 @@ COUNTER: session counter" (defun enkan-repl--setup-start-sessions (alias-list buffer-name) "Start terminal sessions for each alias in ALIAS-LIST and log to BUFFER-NAME. -Includes error handling for individual session failures." +Includes error handling for individual session failures. +Returns a plist with `:success-count' and `:failure-count'." (with-current-buffer buffer-name (princ "🚀 Starting terminal sessions:\n")) (let ((session-number 1) @@ -1533,7 +1546,9 @@ Includes error handling for individual session failures." (setq failure-count (1+ failure-count)))) (setq session-number (1+ session-number))) (with-current-buffer buffer-name - (princ (format "\n📊 Session start summary: %d success, %d failed\n\n" success-count failure-count))))) + (princ (format "\n📊 Session start summary: %d success, %d failed\n\n" success-count failure-count))) + (list :success-count success-count + :failure-count failure-count))) (defun enkan-repl--setup-project-session (alias) "Setup project session for given ALIAS. @@ -1600,7 +1615,12 @@ Category: Session Controller" (error "Project '%s' not found" project-name)) (enkan-repl--setup-set-project-aliases project-name alias-list buffer-name) ;; Start sessions - (enkan-repl--setup-start-sessions alias-list buffer-name)) + (let ((start-result + (enkan-repl--setup-start-sessions alias-list buffer-name))) + (when (> (plist-get start-result :failure-count) 0) + (error "Failed to start %d of %d terminal session(s)" + (plist-get start-result :failure-count) + (length alias-list))))) ;; Set final project configuration (enkan-repl--ws-set-current-project project-name) (princ (format "\n✅ Setup completed for project: %s\n" project-name)) diff --git a/test/enkan-repl-workspace-create-test.el b/test/enkan-repl-workspace-create-test.el index b8506f4..cb79aa0 100644 --- a/test/enkan-repl-workspace-create-test.el +++ b/test/enkan-repl-workspace-create-test.el @@ -118,6 +118,24 @@ (should (string= (plist-get ws-state :current-project) "MyProject")) (should (equal (plist-get ws-state :project-aliases) '("alias1")))))) +(ert-deftest test-enkan-repl--setup-start-sessions-reports-failures () + "Session setup should report failures instead of looking successful." + (let ((buffer-name "*enkan-repl-test-setup-start*") + (enkan-repl-target-directories nil)) + (unwind-protect + (with-current-buffer (get-buffer-create buffer-name) + (erase-buffer) + (let* ((standard-output (current-buffer)) + (result (enkan-repl--setup-start-sessions + '("junk") + buffer-name))) + (should (= 0 (plist-get result :success-count))) + (should (= 1 (plist-get result :failure-count))) + (should (string-match-p "Project alias .junk. not found" + (buffer-string))))) + (when (get-buffer buffer-name) + (kill-buffer buffer-name))))) + (ert-deftest test-enkan-repl--setup-window-terminal-buffer-pure-workspace-context () "Test that window eat buffer name includes correct workspace ID." ;; Load window-layouts if exists @@ -149,4 +167,4 @@ (should (string-match-p "\\*ws:02 " (cdr result)))))))) (provide 'enkan-repl-workspace-create-test) -;;; enkan-repl-workspace-create-test.el ends here \ No newline at end of file +;;; enkan-repl-workspace-create-test.el ends here diff --git a/test/enkan-repl-workspace-state-test.el b/test/enkan-repl-workspace-state-test.el index 43d8a2b..b4ddbd3 100644 --- a/test/enkan-repl-workspace-state-test.el +++ b/test/enkan-repl-workspace-state-test.el @@ -73,6 +73,39 @@ (should (equal enkan-repl--session-counter 2)) (should (equal enkan-repl-project-aliases '(("p3" . "proj3") ("p4" . "proj4"))))))) +(ert-deftest test-enkan-repl--load-workspace-state-preserves-target-registry () + "Loading a workspace should not discard unrelated configured targets." + (let ((enkan-repl--workspaces + '(("01" . (:current-project "er" + :session-list ((1 . "enkan-repl")) + :session-counter 1 + :project-aliases (("er" . "enkan-repl")) + :target-directories + (("er" . ("enkan-repl" . "/repo/enkan-repl"))))))) + (enkan-repl--current-workspace "01") + (enkan-repl--current-project nil) + (enkan-repl-session-list nil) + (enkan-repl--session-counter 0) + (enkan-repl-project-aliases nil) + (enkan-repl-target-directories + '(("er" . ("enkan-repl" . "/repo/enkan-repl")) + ("junk" . ("junk" . "/repo/junk"))))) + (enkan-repl--load-workspace-state "01") + (should (equal '(("er" . ("enkan-repl" . "/repo/enkan-repl")) + ("junk" . ("junk" . "/repo/junk"))) + enkan-repl-target-directories)) + (should (equal '("junk" . "/repo/junk") + (enkan-repl--setup-project-session "junk"))))) + +(ert-deftest test-enkan-repl--merge-target-directories-primary-wins () + "Workspace target directories should win over preserved registry entries." + (should (equal '(("er" . ("enkan-repl" . "/state/enkan-repl")) + ("junk" . ("junk" . "/repo/junk"))) + (enkan-repl--merge-target-directories + '(("er" . ("enkan-repl" . "/state/enkan-repl"))) + '(("er" . ("enkan-repl" . "/config/enkan-repl")) + ("junk" . ("junk" . "/repo/junk"))))))) + (ert-deftest test-enkan-repl--save-workspace-state () "Test saving workspace state." (let ((enkan-repl--workspaces nil) From 69e3f172ff56c57dc64f75221bbc3c78d36bf29f Mon Sep 17 00:00:00 2001 From: Yoshitsugu Sekine Date: Mon, 11 May 2026 07:34:29 +0900 Subject: [PATCH 3/5] test: cover setup after workspace deletion --- AGENTS.md | 58 +++++++++++++++++++ ...kan-repl-workspace-delete-deletion-test.el | 34 +++++++++++ 2 files changed, 92 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..08168dc --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,58 @@ +# Repository Guidelines + +## Project Structure & Modules + +- Core: `enkan-repl.el` plus helpers like `enkan-repl-utils.el`, `hmenu.el`. +- Terminal backend abstraction: + - `enkan-repl-terminal.el` — dispatch + eat backend + tmux backend + - `enkan-repl-state.el` — disk persistence + tmux session reconcile +- Workspace: `enkan-repl-workspace.el`, `enkan-repl-workspace-list.el`. +- Sessions: `enkan-repl-sessions.el`. +- macOS-only notifications: `enkan-repl-mac-notify.el`. +- Tests: `test/*.el` ERT suites (e.g., `enkan-repl-core-test.el`). +- Docs & tooling: `docs/` (gitignored design notes), `scripts/` + (doc/constant generators, test loader), `Makefile`. +- Examples: `examples/*.el` for usage patterns. +- Node tooling only for releases (`package.json`, `semantic-release`). + +## Build, Test, and Development Commands + +- `make help`: List available targets. +- `make install-deps`: Install Emacs dev deps (package-lint from MELPA). +- `make test`: Run ERT tests. +- `make compile`: Byte-compile with warnings-as-errors. +- `make checkdoc`: Check docstrings and headers. +- `make lint`: Run `package-lint` on `enkan-repl.el`. +- `make format`: Indent and untabify key files via Emacs. +- `make check`: Run tests, compile, checkdoc, lint, format. +- `make docs`: Generate constants then public API docs. +Tip: Select a specific Emacs with `EMACS=/path/to/emacs make check`. + +## Coding Style & Naming Conventions + +- Language: Emacs Lisp; 2-space indentation, spaces only (`.editorconfig`). +- Naming: Public symbols prefixed `enkan-repl-`; tests mirror names with `*-test.el`. +- Docstrings: Required for all public functions/vars; keep checkdoc clean. +- Linting: Fix `package-lint` and byte-compile warnings before PR. +- Formatting: Run `make format` before committing. + +## Testing Guidelines + +- Framework: ERT; put tests in `test/` and name with `ert-deftest enkan-…`. +- Running: `make test` or `make check`. +- Scope: Prefer pure, deterministic tests; avoid network and external side effects. +- CI: Tests run on Emacs 30.1 (see `.github/workflows/ci.yml`). + +## Commit & Pull Request Guidelines + +- Commits: Use Conventional Commits (e.g., `feat: add region sender`, `fix: escape handling on macOS`). +- Normal route: update `main`, create a topic branch, make an initial empty + commit, and open a pull request before continuing with substantive changes. +- PRs: Describe intent, link issues, include test notes and doc updates if applicable. +- Requirements: `make check` passes; no `.elc` files; keep diffs focused. +- Releases: Automated via semantic-release on `main`; do not bump versions manually. + +## Security & Configuration + +- No secrets required for local development. Node is only for maintainers’ release pipeline. +- Tests must not execute destructive commands; prefer temporary buffers and stubs. diff --git a/test/enkan-repl-workspace-delete-deletion-test.el b/test/enkan-repl-workspace-delete-deletion-test.el index 5e2b6c2..7ea6956 100644 --- a/test/enkan-repl-workspace-delete-deletion-test.el +++ b/test/enkan-repl-workspace-delete-deletion-test.el @@ -44,5 +44,39 @@ (should (equal 1 enkan-repl--session-counter)) (should (equal "other" enkan-repl--current-project))))) +(ert-deftest test-workspace-delete-preserves-target-registry-for-new-setup () + "Deleting a workspace must not make unrelated project aliases unusable." + (let ((enkan-repl--workspaces + '(("01" . (:current-project "junk" + :session-list ((1 . "junk")) + :session-counter 1 + :project-aliases (("junk" . "junk")) + :target-directories + (("junk" . ("junk" . "/repo/junk"))))) + ("02" . (:current-project "er" + :session-list ((1 . "enkan-repl")) + :session-counter 1 + :project-aliases (("er" . "enkan-repl")) + :target-directories + (("er" . ("enkan-repl" . "/repo/enkan-repl"))))))) + (enkan-repl--current-workspace "01") + (enkan-repl-session-list '((1 . "junk"))) + (enkan-repl--session-counter 1) + (enkan-repl--current-project "junk") + (enkan-repl-project-aliases '(("junk" . "junk"))) + (enkan-repl-target-directories + '(("junk" . ("junk" . "/repo/junk")) + ("er" . ("enkan-repl" . "/repo/enkan-repl"))))) + (cl-letf (((symbol-function 'enkan-repl--stop-workspace-terminals) + (lambda (_workspace-id) + (list :buffers-killed 0 :tmux-killed nil)))) + (enkan-repl--delete-workspace-completely "01") + (should (equal "02" enkan-repl--current-workspace)) + (should (equal '(("er" . ("enkan-repl" . "/repo/enkan-repl")) + ("junk" . ("junk" . "/repo/junk"))) + enkan-repl-target-directories)) + (should (equal '("junk" . "/repo/junk") + (enkan-repl--setup-project-session "junk")))))) + (provide 'enkan-repl-workspace-delete-deletion-test) ;;; enkan-repl-workspace-delete-deletion-test.el ends here From fef75dae8668ca5570cb3b01de3ec7de9fa7bb43 Mon Sep 17 00:00:00 2001 From: Yoshitsugu Sekine Date: Mon, 11 May 2026 07:40:30 +0900 Subject: [PATCH 4/5] fix: preserve registry for legacy workspace states --- enkan-repl.el | 24 ++++++++++++-------- test/enkan-repl-workspace-state-test.el | 30 +++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/enkan-repl.el b/enkan-repl.el index 8c60245..0517876 100644 --- a/enkan-repl.el +++ b/enkan-repl.el @@ -810,23 +810,29 @@ This ensures that directory lookups work correctly after workspace switch." ;; Find project directories from files in standard locations (let ((dirs '())) ;; Check standard project locations - (dolist (alias enkan-repl-project-aliases) + (dolist (alias (enkan-repl--project-alias-names enkan-repl-project-aliases)) ;; Try to find project directory from standard locations - (let* ((home-dir (expand-file-name "~")) + (let* ((project-name + (enkan-repl--project-name-for-alias + alias enkan-repl-project-aliases)) + (home-dir (expand-file-name "~")) (common-paths (list - (expand-file-name (format "dev/self/%s" enkan-repl--current-project) home-dir) - (expand-file-name (format "dev/%s" enkan-repl--current-project) home-dir) - (expand-file-name (format "Documents/%s" enkan-repl--current-project) home-dir) - (expand-file-name (format "projects/%s" enkan-repl--current-project) home-dir) - (expand-file-name enkan-repl--current-project default-directory)))) + (expand-file-name (format "dev/self/%s" project-name) home-dir) + (expand-file-name (format "dev/%s" project-name) home-dir) + (expand-file-name (format "Documents/%s" project-name) home-dir) + (expand-file-name (format "projects/%s" project-name) home-dir) + (expand-file-name project-name default-directory)))) ;; Find first existing directory (dolist (path common-paths) (when (and (not (assoc alias dirs)) (file-directory-p path)) - (push (cons alias (cons enkan-repl--current-project path)) dirs))))) + (push (cons alias (cons project-name path)) dirs))))) ;; Update enkan-repl-target-directories if we found directories (when dirs - (setq enkan-repl-target-directories dirs))))) + (setq enkan-repl-target-directories + (enkan-repl--merge-target-directories + (nreverse dirs) + enkan-repl-target-directories)))))) (defun enkan-repl--initialize-default-workspace () "Initialize default workspace '01' with first available project. diff --git a/test/enkan-repl-workspace-state-test.el b/test/enkan-repl-workspace-state-test.el index b4ddbd3..0e3921d 100644 --- a/test/enkan-repl-workspace-state-test.el +++ b/test/enkan-repl-workspace-state-test.el @@ -97,6 +97,36 @@ (should (equal '("junk" . "/repo/junk") (enkan-repl--setup-project-session "junk"))))) +(ert-deftest test-enkan-repl--load-legacy-state-merges-discovered-targets () + "Loading old states without target dirs should preserve the target registry." + (let* ((project-name "enkan-repl-legacy-project") + (temp-root (file-name-as-directory + (make-temp-file "enkan-repl-legacy-state-" t))) + (project-dir (expand-file-name project-name temp-root)) + (default-directory temp-root) + (enkan-repl--workspaces + `(("01" . (:current-project ,project-name + :session-list ((1 . ,project-name)) + :session-counter 1 + :project-aliases (("legacy" . ,project-name)))))) + (enkan-repl--current-workspace "01") + (enkan-repl--current-project nil) + (enkan-repl-session-list nil) + (enkan-repl--session-counter 0) + (enkan-repl-project-aliases nil) + (enkan-repl-target-directories + '(("junk" . ("junk" . "/repo/junk"))))) + (unwind-protect + (progn + (make-directory project-dir) + (enkan-repl--load-workspace-state "01") + (should (equal `(("legacy" . (,project-name . ,project-dir)) + ("junk" . ("junk" . "/repo/junk"))) + enkan-repl-target-directories)) + (should (equal '("junk" . "/repo/junk") + (enkan-repl--setup-project-session "junk")))) + (delete-directory temp-root t)))) + (ert-deftest test-enkan-repl--merge-target-directories-primary-wins () "Workspace target directories should win over preserved registry entries." (should (equal '(("er" . ("enkan-repl" . "/state/enkan-repl")) From 6577d262180501675d12d62f276719b307e9b326 Mon Sep 17 00:00:00 2001 From: phasetr Date: Wed, 13 May 2026 19:38:59 +0900 Subject: [PATCH 5/5] chore: stop tracking local agent instructions --- AGENTS.md | 58 ------------------------------------------------------- 1 file changed, 58 deletions(-) delete mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 08168dc..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,58 +0,0 @@ -# Repository Guidelines - -## Project Structure & Modules - -- Core: `enkan-repl.el` plus helpers like `enkan-repl-utils.el`, `hmenu.el`. -- Terminal backend abstraction: - - `enkan-repl-terminal.el` — dispatch + eat backend + tmux backend - - `enkan-repl-state.el` — disk persistence + tmux session reconcile -- Workspace: `enkan-repl-workspace.el`, `enkan-repl-workspace-list.el`. -- Sessions: `enkan-repl-sessions.el`. -- macOS-only notifications: `enkan-repl-mac-notify.el`. -- Tests: `test/*.el` ERT suites (e.g., `enkan-repl-core-test.el`). -- Docs & tooling: `docs/` (gitignored design notes), `scripts/` - (doc/constant generators, test loader), `Makefile`. -- Examples: `examples/*.el` for usage patterns. -- Node tooling only for releases (`package.json`, `semantic-release`). - -## Build, Test, and Development Commands - -- `make help`: List available targets. -- `make install-deps`: Install Emacs dev deps (package-lint from MELPA). -- `make test`: Run ERT tests. -- `make compile`: Byte-compile with warnings-as-errors. -- `make checkdoc`: Check docstrings and headers. -- `make lint`: Run `package-lint` on `enkan-repl.el`. -- `make format`: Indent and untabify key files via Emacs. -- `make check`: Run tests, compile, checkdoc, lint, format. -- `make docs`: Generate constants then public API docs. -Tip: Select a specific Emacs with `EMACS=/path/to/emacs make check`. - -## Coding Style & Naming Conventions - -- Language: Emacs Lisp; 2-space indentation, spaces only (`.editorconfig`). -- Naming: Public symbols prefixed `enkan-repl-`; tests mirror names with `*-test.el`. -- Docstrings: Required for all public functions/vars; keep checkdoc clean. -- Linting: Fix `package-lint` and byte-compile warnings before PR. -- Formatting: Run `make format` before committing. - -## Testing Guidelines - -- Framework: ERT; put tests in `test/` and name with `ert-deftest enkan-…`. -- Running: `make test` or `make check`. -- Scope: Prefer pure, deterministic tests; avoid network and external side effects. -- CI: Tests run on Emacs 30.1 (see `.github/workflows/ci.yml`). - -## Commit & Pull Request Guidelines - -- Commits: Use Conventional Commits (e.g., `feat: add region sender`, `fix: escape handling on macOS`). -- Normal route: update `main`, create a topic branch, make an initial empty - commit, and open a pull request before continuing with substantive changes. -- PRs: Describe intent, link issues, include test notes and doc updates if applicable. -- Requirements: `make check` passes; no `.elc` files; keep diffs focused. -- Releases: Automated via semantic-release on `main`; do not bump versions manually. - -## Security & Configuration - -- No secrets required for local development. Node is only for maintainers’ release pipeline. -- Tests must not execute destructive commands; prefer temporary buffers and stubs.