summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AGENTS.md90
-rw-r--r--README.md284
-rw-r--r--abbrev_defs1
-rw-r--r--docs/plans/2026-04-09-crm-property-completion-implementation.md144
-rw-r--r--docs/plans/2026-04-09-journal-open-narrowing-implementation.md85
-rw-r--r--docs/plans/2026-04-09-modular-emacs-architecture-design.md81
-rw-r--r--docs/plans/2026-04-09-modular-emacs-refactor.md152
-rw-r--r--docs/plans/2026-04-10-navigation-history-implementation.md118
-rw-r--r--docs/plans/2026-04-10-olivetti-org-design.md57
-rw-r--r--docs/plans/2026-04-10-olivetti-org-implementation.md129
-rw-r--r--docs/plans/2026-04-10-org-refile-design.md70
-rw-r--r--docs/plans/2026-04-10-org-refile-implementation.md108
-rw-r--r--early-init.el14
-rw-r--r--init.el53
-rw-r--r--lisp/ss-agenda.el40
-rw-r--r--lisp/ss-capture.el180
-rw-r--r--lisp/ss-core.el147
-rw-r--r--lisp/ss-crm.el817
-rw-r--r--lisp/ss-denote.el49
-rw-r--r--lisp/ss-gptel.el21
-rw-r--r--lisp/ss-keys.el60
-rw-r--r--lisp/ss-org.el306
-rw-r--r--lisp/ss-ui.el144
-rwxr-xr-xreset6
-rw-r--r--tests/ss-capture-tests.el176
-rw-r--r--tests/ss-crm-tests.el92
-rw-r--r--tests/ss-org-tests.el88
27 files changed, 0 insertions, 3512 deletions
diff --git a/AGENTS.md b/AGENTS.md
deleted file mode 100644
index aa1216e..0000000
--- a/AGENTS.md
+++ /dev/null
@@ -1,90 +0,0 @@
-# AGENTS.md
-
-## Project Purpose
-
-This repository is for the creation and maintenance of Emacs 30 configuration
-files.
-
-## Repository Layout
-
-- `init.el` is the hand-edited runtime entry point.
-- `early-init.el` is the hand-edited early startup file.
-- Runtime implementation lives in domain modules under `lisp/`.
-- Keep the module set reasonably chunky and domain-based:
- `ss-core.el`, `ss-ui.el`, `ss-org.el`, `ss-agenda.el`, `ss-capture.el`,
- `ss-denote.el`, `ss-crm.el`, `ss-gptel.el`, and `ss-keys.el`.
-- The current Org workflow lives under `~/org/` with `journal.org`, `daily/`,
- `projects/`, `areas/`, `areas/people/people.org`, `resources/`, and
- `archives/`.
-- `~/org/` is external to this repository and must already exist.
-- The configuration may open files in `~/org`, but it must not create
- directories, create files, or validate note structure.
-- `~/org/journal.org` is the operational journal. It must already exist. The
- configuration may open it, but it must not create or manage it.
-- `~/org/moc.org` is a normal note. It must already exist. The configuration
- may open it, but it must not create or manage it.
-- `~/org/areas/people/people.org` is the people CRM file. It must already
- exist. The configuration may open it, but it must not create or manage it.
-- Agenda files are discovered by explicitly including `~/org/journal.org` and
- recursively scanning `.org` files under `~/org/projects/`, `~/org/areas/`,
- and `~/org/resources/`.
-- Agenda discovery must exclude `~/org/archives/`.
-- PARA is the organising model for durable notes, and folder placement carries
- meaning.
-- The config includes an experimental `gptel` setup that uses GitHub Copilot
- as the backend.
-- Do not treat `auto-save-list/` as source content.
-
-## Editing Expectations
-
-- Prefer small, focused changes over broad rewrites.
-- Edit `init.el`, `early-init.el`, and the relevant `lisp/` modules directly.
-- Keep `init.el` as the central composition layer with explicit feature
- inclusion.
-- Keep side effects out of require time where practical; modules should expose
- setup functions.
-- Preserve existing Emacs Lisp style and naming where patterns already exist.
-- Avoid unrelated refactors while working on a specific configuration task.
-- Be explicit about GUI-versus-terminal behavior. If a change affects
- `emacs -nw`, avoid moving terminal UI changes earlier in startup unless that
- timing is intentional.
-
-## Validation Expectations
-
-- For Emacs Lisp changes, verify the hand-edited startup path directly.
-- When Emacs is available, prefer a batch check such as
- `emacs --batch -Q --load ./init.el` from the repository root.
-- Keep regression checks aligned with the modular startup path documented in
- `README.md`.
-- For changes that affect terminal Emacs behavior, verify in an actual
- `emacs -nw` session as well as batch mode; batch load alone will not catch
- interactive tty regressions.
-
-## Git Workflow Expectations
-
-- Use a git-based workflow for repository changes.
-- Assume changes will be committed incrementally as the work progresses.
-- Keep changes small and focused so they can be reviewed and committed
- independently.
-- Treat verification as the gate for each commit-sized unit of work.
-- After verification passes for a unit of work, summarize the result and prompt
- the user before creating the commit.
-- Keep commit messages concise and specific to the verified change.
-- Do not bundle unrelated changes into the same commit.
-
-## Documentation Expectations
-
-- Document non-obvious conventions close to the relevant Lisp module.
-- Update `README.md` whenever configuration or workflow changes alter package
- usage, startup behavior, keybindings, directory layout, capture flow, or
- other documented behavior.
-- `README.md` must describe the current configuration truthfully. Do not leave
- stale documentation behind and do not document planned behavior as current
- behavior.
-- Keep `README.md` and `AGENTS.md` in sync with the modular architecture.
-- Use `~/org` consistently when describing the external notes directory.
-- Before claiming a change is complete or asking to commit it, review whether
- `README.md` needs an update; if it does not, say so explicitly in the
- summary.
-- Keep `AGENTS.md` concise and update it only when the repository structure or
- working rules actually change.
diff --git a/README.md b/README.md
deleted file mode 100644
index da53922..0000000
--- a/README.md
+++ /dev/null
@@ -1,284 +0,0 @@
-# Emacs Configuration
-
-This repository contains a modular Emacs configuration built around Org mode,
-Denote, a PARA-style note layout, a people CRM, and a small completion stack.
-`init.el` is the hand-edited entry point, `early-init.el` handles true early
-startup concerns, and the runtime implementation is hand-edited across the
-domain modules under `lisp/`.
-
-## System Model
-
-This repository configures Emacs. It does not define, create, or validate the
-`~/org` note system.
-
-- `init.el` is the source of truth for runtime composition and feature
- selection.
-- `early-init.el` is the source of truth for true early startup settings.
-- `lisp/ss-*.el` is the source of truth for the runtime implementation by
- domain.
-- `~/org` is external to this repository and must already exist.
-- The configuration may open files in `~/org`, but it must not create
- directories, create files, or validate note structure.
-- `~/org/journal.org` is the operational journal. It must already exist, and
- the configuration may open it but must not create or manage it.
-- `~/org/moc.org` is a normal note. It must already exist, and the
- configuration may open it but must not create or manage it.
-- `~/org/areas/people/people.org` is the people CRM file. It must already
- exist, and the configuration may open it but must not create or manage it.
-- PARA is the organising model for durable notes. Folder placement carries
- meaning, and workflows must respect that placement.
-
-## Repository Layout
-
-The runtime architecture is:
-
-```text
-early-init.el
-init.el
-lisp/
- ss-core.el
- ss-ui.el
- ss-org.el
- ss-agenda.el
- ss-capture.el
- ss-denote.el
- ss-crm.el
- ss-gptel.el
- ss-keys.el
-```
-
-The module responsibilities are:
-
-- `ss-core.el` bootstraps packages, defines shared paths and helpers, and
- applies shared editor defaults.
-- `ss-ui.el` owns theme, fonts, frame behavior, modeline, and completion UI.
-- `ss-org.el` owns base Org setup, startup MOC behavior, and shared note
- helpers.
-- `ss-agenda.el` owns agenda discovery and agenda commands.
-- `ss-capture.el` owns journal capture structure and capture templates.
-- `ss-denote.el` owns Denote setup and durable-note capture helpers.
-- `ss-crm.el` owns all people CRM behavior.
-- `ss-gptel.el` owns the experimental GitHub Copilot-backed `gptel` setup.
-- `ss-keys.el` owns global keybindings only.
-
-`init.el` enables high-level features centrally through `ss-enabled-features`.
-Feature toggling works by including or excluding a module there.
-
-## Package Model
-
-The config bootstraps packages with built-in `package.el` and uses
-`use-package` for declaration and load order. Package archives are configured
-with GNU, NonGNU ELPA, and MELPA, with GNU given highest priority.
-
-The current setup uses:
-
-- `org` and `org-capture` for agenda and journal capture
-- `denote` for durable notes, naming, keywords, and linking
-- `git-auto-commit-mode` for optional automatic commits inside `~/org` when
- enabled by directory-local settings
-- `vertico` for minibuffer completion UI
-- `orderless` for flexible completion matching
-- `marginalia` for minibuffer annotations
-- `corfu` for in-buffer completion popups in text and Org buffers
-- `olivetti` for centered writing layout in Org buffers
-- `gptel` with the GitHub Copilot backend as an experimental tool
-- `dired` with a macOS-safe `ls` configuration
-- `time` for the modeline clock
-- `modus-themes`, using `modus-vivendi`
-
-## Org Layout
-
-The note system lives under `~/org/` and is organised like this:
-
-- `journal.org` for the operational journal
-- `daily/` for older daily Org files that may still exist
-- `projects/` for project notes
-- `areas/` for area notes
-- `areas/people/people.org` for the people CRM
-- `resources/` for reference material
-- `archives/` for archived notes
-
-Agenda discovery is rule-based:
-
-- include `~/org/journal.org`
-- recursively scan `.org` files under `~/org/projects/`, `~/org/areas/`, and
- `~/org/resources/`
-- exclude `~/org/archives/`
-
-## People CRM
-
-The people workflow is a CRM rooted at `~/org/areas/people/people.org`.
-
-- each top-level heading represents one person
-- entries are structured around heading text and flat properties
-- the system rebuilds people abbrevs from the CRM file
-- a CAPF provides canonical-name completion while alias matching remains
- available for lookup
-- Marginalia annotates people with `role | team | engagement | current focus`
-- reports are available by role, team, manager, engagement, supplier, and
- location
-- `TEAM` captures the current working team and `MANAGER` captures the formal
- organisational manager
-- person cards use `ROLE`, `TEAM`, `MANAGER`, `ENGAGEMENT`, `SUPPLIER`,
- `LOCATION`, and `CURRENT_FOCUS` in that order
-
-The CRM commands are:
-
-- `M-x ss-crm-open`
-- `M-x ss-crm-overview`
-- `M-x ss-crm-find`
-- `M-x ss-crm-insert-name`
-- `M-x ss-crm-insert-summary`
-- `M-x ss-crm-add`
-- `M-x ss-crm-report-by-role`
-- `M-x ss-crm-report-by-team`
-- `M-x ss-crm-report-by-manager`
-- `M-x ss-crm-report-by-engagement`
-- `M-x ss-crm-report-by-supplier`
-- `M-x ss-crm-report-by-location`
-
-Persistent abbrevs live in `abbrev_defs` at the repository root. The config
-loads that file on startup, enables abbrev mode only in text-like buffers, and
-saves learned abbrevs back to the same file silently when buffers are saved.
-People-specific abbrevs are rebuilt from `~/org/areas/people/people.org`
-whenever that file changes.
-
-## Workflow
-
-`~/org/moc.org` is a normal note. It is treated as a curated navigation note,
-not a generated system file. The config may open it on startup, and `C-c n M`
-opens it manually.
-
-The capture model has two distinct paths:
-
-- fast operational capture goes to `~/org/journal.org`
-- durable notes use Denote in the PARA directories under `~/org/`
-
-`~/org/journal.org` remains the operational capture surface. Journal capture
-uses a Year -> Day outline in `journal.org` with explicit `Tasks`, `Notes`,
-and `Meetings` headings beneath each day entry.
-
-Org buffers enable `olivetti-mode` automatically for a centered writing layout
-in both GUI and terminal Emacs sessions.
-
-Invisible edits in Org are blocked with an error rather than silently changing
-hidden content.
-
-Questions that come up during the day can be tracked as Org tasks under the
-day's `Tasks` heading. The intended task workflow is `TODO`, `CLARIFY`,
-`WAIT`, `DONE`, and `CANCELLED`: use `CLARIFY` for open questions or ambiguity,
-and `WAIT` once the question has been asked and the answer is pending.
-Completing an item prompts for a note so the answer can be recorded in the
-task log, and those state logs are stored in a `LOGBOOK` drawer.
-
-Org refile uses the current `org-agenda-files` set as its target space and can
-move entries to any heading within those files. Refile selection uses full
-outline paths, including the file name, so the existing Vertico, Orderless, and
-Marginalia stack can present a clearer path-based destination prompt.
-
-The configured capture templates cover:
-
-- journal tasks
-- journal notes
-- journal meetings
-- Denote-backed captures for generic notes, projects, areas, and resources
-
-The people CRM remains outside `org-capture`: `M-x ss-crm-add` writes directly
-to `~/org/areas/people/people.org`.
-
-Navigation history adds a small browser-style back and forward layer on top of
-mark-based movement. It records significant note jumps from the custom journal
-and MOC commands, CRM find/open commands, `org-open-at-point`, common agenda
-jumps, and `denote-open-or-create`, while ignoring ordinary cursor motion and
-same-location no-ops.
-
-## Keybindings
-
-The main bindings are:
-
-- `C-c a` for the agenda
-- `C-c b` to move back through recorded note and mark navigation history
-- `c` inside the agenda dispatcher to show the custom `Clarify items` view for
- `CLARIFY` tasks
-- `C-c c` for capture
-- `C-c f` to move forward again after using navigation back
-- `C-c n n` to open or create a Denote note
-- `C-c n l` to insert a Denote link
-- `C-c n j` to open the full `~/org/journal.org` buffer
-- `C-c n M` to open the MOC
-- `C-c n d` to open today's journal entry in a focused session; when today's
- entry does not yet exist, the command creates it using the normal journal
- datetree structure, then narrows to that entry; inside the session,
- `C-c C-c` saves and dismisses, and `C-c C-k` dismisses without auto-saving
-- `C-c n p` to open the people CRM
-- `C-c n P` to add a new person card
-- `C-c n f` to find a person card
-- `C-c n i` to insert a canonical person name
-- `C-c n I` to insert a compact person summary
-- `C-c n o` to restore the people overview
-- `C-c n O` to show people grouped by role
-- `C-c n T` to show people grouped by team
-- `C-c n R` to show people grouped by manager
-- `C-c n E` to show people grouped by engagement
-- `C-c n S` to show people grouped by supplier
-- `C-c n L` to show people grouped by location
-- `C-c n g` to start `gptel`
-- `C-c n s` to send in `gptel`
-- `C-c n r` to rewrite with `gptel`
-- `C-c n a` to add context in `gptel`
-
-## Automatic Note Commits
-
-The configuration provides `git-auto-commit-mode` capability. Behaviour is
-defined in `~/org/.dir-locals.el`.
-
-When enabled in `~/org/.dir-locals.el`, saving a file in `~/org/` makes Emacs
-try to commit that change. The Emacs config supplies the package and selects
-the shell command separator based on the active shell, while the note tree
-defines add, push, debounce, and commit-message behavior.
-
-Place this file at `~/org/.dir-locals.el`:
-
-```emacs-lisp
-((nil .
- ((eval .
- (progn
- (setq-local gac-automatically-add-new-files-p t
- gac-automatically-push-p t
- gac-debounce-interval 60
- gac-default-message
- (lambda (_filename)
- (format-time-string "Auto-commit: %Y-%m-%d %H:%M:%S")))
- (git-auto-commit-mode 1))))))
-```
-
-## Validation
-
-The primary verification command for the runtime path is:
-
-```sh
-emacs --batch -Q --load ./init.el
-```
-
-`early-init.el` and `init.el` are hand-edited source files, not generated
-artifacts.
-
-## Terminal and GUI Behavior
-
-GUI Emacs and terminal Emacs are handled slightly differently.
-
-- GUI frames get the preferred frame size, font setup, and UI trimming.
-- In `emacs -nw`, the menu bar is disabled on `emacs-startup-hook` rather than
- earlier in startup, because changing that timing too early caused
- interactive terminal regressions in kitty.
-
-If you change terminal behavior, test it in a real `emacs -nw` session. Batch
-load checks are necessary, but they are not enough for tty input and UI
-behavior.
-
-## Maintenance Rules
-
-- Edit `init.el`, `early-init.el`, and `lisp/ss-*.el` directly.
-- Keep this README aligned with the current configuration.
-- Keep `README.md` and `AGENTS.md` in sync.
-- Do not document planned behavior as if it already exists.
diff --git a/abbrev_defs b/abbrev_defs
deleted file mode 100644
index 64bee34..0000000
--- a/abbrev_defs
+++ /dev/null
@@ -1 +0,0 @@
-;;-*-coding: utf-8;-*-
diff --git a/docs/plans/2026-04-09-crm-property-completion-implementation.md b/docs/plans/2026-04-09-crm-property-completion-implementation.md
deleted file mode 100644
index 88b3d01..0000000
--- a/docs/plans/2026-04-09-crm-property-completion-implementation.md
+++ /dev/null
@@ -1,144 +0,0 @@
-# CRM Property Completion Implementation Plan
-
-> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
-
-**Goal:** Add data-driven CRM property completion and inline value creation to `ss-crm-add` without introducing a second source of truth.
-
-**Architecture:** Extend `lisp/ss-crm.el` with small lookup helpers that build on the existing parsed CRM entry cache, plus a single generic choice reader that handles blank selection, optional freeform values, confirmation, and advisory duplicate warnings. Keep seeded vocabularies in code, wire field-specific readers into `ss-crm-add`, and verify with focused ERT coverage plus batch startup loading.
-
-**Tech Stack:** Emacs Lisp, Org, ERT, batch Emacs verification
-
----
-
-### Task 1: Add failing CRM helper tests
-
-**Files:**
-- Create: `tests/ss-crm-tests.el`
-- Modify: `lisp/ss-crm.el`
-
-**Step 1: Write the failing test**
-
-```elisp
-(ert-deftest ss-crm-known-property-values-sorts-and-deduplicates ()
- (cl-letf (((symbol-function 'ss-crm-entries)
- (lambda ()
- (list (list :role "Engineer")
- (list :role " engineer ")
- (list :role "Architect")
- (list :role nil)))))
- (should (equal (ss-crm-known-property-values "ROLE")
- '("Architect" "Engineer" " engineer ")))))
-```
-
-**Step 2: Run test to verify it fails**
-
-Run: `emacs --batch -Q -L . -L lisp -l tests/ss-crm-tests.el -f ert-run-tests-batch-and-exit`
-Expected: FAIL because the new CRM helper functions do not exist yet.
-
-**Step 3: Write minimal implementation**
-
-```elisp
-(defun ss-crm-known-property-values (property)
- ...)
-```
-
-**Step 4: Run test to verify it passes**
-
-Run: `emacs --batch -Q -L . -L lisp -l tests/ss-crm-tests.el -f ert-run-tests-batch-and-exit`
-Expected: PASS for the helper coverage.
-
-**Step 5: Commit**
-
-```bash
-git add tests/ss-crm-tests.el lisp/ss-crm.el
-git commit -m "Add CRM property completion helpers"
-```
-
-### Task 2: Add completion-reader tests and implement prompt behavior
-
-**Files:**
-- Modify: `tests/ss-crm-tests.el`
-- Modify: `lisp/ss-crm.el`
-
-**Step 1: Write the failing test**
-
-```elisp
-(ert-deftest ss-crm-read-choice-warns-on-new-case-insensitive-duplicate ()
- (let (warning)
- (cl-letf (((symbol-function 'completing-read) (lambda (&rest _) "sydney"))
- ((symbol-function 'yes-or-no-p) (lambda (&rest _) t))
- ((symbol-function 'display-warning)
- (lambda (_type message &rest _) (setq warning message))))
- (should (equal (ss-crm-read-choice "Location: " '("Sydney")
- :allow-blank t
- :allow-new t)
- "sydney"))
- (should (string-match-p "Sydney" warning))))
-```
-
-**Step 2: Run test to verify it fails**
-
-Run: `emacs --batch -Q -L . -L lisp -l tests/ss-crm-tests.el -f ert-run-tests-batch-and-exit`
-Expected: FAIL because `ss-crm-read-choice` does not support the new behavior yet.
-
-**Step 3: Write minimal implementation**
-
-```elisp
-(defun ss-crm-read-choice (prompt choices &rest plist)
- ...)
-```
-
-**Step 4: Run test to verify it passes**
-
-Run: `emacs --batch -Q -L . -L lisp -l tests/ss-crm-tests.el -f ert-run-tests-batch-and-exit`
-Expected: PASS for blank selection, existing completion, and new-value confirmation coverage.
-
-**Step 5: Commit**
-
-```bash
-git add tests/ss-crm-tests.el lisp/ss-crm.el
-git commit -m "Add CRM completion prompts"
-```
-
-### Task 3: Integrate field readers into `ss-crm-add` and verify startup
-
-**Files:**
-- Modify: `lisp/ss-crm.el`
-- Review: `README.md`
-
-**Step 1: Write the failing test**
-
-```elisp
-(ert-deftest ss-crm-read-manager-uses-known-person-names ()
- (cl-letf (((symbol-function 'ss-crm-known-person-names)
- (lambda () '("Alice" "Bob")))
- ((symbol-function 'ss-crm-read-choice)
- (lambda (_prompt choices &rest _plist) choices)))
- (should (equal (ss-crm-read-manager) '("Alice" "Bob")))))
-```
-
-**Step 2: Run test to verify it fails**
-
-Run: `emacs --batch -Q -L . -L lisp -l tests/ss-crm-tests.el -f ert-run-tests-batch-and-exit`
-Expected: FAIL until the field readers are wired up.
-
-**Step 3: Write minimal implementation**
-
-```elisp
-(defun ss-crm-read-manager ()
- (ss-crm-read-choice "Manager: " (ss-crm-known-person-names)
- :allow-blank t
- :require-match t))
-```
-
-**Step 4: Run test to verify it passes**
-
-Run: `emacs --batch -Q -L . -L lisp -l tests/ss-crm-tests.el -f ert-run-tests-batch-and-exit`
-Expected: PASS, then run `emacs --batch -Q --load ./init.el` to confirm startup remains healthy.
-
-**Step 5: Commit**
-
-```bash
-git add tests/ss-crm-tests.el lisp/ss-crm.el README.md
-git commit -m "Guide CRM add-person property entry"
-```
diff --git a/docs/plans/2026-04-09-journal-open-narrowing-implementation.md b/docs/plans/2026-04-09-journal-open-narrowing-implementation.md
deleted file mode 100644
index 59a80de..0000000
--- a/docs/plans/2026-04-09-journal-open-narrowing-implementation.md
+++ /dev/null
@@ -1,85 +0,0 @@
-# Journal Open Narrowing Implementation Plan
-
-> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
-
-**Goal:** Make `ss-open-journal` narrow to today's subtree when today's journal entry exists, while preserving the current fallback when it does not.
-
-**Architecture:** Add focused ERT coverage for the journal-open helper in `tests/`, then update `lisp/ss-org.el` so it widens first, reuses the existing `ss-journal-goto-date` lookup, and narrows only on the successful path. Keep the missing-entry case unchanged by leaving point at the end of the journal buffer without creating new headings.
-
-**Tech Stack:** Emacs Lisp, ERT, batch Emacs verification
-
----
-
-### Task 1: Add failing journal-open test
-
-**Files:**
-- Modify: `tests/ss-capture-tests.el`
-- Modify: `lisp/ss-org.el`
-
-**Step 1: Write the failing test**
-
-```elisp
-(ert-deftest ss-open-journal-narrows-to-today-when-entry-exists ()
- ...)
-```
-
-**Step 2: Run test to verify it fails**
-
-Run: `emacs --batch -Q -L . -L lisp -l tests/ss-capture-tests.el -f ert-run-tests-batch-and-exit`
-Expected: FAIL because `ss-open-journal` currently widens and jumps, but does not narrow.
-
-**Step 3: Write minimal implementation**
-
-```elisp
-(when (ss-journal-goto-date)
- (org-narrow-to-subtree))
-```
-
-**Step 4: Run test to verify it passes**
-
-Run: `emacs --batch -Q -L . -L lisp -l tests/ss-capture-tests.el -f ert-run-tests-batch-and-exit`
-Expected: PASS, with the fallback case still leaving the buffer widened at end of file.
-
-**Step 5: Commit**
-
-```bash
-git add tests/ss-capture-tests.el lisp/ss-org.el
-git commit -m "Narrow journal open to today"
-```
-
-### Task 2: Run regression verification
-
-**Files:**
-- Review: `README.md`
-- Verify: `lisp/ss-org.el`
-
-**Step 1: Write the failing test**
-
-```elisp
-(ert-deftest ss-open-journal-falls-back-to-end-when-today-missing ()
- ...)
-```
-
-**Step 2: Run test to verify it fails**
-
-Run: `emacs --batch -Q -L . -L lisp -l tests/ss-capture-tests.el -f ert-run-tests-batch-and-exit`
-Expected: FAIL until the fallback remains explicitly covered.
-
-**Step 3: Write minimal implementation**
-
-```elisp
-(unless (ss-journal-goto-date)
- (goto-char (point-max)))
-```
-
-**Step 4: Run test to verify it passes**
-
-Run: `emacs --batch -Q -L . -L lisp -l tests/ss-capture-tests.el -l tests/ss-crm-tests.el -f ert-run-tests-batch-and-exit`
-Expected: PASS, then run `emacs --batch -Q --load ./init.el` to confirm startup remains healthy.
-
-**Step 5: Commit**
-
-```bash
-git add tests/ss-capture-tests.el lisp/ss-org.el README.md
-git commit -m "Preserve journal open fallback"
-```
diff --git a/docs/plans/2026-04-09-modular-emacs-architecture-design.md b/docs/plans/2026-04-09-modular-emacs-architecture-design.md
deleted file mode 100644
index 1c2ce14..0000000
--- a/docs/plans/2026-04-09-modular-emacs-architecture-design.md
+++ /dev/null
@@ -1,81 +0,0 @@
-# Modular Emacs Architecture Design
-
-**Date:** 2026-04-09
-
-## Goal
-
-Refactor the repository from a single literate `config.org` source into a
-hand-edited modular Emacs configuration built around `init.el`,
-`early-init.el`, and a small set of domain-based Lisp modules under `lisp/`,
-while preserving existing behavior as closely as possible.
-
-## Scope
-
-This change is an architectural refactor, not a workflow redesign. Existing
-startup behavior, packages, capture flows, agenda rules, CRM commands,
-completion setup, and keybindings should remain materially the same unless a
-small structural adjustment is required by the new module boundaries.
-
-## Architecture
-
-### Entry points
-
-- `early-init.el` remains a standalone file and contains only true early
- startup concerns that must exist before the first GUI frame.
-- `init.el` becomes the hand-edited runtime entry point.
-- `init.el` adds `lisp/` to `load-path`, defines a central
- `ss-enabled-features` list, requires `ss-core`, and conditionally loads and
- sets up each high-level feature module.
-
-### Module boundaries
-
-- `lisp/ss-core.el`
- Owns Emacs version checks, package bootstrap, shared constants and helper
- functions, note-system paths, and small shared editor defaults that other
- modules depend on.
-- `lisp/ss-ui.el`
- Owns theme, fonts, frame behavior, modeline, and terminal-versus-GUI UI
- setup.
-- `lisp/ss-org.el`
- Owns base Org configuration, shared Org helpers, startup MOC behavior, and
- note-opening helpers.
-- `lisp/ss-agenda.el`
- Owns agenda file discovery rules and agenda command setup.
-- `lisp/ss-capture.el`
- Owns journal capture helpers and `org-capture` templates.
-- `lisp/ss-denote.el`
- Owns Denote configuration and note creation helpers.
-- `lisp/ss-crm.el`
- Owns all people CRM logic, including parsing, cache management, abbrevs,
- CAPF, lookup, open/find/insert/add/report commands, and prompt helpers.
-- `lisp/ss-gptel.el`
- Owns the experimental `gptel` integration.
-- `lisp/ss-keys.el`
- Owns central global keybindings only.
-
-### Dependency shape
-
-- `ss-core` is the only unconditional module.
-- Other modules may depend on `ss-core` helpers and shared path constants.
-- `ss-keys` binds commands provided by other modules but should not implement
- workflow logic itself.
-- Side effects should happen in each module's `ss-...-setup` function rather
- than during `require`, except for definitions that are harmless at load time.
-
-## Migration decisions
-
-- Remove `config.org` entirely after extraction. There will be no transitional
- dual system and no compatibility tangling layer.
-- Keep `build` only if it remains useful for the modular setup; otherwise
- remove or repurpose it truthfully.
-- Keep `reset` only if it still aligns with the hand-edited architecture;
- update it so it no longer treats `init.el` or `early-init.el` as generated
- artifacts.
-
-## Validation
-
-- Primary validation: `emacs --batch -Q --load ./init.el`
-- If the refactor preserves terminal-specific UI behavior, also verify in a
- real `emacs -nw` session when practical.
-- Documentation must be updated in the same change so `README.md` and
- `AGENTS.md` describe the modular architecture truthfully.
diff --git a/docs/plans/2026-04-09-modular-emacs-refactor.md b/docs/plans/2026-04-09-modular-emacs-refactor.md
deleted file mode 100644
index ad44ca1..0000000
--- a/docs/plans/2026-04-09-modular-emacs-refactor.md
+++ /dev/null
@@ -1,152 +0,0 @@
-# Modular Emacs Refactor Implementation Plan
-
-> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
-
-**Goal:** Replace the literate `config.org` runtime with a modular hand-edited Emacs configuration centered on `init.el`, `early-init.el`, and `lisp/ss-*.el`.
-
-**Architecture:** Keep `early-init.el` minimal, move reusable runtime logic into domain modules under `lisp/`, and make `init.el` the single composition layer that enables features explicitly and calls one setup function per module. Preserve current behavior unless a structural refactor requires a minimal change.
-
-**Tech Stack:** Emacs Lisp, built-in `package.el`, `use-package`, Org mode, Denote, gptel
-
----
-
-### Task 1: Create the modular file layout
-
-**Files:**
-- Create: `early-init.el`
-- Create: `init.el`
-- Create: `lisp/ss-core.el`
-- Create: `lisp/ss-ui.el`
-- Create: `lisp/ss-org.el`
-- Create: `lisp/ss-agenda.el`
-- Create: `lisp/ss-capture.el`
-- Create: `lisp/ss-denote.el`
-- Create: `lisp/ss-crm.el`
-- Create: `lisp/ss-gptel.el`
-- Create: `lisp/ss-keys.el`
-
-**Step 1: Create `early-init.el` with only early frame settings**
-
-Write the standalone early startup file and keep it limited to frame defaults
-that previously had to exist before the first GUI frame.
-
-**Step 2: Create `init.el` as the composition root**
-
-Add `lisp/` to `load-path`, define `ss-enabled-features`, require `ss-core`,
-and conditionally require each optional module before calling its setup
-function.
-
-**Step 3: Create module skeletons**
-
-Add one `ss-...-setup` function per module and provide each feature.
-
-### Task 2: Extract shared runtime and UI behavior
-
-**Files:**
-- Modify: `lisp/ss-core.el`
-- Modify: `lisp/ss-ui.el`
-
-**Step 1: Move version checks, package bootstrap, shared paths, and editing defaults into `ss-core.el`**
-
-Keep shared constants and helper functions in one place and avoid hidden
-cross-module state.
-
-**Step 2: Move theme, fonts, frame behavior, and modeline setup into `ss-ui.el`**
-
-Preserve the current GUI-versus-terminal behavior and keep side effects in
-`ss-ui-setup`.
-
-### Task 3: Extract Org, agenda, capture, and Denote domains
-
-**Files:**
-- Modify: `lisp/ss-org.el`
-- Modify: `lisp/ss-agenda.el`
-- Modify: `lisp/ss-capture.el`
-- Modify: `lisp/ss-denote.el`
-
-**Step 1: Move shared Org paths and note helpers into `ss-org.el`**
-
-Keep `~/org` invariants unchanged and preserve startup MOC behavior.
-
-**Step 2: Move agenda discovery and agenda command wiring into `ss-agenda.el`**
-
-Preserve explicit include and exclude rules.
-
-**Step 3: Move journal capture helpers and `org-capture` templates into `ss-capture.el`**
-
-Keep the existing templates and journal structure intact.
-
-**Step 4: Move Denote setup into `ss-denote.el`**
-
-Preserve prompts, keywords, and key-facing commands.
-
-### Task 4: Extract CRM and gptel domains
-
-**Files:**
-- Modify: `lisp/ss-crm.el`
-- Modify: `lisp/ss-gptel.el`
-
-**Step 1: Move all CRM logic into `ss-crm.el`**
-
-Keep parsing, cache invalidation, abbrevs, CAPF, reports, and commands
-together in the CRM module only.
-
-**Step 2: Move experimental Copilot-backed gptel setup into `ss-gptel.el`**
-
-Preserve existing commands and defaults.
-
-### Task 5: Centralize keybindings
-
-**Files:**
-- Modify: `lisp/ss-keys.el`
-
-**Step 1: Bind the existing workflow commands in one place**
-
-Move global bindings out of the feature modules so feature inclusion remains
-centralized and explicit.
-
-### Task 6: Update scripts and documentation
-
-**Files:**
-- Modify: `README.md`
-- Modify: `AGENTS.md`
-- Modify or delete: `build`
-- Modify: `reset`
-- Delete: `config.org`
-
-**Step 1: Rewrite documentation to describe the modular architecture truthfully**
-
-Remove stale references to tangling, generated startup files, and `config.org`
-as the source of truth.
-
-**Step 2: Update helper scripts**
-
-Remove or rewrite anything that only made sense for the literate build path.
-
-**Step 3: Remove `config.org`**
-
-Delete the literate source once the extracted runtime files are in place.
-
-### Task 7: Validate the new startup path
-
-**Files:**
-- Verify: `init.el`
-- Verify: `early-init.el`
-- Verify: `lisp/ss-*.el`
-
-**Step 1: Run batch load verification**
-
-Run: `emacs --batch -Q --load ./init.el`
-
-Expected: startup completes without load errors.
-
-**Step 2: Run a terminal startup check if practical**
-
-Run: `emacs -nw`
-
-Expected: terminal UI behavior still matches the previous configuration.
-
-**Step 3: Summarize validation and any intentional behavior changes**
-
-Note any small structural changes required by the refactor, and call out
-whether `README.md` and `AGENTS.md` were updated.
diff --git a/docs/plans/2026-04-10-navigation-history-implementation.md b/docs/plans/2026-04-10-navigation-history-implementation.md
deleted file mode 100644
index 0d58756..0000000
--- a/docs/plans/2026-04-10-navigation-history-implementation.md
+++ /dev/null
@@ -1,118 +0,0 @@
-# Navigation History Implementation Plan
-
-> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
-
-**Goal:** Add small, browser-style back and forward navigation commands for note and mark-based movement across Org, agenda, CRM, and Denote workflows.
-
-**Architecture:** Keep the navigation state and restoration helpers in `lisp/ss-org.el`, using marker-based location records plus a simple back stack and forward stack. Record only significant jumps by wiring the repo's custom note commands and advising common built-in jump commands after they move, while skipping same-location noise and clearing forward history on fresh navigation.
-
-**Tech Stack:** Emacs Lisp, ERT, batch Emacs verification, interactive `emacs -nw` sanity check
-
----
-
-### Task 1: Add failing navigation tests
-
-**Files:**
-- Modify: `tests/ss-capture-tests.el`
-- Modify: `lisp/ss-org.el`
-
-**Step 1: Write the failing tests**
-
-```elisp
-(ert-deftest ss-jump-back-restores-previous-location-and-enables-forward ()
- ...)
-
-(ert-deftest ss-navigation-push-current-location-clears-forward-on-fresh-jump ()
- ...)
-```
-
-**Step 2: Run test to verify it fails**
-
-Run: `emacs --batch -Q -L . -L lisp -l tests/ss-capture-tests.el -f ert-run-tests-batch-and-exit`
-Expected: FAIL because the navigation stack commands and helper functions do not exist yet.
-
-**Step 3: Write minimal implementation**
-
-```elisp
-(defvar ss-navigation-back-stack nil)
-(defvar ss-navigation-forward-stack nil)
-...
-```
-
-**Step 4: Run test to verify it passes**
-
-Run: `emacs --batch -Q -L . -L lisp -l tests/ss-capture-tests.el -f ert-run-tests-batch-and-exit`
-Expected: PASS for the new stack behavior tests.
-
-**Step 5: Commit**
-
-```bash
-git add tests/ss-capture-tests.el lisp/ss-org.el
-git commit -m "Add navigation history stack"
-```
-
-### Task 2: Wire note and jump commands into history
-
-**Files:**
-- Modify: `lisp/ss-org.el`
-- Modify: `lisp/ss-keys.el`
-
-**Step 1: Write the failing test**
-
-```elisp
-(ert-deftest ss-navigation-jump-wrapper-records-pre-jump-location ()
- ...)
-```
-
-**Step 2: Run test to verify it fails**
-
-Run: `emacs --batch -Q -L . -L lisp -l tests/ss-capture-tests.el -f ert-run-tests-batch-and-exit`
-Expected: FAIL because custom navigation commands and advised jump commands do not record history yet.
-
-**Step 3: Write minimal implementation**
-
-```elisp
-(defun ss-navigation-record-before-command (&rest _)
- ...)
-
-(advice-add 'org-open-at-point :before #'ss-navigation-record-before-command)
-```
-
-**Step 4: Run test to verify it passes**
-
-Run: `emacs --batch -Q -L . -L lisp -l tests/ss-capture-tests.el -l tests/ss-crm-tests.el -f ert-run-tests-batch-and-exit`
-Expected: PASS, with the navigation helpers loaded cleanly beside the existing CRM and capture tests.
-
-**Step 5: Commit**
-
-```bash
-git add lisp/ss-org.el lisp/ss-keys.el tests/ss-capture-tests.el
-git commit -m "Wire note jumps into navigation history"
-```
-
-### Task 3: Update docs and verify startup behavior
-
-**Files:**
-- Modify: `README.md`
-- Review: `AGENTS.md`
-
-**Step 1: Update docs**
-
-Add the new `C-c b` and `C-c f` bindings plus a short explanation of what participates in navigation history.
-
-**Step 2: Run verification**
-
-Run: `emacs --batch -Q --load ./init.el`
-Expected: PASS with the updated navigation code loaded through the normal startup path.
-
-**Step 3: Run interactive sanity check**
-
-Run: `emacs -nw`
-Expected: manual verification that MOC, journal, and note jumps can go back and forward, and that a fresh jump clears forward history.
-
-**Step 4: Commit**
-
-```bash
-git add README.md
-git commit -m "Document navigation history bindings"
-```
diff --git a/docs/plans/2026-04-10-olivetti-org-design.md b/docs/plans/2026-04-10-olivetti-org-design.md
deleted file mode 100644
index 863ca65..0000000
--- a/docs/plans/2026-04-10-olivetti-org-design.md
+++ /dev/null
@@ -1,57 +0,0 @@
-# Olivetti Org Design
-
-## Context
-
-The configuration is modular:
-
-- `lisp/ss-ui.el` owns visual packages and interface defaults.
-- `lisp/ss-org.el` owns Org-specific setup and hooks.
-- `init.el` composes modules centrally through `ss-enabled-features`.
-
-The requested behavior is to add `olivetti-mode` to the configuration and
-enable it automatically for Org buffers in both GUI Emacs and `emacs -nw`.
-
-## Options Considered
-
-### 1. Recommended: split package ownership and activation by module
-
-- Declare and configure `olivetti` in `lisp/ss-ui.el`.
-- Enable `olivetti-mode` from `org-mode-hook` in `lisp/ss-org.el`.
-
-This matches the repository boundaries: visual package ownership remains in the
-UI module, while Org-specific behavior remains in the Org module.
-
-### 2. Put everything in `lisp/ss-org.el`
-
-- Add the package declaration and the hook together in the Org module.
-
-This is workable but muddies module boundaries by making the Org module own a
-general presentation package.
-
-### 3. Add a new writing-focused module
-
-- Introduce a dedicated module for prose layout and writing helpers.
-
-This is clean only if more writing-mode features are expected soon. For a
-single package addition, it adds unnecessary structure.
-
-## Chosen Design
-
-Use option 1.
-
-- Add `olivetti` in `lisp/ss-ui.el` with a modest body width that works in GUI
- and terminal frames.
-- Enable `olivetti-mode` automatically in Org buffers from `lisp/ss-org.el`.
-- Do not enable it globally or for non-Org buffers.
-- Keep startup order unchanged.
-
-## Verification
-
-- Run `emacs --batch -Q --load ./init.el` from the repository root.
-- Run an actual terminal Emacs startup check with `emacs -nw` loading this
- configuration, since batch mode alone will not catch tty regressions.
-
-## Documentation Impact
-
-`README.md` should be updated so the package list and Org behavior describe the
-new default truthfully.
diff --git a/docs/plans/2026-04-10-olivetti-org-implementation.md b/docs/plans/2026-04-10-olivetti-org-implementation.md
deleted file mode 100644
index 7853be3..0000000
--- a/docs/plans/2026-04-10-olivetti-org-implementation.md
+++ /dev/null
@@ -1,129 +0,0 @@
-# Olivetti Org Implementation Plan
-
-> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
-
-**Goal:** Add `olivetti` to the Emacs config and enable it automatically for Org buffers in GUI and terminal sessions.
-
-**Architecture:** Keep visual package ownership in `lisp/ss-ui.el` and Org-specific activation in `lisp/ss-org.el`. Update `README.md` so the package model and Org behavior remain accurate, then verify the hand-edited startup path in batch and terminal Emacs.
-
-**Tech Stack:** Emacs Lisp, `use-package`, Org mode, `olivetti`
-
----
-
-### Task 1: Add the visual package setup
-
-**Files:**
-- Modify: `lisp/ss-ui.el`
-
-**Step 1: Write the failing test**
-
-For this configuration-only change, there is no existing automated test harness
-covering package declarations or minor-mode activation. Use startup
-verification as the regression check for this unit.
-
-**Step 2: Run test to verify it fails**
-
-Not applicable for this repository layout. The useful red state is the absence
-of `olivetti` configuration in the current source.
-
-**Step 3: Write minimal implementation**
-
-- Add a `use-package olivetti` declaration to `ss-ui`.
-- Set a conservative width such as `olivetti-body-width 100`.
-
-**Step 4: Run test to verify it passes**
-
-Run: `emacs --batch -Q --load ./init.el`
-Expected: startup completes without Lisp errors.
-
-**Step 5: Commit**
-
-Wait for verification and user approval before creating a commit.
-
-### Task 2: Enable Olivetti for Org buffers
-
-**Files:**
-- Modify: `lisp/ss-org.el`
-
-**Step 1: Write the failing test**
-
-Use the same configuration exception as Task 1. There is no existing targeted
-test scaffold for `org-mode-hook` behavior here.
-
-**Step 2: Run test to verify it fails**
-
-Not applicable. The current source does not add `olivetti-mode` to
-`org-mode-hook`.
-
-**Step 3: Write minimal implementation**
-
-- Extend the existing `org-mode-hook` lambda to enable `olivetti-mode`.
-
-**Step 4: Run test to verify it passes**
-
-Run: `emacs --batch -Q --load ./init.el`
-Expected: startup completes without Lisp errors.
-
-Run: `emacs -nw --eval '(progn (load-file \"./init.el\") (with-current-buffer (get-buffer-create \"*olivetti-check*\") (org-mode) (princ (if olivetti-mode \"olivetti-on\" \"olivetti-off\"))))'`
-Expected: output includes `olivetti-on`.
-
-**Step 5: Commit**
-
-Wait for verification and user approval before creating a commit.
-
-### Task 3: Update documentation
-
-**Files:**
-- Modify: `README.md`
-
-**Step 1: Write the failing test**
-
-The failing condition is documentation drift: the current README does not list
-`olivetti` or mention that Org buffers enable it automatically.
-
-**Step 2: Run test to verify it fails**
-
-Review `README.md` and confirm it lacks that behavior.
-
-**Step 3: Write minimal implementation**
-
-- Add `olivetti` to the package model.
-- Add a short note in the Org workflow description that Org buffers enable
- `olivetti-mode` for centered writing layout.
-
-**Step 4: Run test to verify it passes**
-
-Review the updated README text for accuracy against the code.
-
-**Step 5: Commit**
-
-Wait for verification and user approval before creating a commit.
-
-### Task 4: Verify the complete change
-
-**Files:**
-- Verify only
-
-**Step 1: Write the failing test**
-
-Use the repository’s expected verification path rather than adding new tests.
-
-**Step 2: Run test to verify it fails**
-
-Not applicable before implementation.
-
-**Step 3: Write minimal implementation**
-
-No code changes in this task.
-
-**Step 4: Run test to verify it passes**
-
-Run: `emacs --batch -Q --load ./init.el`
-Expected: exits successfully.
-
-Run: `emacs -nw --eval '(progn (load-file \"./init.el\") (with-temp-buffer (org-mode) (princ (if olivetti-mode \"olivetti-on\" \"olivetti-off\"))))'`
-Expected: prints `olivetti-on`.
-
-**Step 5: Commit**
-
-Wait for verification and user approval before creating a commit.
diff --git a/docs/plans/2026-04-10-org-refile-design.md b/docs/plans/2026-04-10-org-refile-design.md
deleted file mode 100644
index ae7e881..0000000
--- a/docs/plans/2026-04-10-org-refile-design.md
+++ /dev/null
@@ -1,70 +0,0 @@
-# Org Refile Design
-
-## Context
-
-The configuration already has the core minibuffer completion stack in place:
-
-- `lisp/ss-ui.el` enables Vertico, Orderless, and Marginalia globally.
-- `lisp/ss-org.el` owns base Org configuration.
-- `lisp/ss-agenda.el` already discovers `org-agenda-files` from the journal and
- PARA directories.
-
-The missing piece is Org refile configuration. The requested behavior is to
-refile Org entries to any heading in the current `org-agenda-files`, using a
-single, path-oriented prompt that works well with the existing Vertico-based
-completion UI.
-
-## Options Considered
-
-### 1. Recommended: configure built-in Org refile against `org-agenda-files`
-
-- Use Org's standard `org-refile` command and target machinery.
-- Refresh `org-agenda-files` before refile so the target set stays aligned with
- the existing agenda discovery rules.
-- Configure path-based completion so Vertico and Orderless present the target
- list cleanly.
-
-This keeps the workflow conventional, reuses existing repository structure, and
-avoids maintaining a parallel refile implementation.
-
-### 2. Add a custom wrapper command with richer target formatting
-
-- Build a custom candidate list for headings across agenda files.
-- Pass the chosen destination back into Org's refile internals.
-
-This could show more custom metadata, but it duplicates behavior Org already
-provides and increases maintenance cost for little practical gain.
-
-### 3. Add more completion packages just for refile
-
-- Introduce a Vertico extension or a separate package to alter refile prompts.
-
-This adds package surface area without first exhausting the built-in Org and
-completion capabilities already present in the config.
-
-## Chosen Design
-
-Use option 1.
-
-- Configure `org-refile-targets` to use `org-agenda-files` with unrestricted
- heading depth.
-- Enable outline-path completion so identically named headings are
- distinguishable by their parent path.
-- Use the direct, path-based completion flow rather than an additional outline
- navigation step.
-- Refresh `org-agenda-files` before refile by reusing the existing agenda file
- discovery helper instead of copying the directory rules.
-- Keep the change inside the existing module boundaries: Org behavior in
- `lisp/ss-org.el`, with a UI tweak in `lisp/ss-ui.el` only if the current
- completion categories need one.
-
-## Verification
-
-- Add focused ERT coverage for the refile setup helper and variable values.
-- Run `emacs --batch -Q --load ./init.el` from the repository root.
-
-## Documentation Impact
-
-`README.md` should be updated to describe that Org refile targets any heading in
-`org-agenda-files` and uses the configured minibuffer completion stack for
-path-based target selection. \ No newline at end of file
diff --git a/docs/plans/2026-04-10-org-refile-implementation.md b/docs/plans/2026-04-10-org-refile-implementation.md
deleted file mode 100644
index 687e86d..0000000
--- a/docs/plans/2026-04-10-org-refile-implementation.md
+++ /dev/null
@@ -1,108 +0,0 @@
-# Org Refile Implementation Plan
-
-> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
-
-**Goal:** Configure Org refile so entries can be moved to any heading in `org-agenda-files` with a clearer Vertico-friendly path-based prompt.
-
-**Architecture:** Reuse the existing agenda discovery helper to keep `org-agenda-files` current before refile, then configure built-in Org refile variables in `lisp/ss-org.el`. Add narrow ERT coverage for the helper and settings, and update `README.md` so the documented workflow matches the configuration.
-
-**Tech Stack:** Emacs Lisp, Org mode, ERT, Vertico, Orderless, Marginalia
-
----
-
-## Chunk 1: Refile Configuration
-
-### Task 1: Add refile refresh helper and Org settings
-
-**Files:**
-- Modify: `lisp/ss-org.el`
-- Reference: `lisp/ss-agenda.el`
-
-- [ ] **Step 1: Write the failing test**
-
-Add ERT coverage that loads `ss-org`, stubs agenda refresh behavior, runs the
-new setup helper, and asserts:
-
-- `org-refile-targets` points at `org-agenda-files`
-- unlimited heading depth is enabled
-- outline-path completion is enabled
-- the direct path completion flow is selected
-
-- [ ] **Step 2: Run test to verify it fails**
-
-Run: `emacs --batch -Q -L lisp -l tests/ss-org-tests.el -f ert-run-tests-batch-and-exit`
-Expected: FAIL because the refile helper and settings do not exist yet.
-
-- [ ] **Step 3: Write minimal implementation**
-
-- Add a small helper in `lisp/ss-org.el` that refreshes `org-agenda-files`
- before refile, reusing `ss-refresh-org-agenda-files` when available.
-- Configure Org refile variables during `ss-org-setup`.
-
-- [ ] **Step 4: Run test to verify it passes**
-
-Run: `emacs --batch -Q -L lisp -l tests/ss-org-tests.el -f ert-run-tests-batch-and-exit`
-Expected: PASS.
-
-- [ ] **Step 5: Commit**
-
-Wait for verification and user approval before creating a commit.
-
-## Chunk 2: Documentation and Startup Verification
-
-### Task 2: Document the refile workflow
-
-**Files:**
-- Modify: `README.md`
-
-- [ ] **Step 1: Write the failing test**
-
-The failing condition is documentation drift: the README currently does not
-describe refile behavior.
-
-- [ ] **Step 2: Run test to verify it fails**
-
-Review `README.md` and confirm it lacks refile documentation.
-
-- [ ] **Step 3: Write minimal implementation**
-
-- Add a short note describing that Org refile targets any heading in
- `org-agenda-files` and uses path-based minibuffer completion.
-
-- [ ] **Step 4: Run test to verify it passes**
-
-Review the updated text against the code for accuracy.
-
-- [ ] **Step 5: Commit**
-
-Wait for verification and user approval before creating a commit.
-
-### Task 3: Verify the full startup path
-
-**Files:**
-- Verify only
-
-- [ ] **Step 1: Write the failing test**
-
-Use the repository's normal startup verification path in addition to the new
-targeted ERT coverage.
-
-- [ ] **Step 2: Run test to verify it fails**
-
-Not applicable before implementation.
-
-- [ ] **Step 3: Write minimal implementation**
-
-No code changes in this task.
-
-- [ ] **Step 4: Run test to verify it passes**
-
-Run: `emacs --batch -Q --load ./init.el`
-Expected: exits successfully.
-
-Run: `emacs --batch -Q -L lisp -l tests/ss-org-tests.el -f ert-run-tests-batch-and-exit`
-Expected: PASS.
-
-- [ ] **Step 5: Commit**
-
-Wait for verification and user approval before creating a commit. \ No newline at end of file
diff --git a/early-init.el b/early-init.el
deleted file mode 100644
index dcf868c..0000000
--- a/early-init.el
+++ /dev/null
@@ -1,14 +0,0 @@
-;;; early-init.el --- Early startup settings -*- lexical-binding: t; -*-
-
-;;; Commentary:
-
-;; Keep this file limited to settings that must exist before the first frame.
-
-;;; Code:
-
-(dolist (parameter '((width . 140)
- (height . 42)))
- (add-to-list 'default-frame-alist parameter)
- (add-to-list 'initial-frame-alist parameter))
-
-;;; early-init.el ends here
diff --git a/init.el b/init.el
deleted file mode 100644
index c63d477..0000000
--- a/init.el
+++ /dev/null
@@ -1,53 +0,0 @@
-;;; init.el --- Main Emacs entry point -*- lexical-binding: t; -*-
-
-;;; Commentary:
-
-;; Hand-edited runtime entry point for this Emacs configuration.
-
-;;; Code:
-
-(add-to-list
- 'load-path
- (expand-file-name "lisp" (file-name-directory (or load-file-name user-init-file))))
-
-(setq ss-enabled-features
- '(ui org agenda denote capture crm gptel keys))
-
-(require 'ss-core)
-(ss-core-setup)
-
-(when (memq 'ui ss-enabled-features)
- (require 'ss-ui)
- (ss-ui-setup))
-
-(when (memq 'org ss-enabled-features)
- (require 'ss-org)
- (ss-org-setup))
-
-(when (memq 'agenda ss-enabled-features)
- (require 'ss-agenda)
- (ss-agenda-setup))
-
-(when (memq 'denote ss-enabled-features)
- (require 'ss-denote)
- (ss-denote-setup))
-
-(when (memq 'capture ss-enabled-features)
- (require 'ss-capture)
- (ss-capture-setup))
-
-(when (memq 'crm ss-enabled-features)
- (require 'ss-crm)
- (ss-crm-setup))
-
-(when (memq 'gptel ss-enabled-features)
- (require 'ss-gptel)
- (ss-gptel-setup))
-
-(when (memq 'keys ss-enabled-features)
- (require 'ss-keys)
- (ss-keys-setup))
-
-(ss-core-load-custom-file)
-
-;;; init.el ends here
diff --git a/lisp/ss-agenda.el b/lisp/ss-agenda.el
deleted file mode 100644
index a89a52e..0000000
--- a/lisp/ss-agenda.el
+++ /dev/null
@@ -1,40 +0,0 @@
-;;; ss-agenda.el --- Agenda configuration -*- lexical-binding: t; -*-
-
-;;; Commentary:
-
-;; Agenda discovery and agenda commands.
-
-;;; Code:
-
-(require 'ss-core)
-(require 'ss-org)
-
-(defun ss-refresh-org-agenda-files (&rest _)
- "Refresh `org-agenda-files' from the journal and PARA directories.
-Ignore any arguments passed by advice wrappers."
- (require 'org-agenda)
- (setq org-agenda-files
- (sort
- (delete-dups
- (append
- (list (ss-require-existing-file ss-journal-file))
- (apply #'append
- (mapcar (lambda (directory)
- (directory-files-recursively
- (ss-require-existing-directory directory)
- "\\.org\\'"))
- ss-org-agenda-directories))))
- #'string<)))
-
-(defun ss-open-agenda ()
- "Refresh agenda files and invoke `org-agenda'."
- (interactive)
- (call-interactively #'org-agenda))
-
-(defun ss-agenda-setup ()
- "Initialize agenda behavior."
- (advice-add 'org-agenda :before #'ss-refresh-org-agenda-files))
-
-(provide 'ss-agenda)
-
-;;; ss-agenda.el ends here
diff --git a/lisp/ss-capture.el b/lisp/ss-capture.el
deleted file mode 100644
index 0489888..0000000
--- a/lisp/ss-capture.el
+++ /dev/null
@@ -1,180 +0,0 @@
-;;; ss-capture.el --- Capture configuration -*- lexical-binding: t; -*-
-
-;;; Commentary:
-
-;; Journal capture helpers and capture templates.
-
-;;; Code:
-
-(require 'calendar)
-(require 'org)
-(require 'org-capture)
-(require 'ss-core)
-(require 'ss-org)
-
-(defun ss-journal-capture-time ()
- "Return the effective timestamp for the current journal capture."
- (or org-overriding-default-time
- (org-capture-get :default-time)
- (current-time)))
-
-(defun ss-journal-calendar-date (&optional time)
- "Return TIME as a Gregorian date list for datetree helpers."
- (calendar-gregorian-from-absolute
- (time-to-days (or time (current-time)))))
-
-(defun ss-journal-year-heading (&optional time)
- "Return the journal year heading text for TIME."
- (format-time-string "%Y" (or time (current-time))))
-
-(defun ss-journal-day-heading (&optional time)
- "Return the journal day heading text for TIME."
- (format-time-string "%Y-%m-%d %A" (or time (current-time))))
-
-(defun ss-journal-find-or-create-heading (level heading)
- "Move to HEADING at LEVEL, creating it when missing."
- (goto-char (point-min))
- (if (re-search-forward
- (format "^%s %s$"
- (make-string level ?*)
- (regexp-quote heading))
- nil t)
- (goto-char (match-beginning 0))
- (goto-char (point-max))
- (unless (bolp)
- (insert "\n"))
- (insert (make-string level ?*) " " heading "\n")
- (forward-line -1))
- (org-back-to-heading t))
-
-(defun ss-journal-goto-date (&optional time create)
- "Move to TIME's journal date heading.
-When CREATE is non-nil, create the datetree entry when missing."
- (goto-char (point-min))
- (if create
- (let ((year-heading (ss-journal-year-heading time))
- (day-heading (ss-journal-day-heading time)))
- (ss-journal-find-or-create-heading 1 year-heading)
- (save-restriction
- (org-narrow-to-subtree)
- (ss-journal-find-or-create-heading 2 day-heading))
- t)
- (when (re-search-forward
- (format "^\\*\\* %s$"
- (regexp-quote
- (ss-journal-day-heading (or time (current-time)))))
- nil t)
- (goto-char (match-beginning 0))
- t)))
-
-(defun ss-journal-ensure-day-sections ()
- "Ensure the standard section headings exist under the current journal day."
- (org-back-to-heading t)
- (let ((section-level (1+ (org-outline-level))))
- (save-excursion
- (save-restriction
- (org-narrow-to-subtree)
- (dolist (section ss-journal-section-headings)
- (goto-char (point-min))
- (forward-line 1)
- (unless (re-search-forward
- (format "^%s %s$"
- (make-string section-level ?*)
- (regexp-quote section))
- nil t)
- (goto-char (point-max))
- (unless (bolp)
- (insert "\n"))
- (insert (make-string section-level ?*) " " section "\n")))))))
-
-(defun ss-journal-goto-section (section &optional time)
- "Move to SECTION beneath TIME's journal date, creating structure as needed."
- (unless (member section ss-journal-section-headings)
- (user-error "Unknown journal section: %s" section))
- (ss-journal-goto-date time 'create)
- (ss-journal-ensure-day-sections)
- (let ((section-level (1+ (org-outline-level)))
- position)
- (save-restriction
- (org-narrow-to-subtree)
- (goto-char (point-min))
- (when (re-search-forward
- (format "^%s %s$"
- (make-string section-level ?*)
- (regexp-quote section))
- nil t)
- (setq position (match-beginning 0))))
- (unless position
- (user-error "Journal section not found: %s" section))
- (goto-char position)
- (org-back-to-heading t)))
-
-(defun ss-journal-capture-target (section)
- "Select SECTION under today's journal datetree entry for capture."
- (set-buffer (find-file-noselect (ss-require-existing-file ss-journal-file)))
- (widen)
- (ss-journal-goto-section section (ss-journal-capture-time)))
-
-(defun ss-capture--denote-templates ()
- "Return Denote-backed capture templates when Denote is enabled."
- (when (ss-feature-enabled-p 'denote)
- `(("n" "Denote")
- ("nn" "Generic" plain
- (file denote-last-path)
- (function
- (lambda ()
- (denote-org-capture-with-prompts :title :keywords :subdirectory)))
- :no-save t
- :immediate-finish nil
- :kill-buffer t
- :jump-to-captured t)
- ("np" "Project" plain
- (file denote-last-path)
- (function
- (lambda ()
- (ss-denote-capture-in-directory
- ss-org-projects-directory '("project") :title :keywords :subdirectory)))
- :no-save t
- :immediate-finish nil
- :kill-buffer t
- :jump-to-captured t)
- ("na" "Area" plain
- (file denote-last-path)
- (function
- (lambda ()
- (ss-denote-capture-in-directory
- ss-org-areas-directory nil :title :keywords :subdirectory)))
- :no-save t
- :immediate-finish nil
- :kill-buffer t
- :jump-to-captured t)
- ("nr" "Resource" plain
- (file denote-last-path)
- (function
- (lambda ()
- (ss-denote-capture-in-directory
- ss-org-resources-directory nil :title :keywords :subdirectory)))
- :no-save t
- :immediate-finish nil
- :kill-buffer t
- :jump-to-captured t))))
-
-(defun ss-capture-setup ()
- "Initialize capture templates."
- (setq org-capture-templates
- (append
- '(("j" "Journal")
- ("jt" "Task" entry
- (function (lambda () (ss-journal-capture-target "Tasks")))
- "* TODO %?")
- ("jn" "Note" entry
- (function (lambda () (ss-journal-capture-target "Notes")))
- "* %?")
- ("jm" "Meeting" entry
- (function (lambda () (ss-journal-capture-target "Meetings")))
- "* <%<%Y-%m-%d %H:%M>> %?"))
- (ss-capture--denote-templates))))
-
-(provide 'ss-capture)
-
-;;; ss-capture.el ends here
diff --git a/lisp/ss-core.el b/lisp/ss-core.el
deleted file mode 100644
index 3be0711..0000000
--- a/lisp/ss-core.el
+++ /dev/null
@@ -1,147 +0,0 @@
-;;; ss-core.el --- Shared core setup -*- lexical-binding: t; -*-
-
-;;; Commentary:
-
-;; Shared startup helpers, package bootstrap, paths, and editor defaults.
-
-;;; Code:
-
-(require 'subr-x)
-
-(defconst ss-minimum-emacs-version "27.1"
- "Minimum supported Emacs version.")
-
-(defconst ss-warning-emacs-version "28.1"
- "Version threshold for compatibility warnings.")
-
-(defconst ss-spell-check-support-enabled nil)
-(defconst ss-is-windows (memq system-type '(windows-nt ms-dos cygwin)))
-(defconst ss-is-linux (eq system-type 'gnu/linux))
-(defconst ss-is-mac (eq system-type 'darwin))
-
-(defconst ss-org-directory (expand-file-name "~/org/")
- "Root directory for Org files.")
-
-(defconst ss-journal-file (expand-file-name "journal.org" ss-org-directory)
- "Single-file work journal for operational capture.")
-
-(defconst ss-org-projects-directory (expand-file-name "projects/" ss-org-directory)
- "Directory for project notes.")
-
-(defconst ss-org-areas-directory (expand-file-name "areas/" ss-org-directory)
- "Directory for area notes.")
-
-(defconst ss-org-resources-directory (expand-file-name "resources/" ss-org-directory)
- "Directory for resource notes.")
-
-(defconst ss-org-archives-directory (expand-file-name "archives/" ss-org-directory)
- "Directory for archived notes.")
-
-(defconst ss-moc-file (expand-file-name "moc.org" ss-org-directory)
- "Central MOC note.")
-
-(defconst ss-crm-file (expand-file-name "areas/people/people.org" ss-org-directory)
- "Single source of truth for the people CRM.")
-
-(defconst ss-journal-section-headings
- '("Tasks" "Notes" "Meetings")
- "Per-day section headings maintained under each journal datetree entry.")
-
-(defconst ss-org-agenda-directories
- (list ss-org-projects-directory
- ss-org-areas-directory
- ss-org-resources-directory)
- "Directories whose Org files feed the agenda.")
-
-(defun ss-feature-enabled-p (feature)
- "Return non-nil when FEATURE is enabled in `ss-enabled-features'."
- (memq feature ss-enabled-features))
-
-(defun ss-require-existing-directory (directory)
- "Return DIRECTORY, signaling when it does not exist."
- (unless (file-directory-p directory)
- (user-error "Directory does not exist: %s" directory))
- directory)
-
-(defun ss-require-existing-file (file)
- "Return FILE, signaling when it does not exist."
- (unless (file-exists-p file)
- (user-error "File does not exist: %s" file))
- file)
-
-(defun ss-enable-prose-abbrev-mode ()
- "Enable abbrev mode in prose buffers.
-We keep this mode-local so code buffers stay on their own completion rules."
- (abbrev-mode 1))
-
-(defun ss-core-setup ()
- "Initialize shared core behavior."
- (let ((minver ss-minimum-emacs-version))
- (when (version< emacs-version minver)
- (error "Your Emacs is too old -- this config requires v%s or higher" minver)))
- (when (version< emacs-version ss-warning-emacs-version)
- (message
- (concat
- "Your Emacs is old, and some functionality in this config will be "
- "disabled. Please upgrade if possible.")))
-
- ;; Keep custom-set-variables out of the main config.
- (setq custom-file (expand-file-name "custom.el" user-emacs-directory))
-
- (require 'package)
- (setq package-archives
- (append '(("melpa" . "https://melpa.org/packages/"))
- package-archives)
- package-archive-priorities '(("gnu" . 10)
- ("nongnu" . 8)
- ("melpa" . 5))
- package-install-upgrade-built-in t
- use-package-always-ensure nil)
- (package-initialize)
- (require 'use-package)
- (require 'abbrev)
-
- (set-language-environment "UTF-8")
- (set-default-coding-systems 'utf-8)
- (prefer-coding-system 'utf-8)
-
- (setq abbrev-file-name (expand-file-name "abbrev_defs" user-emacs-directory)
- save-abbrevs 'silently)
- (when (file-exists-p abbrev-file-name)
- (quietly-read-abbrev-file abbrev-file-name))
-
- (dolist (hook '(text-mode-hook org-mode-hook))
- (add-hook hook #'ss-enable-prose-abbrev-mode))
-
- (setq auto-save-default nil
- backup-inhibited t
- echo-keystrokes 0.1
- compilation-ask-about-save nil
- mouse-wheel-scroll-amount '(1 ((shift) . 1))
- mouse-wheel-progressive-speed nil
- mouse-wheel-follow-mouse t
- scroll-step 1
- scroll-conservatively 101
- enable-recursive-minibuffers t
- gc-cons-threshold (* 128 1024 1024)
- read-process-output-max (* 4 1024 1024)
- process-adaptive-read-buffering nil)
-
- (fset 'yes-or-no-p 'y-or-n-p)
- (global-auto-revert-mode 1)
- (delete-selection-mode 1)
-
- (setq-default indent-tabs-mode nil
- fill-column 80
- tab-width 2
- indicate-empty-lines t
- sentence-end-double-space nil))
-
-(defun ss-core-load-custom-file ()
- "Load `custom-file' when it exists."
- (when (file-exists-p custom-file)
- (load custom-file nil 'nomessage)))
-
-(provide 'ss-core)
-
-;;; ss-core.el ends here
diff --git a/lisp/ss-crm.el b/lisp/ss-crm.el
deleted file mode 100644
index 62dc3e3..0000000
--- a/lisp/ss-crm.el
+++ /dev/null
@@ -1,817 +0,0 @@
-;;; ss-crm.el --- People CRM -*- lexical-binding: t; -*-
-
-;;; Commentary:
-
-;; People CRM parsing, lookup, completion, insertion, and reporting.
-
-;;; Code:
-
-(require 'abbrev)
-(require 'org)
-(require 'org-element)
-(require 'seq)
-(require 'ss-core)
-(require 'subr-x)
-(require 'marginalia nil t)
-
-(defconst ss-crm-engagement-options
- '("Perm" "SOW" "SOW Fixed Outcome" "NCS India")
- "Canonical engagement values for people cards.")
-
-(defconst ss-crm-seeded-roles
- '("Account Manager"
- "AD Platform Manager"
- "AD, Billing & Catalogue, Engineering"
- "AD, Frontend Engineering"
- "AD, IT Core & MarTech"
- "AD, Mobile Engineering"
- "AD, Platform Engineering"
- "AD, Portfolio Delivery Products and API Management"
- "AD, Portfolio Mgmt. Customer Engagement"
- "AD, Quality Engineering"
- "AD, Tech Product Owner"
- "AD, Technical Delivery"
- "AD, Technical Platform Management"
- "AD, Technical Platform Manager, API"
- "AD, Technical Platform Manager, Catalog"
- "AD, Technical Platform Manager, Order and Quote"
- "AD, Technical Product Management"
- "Agile Scrum Master"
- "Analyst Programmer - Senior"
- "Android Developer"
- "Android Developer - Senior"
- "Application Development Lead"
- "Architect"
- "Associate Engineer"
- "Associate Engineer"
- "Associate Engineer, Frontend"
- "Associate Engineer, Platform Engineering"
- "Associate Software Engineer"
- "Billing Executive"
- "Business Analyst"
- "Business Analyst - Senior"
- "Business Development Manager"
- "Business Intelligence Analyst"
- "Business Program Manager"
- "Cloud Engineer"
- "Consultant"
- "CRM Developer"
- "Data Analyst"
- "Data Analyst - Senior"
- "Delivery Lead"
- "Delivery Manager"
- "Demand Lead"
- "Deployment Manager"
- "Development Manager"
- "DevOps Engineer"
- "DevOps Engineer - Senior"
- "Digital Delivery Manager"
- "Director Customer Engagement"
- "Director Sales Technology"
- "Director, Billing and Charging"
- "Director, Delivery Portfolio Billing and Charging"
- "Director, Digital Engineering"
- "Director, Portfolio Mgmt."
- "Director, SF Comms Technical Platform"
- "Director, SSF Core & Commerce"
- "Engineer"
- "Engineer - Junior"
- "Engineer - Senior"
- "Engineer, Platform Engineering"
- "Engineer, Salesforce"
- "Engineering Manager"
- "Engineering Manager, Mobile"
- "Engineering Manager, Platform Engineering"
- "Executive Assistant"
- "Integration Lead"
- "Integration Solution Designer"
- "Integration Technical Specialist"
- "iOS Developer"
- "iOS Developer - Senior"
- "IT Business Partner"
- "Lead Consultant"
- "Lead Developer"
- "Manager, Release Engineer"
- "Manager, Release Train Engineer"
- "Mobile API Developer"
- "Optus Tech Graduate"
- "Portfolio Manager"
- "Process Analyst - Senior"
- "Product Manager"
- "Product Solution Designer"
- "Program Analyst"
- "Program Director"
- "Program Manager"
- "Project Architect"
- "Project Coordinator"
- "Project Manager"
- "Quality Assurance Analyst"
- "Quality Engineering Professional"
- "Release Coordinator"
- "Release Manager"
- "Salesforce Administrator"
- "Salesforce Developer (Heroku, Lightning, etc)"
- "Salesforce Marketing Cloud Architect"
- "Salesforce Operations Manager"
- "Scrum Master"
- "SD, Engineering Capability"
- "SD, Marketing Technology"
- "SD, Technical Products"
- "Senior Business Analyst"
- "Senior Delivery Manager"
- "Senior Delivery Manger"
- "Senior Digital Technical Producer"
- "Senior E2E Business Analyst Lead"
- "Senior Engineer"
- "Senior Engineer, API Engineering"
- "Senior Engineer, Billing & Catalogue"
- "Senior Engineer, Platform Engineering"
- "Senior Engineer, Salesforce"
- "Senior Front End Developer"
- "Senior Integration Solution Designer"
- "Senior Quality Engineering Professional"
- "Senior Salesforce Administrator"
- "Senior Salesforce Consultant"
- "Senior Salesforce Functional Consultant"
- "Senior Salesforce Operations Lead"
- "Senior Software Developer"
- "Senior Software Engineer"
- "Senior Software Engineer, Frontend"
- "Senior Software Engineer, Mobile"
- "Senior Solution Designer"
- "Snr Dir, Delivery & Platform Manager"
- "Snr Director, Portfolio Mgmt. & Delivery"
- "Software Developer"
- "Software Developer - Junior"
- "Software Developer - Senior"
- "Software Engineer"
- "Software Engineer, Frontend"
- "Software Engineer, Mobile"
- "Solution Architect"
- "Solution Designer"
- "Solution Designer - Senior"
- "Staff Engineer"
- "Support Analyst"
- "System Analyst"
- "System Engineer"
- "Technical Lead"
- "Technical Platform Manager"
- "Technical Platform Manager, Billing and Charging"
- "Technical Product Manager"
- "Technical Project Manager - Senior"
- "Technical Specialist - Senior"
- "Technology Delivery Analyst"
- "Test Analyst"
- "Test Lead"
- "Test Manager"
- "VP, EB IT")
- "Seeded role values derived from the historic roles CSV.")
-
-(defconst ss-crm-supplier-options
- '("Accenture Song"
- "INFOSYS TECHNOLOGIES LIMITED"
- "MAKK Integrations Pty Ltd"
- "NCSI Technologies India Private Limited"
- "TECH MAHINDRA LTD")
- "Canonical supplier values for people cards.")
-
-(defvar ss-crm--cache nil
- "Cached CRM entries loaded from `ss-crm-file'.")
-
-(defvar ss-crm--cache-mtime nil
- "Modification time of the cached CRM entries.")
-
-(defun ss-crm--entry-name (entry)
- "Return the canonical name in ENTRY."
- (plist-get entry :name))
-
-(defun ss-crm--entry-abbrev (entry)
- "Return the abbrev trigger in ENTRY."
- (plist-get entry :abbrev))
-
-(defun ss-crm--entry-aliases (entry)
- "Return alias variants in ENTRY."
- (plist-get entry :aliases))
-
-(defun ss-crm--entry-role (entry)
- "Return the role in ENTRY."
- (plist-get entry :role))
-
-(defun ss-crm--entry-team (entry)
- "Return the team in ENTRY."
- (plist-get entry :team))
-
-(defun ss-crm--entry-manager (entry)
- "Return the manager in ENTRY."
- (plist-get entry :manager))
-
-(defun ss-crm--entry-engagement (entry)
- "Return the engagement in ENTRY."
- (plist-get entry :engagement))
-
-(defun ss-crm--entry-supplier (entry)
- "Return the supplier in ENTRY."
- (plist-get entry :supplier))
-
-(defun ss-crm--entry-location (entry)
- "Return the location in ENTRY."
- (plist-get entry :location))
-
-(defun ss-crm--entry-current-focus (entry)
- "Return the current focus in ENTRY."
- (plist-get entry :current-focus))
-
-(defun ss-crm-default-abbrev (name)
- "Suggest a short abbrev trigger for NAME."
- (let* ((parts (split-string (string-trim name) "[[:space:]]+" t))
- (first (downcase (substring (car parts) 0 (min 2 (length (car parts))))))
- (last (downcase (substring (car (last parts)) 0 1))))
- (if (> (length parts) 1)
- (concat ";" first last)
- (concat ";" first))))
-
-(defun ss-crm--split-values (value)
- "Split VALUE on commas and trim each item."
- (when (and value (not (string-empty-p value)))
- (seq-filter
- (lambda (string)
- (not (string-empty-p string)))
- (mapcar #'string-trim (split-string value "," t)))))
-
-(defun ss-crm--summary (entry)
- "Return the compact one-line summary for ENTRY."
- (string-join
- (seq-filter
- (lambda (string)
- (and string (not (string-empty-p string))))
- (list (ss-crm--entry-role entry)
- (ss-crm--entry-team entry)
- (ss-crm--entry-engagement entry)
- (ss-crm--entry-current-focus entry)))
- " | "))
-
-(defun ss-crm--display (entry)
- "Return the compact display string for ENTRY."
- (let ((summary (ss-crm--summary entry)))
- (if (string-empty-p summary)
- (ss-crm--entry-name entry)
- (format "%s %s" (ss-crm--entry-name entry) summary))))
-
-(defun ss-crm--property-line (key value)
- "Return an Org property line for KEY and VALUE."
- (if (and value (not (string-empty-p value)))
- (format ":%s: %s\n" key value)
- ""))
-
-(defun ss-crm--require-file ()
- "Return `ss-crm-file', signaling when it is unavailable."
- (ss-require-existing-file ss-crm-file))
-
-(defun ss-crm--parse-entry-at-point (headline)
- "Return the CRM entry described by HEADLINE at point."
- (list :name (org-element-property :raw-value headline)
- :abbrev (org-entry-get nil "ABBREV")
- :aliases (ss-crm--split-values (org-entry-get nil "ALIASES"))
- :role (org-entry-get nil "ROLE")
- :team (org-entry-get nil "TEAM")
- :manager (org-entry-get nil "MANAGER")
- :engagement (org-entry-get nil "ENGAGEMENT")
- :supplier (org-entry-get nil "SUPPLIER")
- :location (org-entry-get nil "LOCATION")
- :current-focus (org-entry-get nil "CURRENT_FOCUS")))
-
-(defun ss-crm--parse-entries ()
- "Parse top-level CRM entries from `ss-crm-file'."
- (with-temp-buffer
- (insert-file-contents (ss-crm--require-file))
- ;; Parse cards without running user hooks; otherwise the CRM's own Org
- ;; hooks recurse back into this parser.
- (delay-mode-hooks
- (org-mode))
- (let ((ast (org-element-parse-buffer))
- cards)
- (org-element-map ast 'headline
- (lambda (headline)
- (when (= 1 (org-element-property :level headline))
- (goto-char (org-element-property :begin headline))
- (push (ss-crm--parse-entry-at-point headline) cards))))
- (sort cards
- (lambda (left right)
- (string< (ss-crm--entry-name left)
- (ss-crm--entry-name right)))))))
-
-(defun ss-crm-entries ()
- "Return top-level people cards from `ss-crm-file'."
- (let* ((file (ss-crm--require-file))
- (attributes (file-attributes file))
- (mtime (file-attribute-modification-time attributes)))
- (unless (and ss-crm--cache
- (equal mtime ss-crm--cache-mtime))
- (setq ss-crm--cache (ss-crm--parse-entries)
- ss-crm--cache-mtime mtime))
- ss-crm--cache))
-
-(defun ss-crm-reload ()
- "Reload the people cache and refresh prose buffers."
- (interactive)
- (setq ss-crm--cache nil
- ss-crm--cache-mtime nil)
- (ss-crm-refresh-buffers)
- (message "Reloaded people CRM"))
-
-(defun ss-crm--entry-by-name (name)
- "Return the people entry matching canonical NAME."
- (seq-find
- (lambda (entry)
- (string= name (ss-crm--entry-name entry)))
- (ss-crm-entries)))
-
-(defun ss-crm--search-keys (entry)
- "Return canonical and alias search keys for ENTRY."
- (cons (ss-crm--entry-name entry)
- (ss-crm--entry-aliases entry)))
-
-(defun ss-crm--match-p (query entry)
- "Return non-nil when QUERY matches ENTRY name or aliases."
- (let* ((parts (split-string (downcase (string-trim query)) "[[:space:]]+" t))
- (keys (mapcar #'downcase (ss-crm--search-keys entry))))
- (seq-every-p
- (lambda (part)
- (seq-some
- (lambda (key)
- (string-match-p (regexp-quote part) key))
- keys))
- parts)))
-
-(defun ss-crm--matching-entries (query)
- "Return entries whose canonical name or aliases match QUERY."
- (let ((entries (ss-crm-entries)))
- (if (string-empty-p (string-trim query))
- entries
- (seq-filter
- (lambda (entry)
- (ss-crm--match-p query entry))
- entries))))
-
-(defun ss-crm--completion-table (string pred action)
- "Complete canonical people names while matching aliases via STRING."
- (if (eq action 'metadata)
- '(metadata (category . ss-person))
- (complete-with-action
- action
- (mapcar #'ss-crm--entry-name (ss-crm--matching-entries string))
- string
- pred)))
-
-(defun ss-crm-marginalia-annotator (candidate)
- "Return a Marginalia annotation for person CANDIDATE."
- (when-let ((entry (ss-crm--entry-by-name candidate)))
- (concat " " (ss-crm--summary entry))))
-
-(defun ss-crm-select-entry (&optional prompt)
- "Select a person entry using PROMPT."
- (let ((completion-extra-properties
- '(:annotation-function ss-crm-marginalia-annotator)))
- (ss-crm--entry-by-name
- (completing-read (or prompt "Person: ")
- #'ss-crm--completion-table
- nil
- t))))
-
-(defun ss-crm-overview ()
- "Open `ss-crm-file' in overview mode, widening first when needed."
- (interactive)
- (unless (and buffer-file-name
- (string= (file-truename buffer-file-name)
- (file-truename ss-crm-file)))
- (find-file (ss-crm--require-file)))
- (widen)
- (goto-char (point-min))
- (org-overview)
- (org-cycle-hide-drawers 'all))
-
-(defun ss-crm-open ()
- "Open the people CRM by delegating to `ss-crm-overview'."
- (interactive)
- (ss-crm-overview))
-
-(defun ss-crm--track-buffer ()
- "Refresh CRM caches when `ss-crm-file' is saved."
- (when (and buffer-file-name
- (string= (file-truename buffer-file-name)
- (file-truename ss-crm-file)))
- (add-hook 'after-save-hook #'ss-crm-reload nil t)))
-
-(defun ss-crm--source-buffer-p ()
- "Return non-nil when the current buffer visits `ss-crm-file'."
- (and buffer-file-name
- (string= (file-truename buffer-file-name)
- (file-truename ss-crm-file))))
-
-(defun ss-crm--open-entry (entry)
- "Open the people CRM file, then narrow to ENTRY for card view."
- (find-file (ss-crm--require-file))
- (widen)
- (let ((position (org-find-exact-headline-in-buffer
- (ss-crm--entry-name entry))))
- (unless position
- (user-error "No people card for %s" (ss-crm--entry-name entry)))
- (goto-char position))
- (org-narrow-to-subtree)
- (org-fold-show-subtree)
- (org-fold-show-entry)
- (goto-char (point-min)))
-
-(defun ss-crm-find ()
- "Find a person and open that card."
- (interactive)
- (ss-crm--open-entry
- (or (ss-crm-select-entry "Find person: ")
- (user-error "No person selected"))))
-
-(defun ss-crm-insert-name ()
- "Insert a canonical person name at point."
- (interactive)
- (let ((entry (or (ss-crm-select-entry "Insert person name: ")
- (user-error "No person selected"))))
- (insert (ss-crm--entry-name entry))))
-
-(defun ss-crm-insert-summary ()
- "Insert a compact person summary at point."
- (interactive)
- (let ((entry (or (ss-crm-select-entry "Insert person summary: ")
- (user-error "No person selected"))))
- (insert (ss-crm--display entry))))
-
-(defun ss-crm--report-buffer (title group-fn)
- "Render a grouped CRM report titled TITLE using GROUP-FN."
- (let* ((entries (ss-crm-entries))
- (groups
- (seq-group-by
- (lambda (entry)
- (let ((value (funcall group-fn entry)))
- (if (string-empty-p (or value ""))
- "(none)"
- value)))
- entries)))
- (setq groups
- (sort groups
- (lambda (left right)
- (string< (car left) (car right)))))
- (with-current-buffer (get-buffer-create "*People Report*")
- (let ((inhibit-read-only t))
- (erase-buffer)
- (org-mode)
- (insert "#+title: " title "\n\n")
- (dolist (group groups)
- (insert "* " (car group) "\n")
- (dolist (entry (sort (copy-sequence (cdr group))
- (lambda (left right)
- (string< (ss-crm--entry-name left)
- (ss-crm--entry-name right)))))
- (insert "- " (ss-crm--display entry) "\n")))
- (goto-char (point-min))
- (read-only-mode 1)
- (view-mode 1))
- (pop-to-buffer (current-buffer)))))
-
-(defun ss-crm-report-by-team ()
- "Show people grouped by team."
- (interactive)
- (ss-crm--report-buffer "People by team" #'ss-crm--entry-team))
-
-(defun ss-crm-report-by-manager ()
- "Show people grouped by manager."
- (interactive)
- (ss-crm--report-buffer "People by manager" #'ss-crm--entry-manager))
-
-(defun ss-crm-report-by-engagement ()
- "Show people grouped by engagement."
- (interactive)
- (ss-crm--report-buffer "People by engagement" #'ss-crm--entry-engagement))
-
-(defun ss-crm-report-by-supplier ()
- "Show non-empty suppliers grouped by supplier."
- (interactive)
- (let* ((entries
- (seq-filter
- (lambda (entry)
- (not (string-empty-p (or (ss-crm--entry-supplier entry) ""))))
- (ss-crm-entries)))
- (groups (seq-group-by #'ss-crm--entry-supplier entries)))
- (setq groups
- (sort groups
- (lambda (left right)
- (string< (car left) (car right)))))
- (with-current-buffer (get-buffer-create "*People Report*")
- (let ((inhibit-read-only t))
- (erase-buffer)
- (org-mode)
- (insert "#+title: People by supplier\n\n")
- (dolist (group groups)
- (insert "* " (car group) "\n")
- (dolist (entry (sort (copy-sequence (cdr group))
- (lambda (left right)
- (string< (ss-crm--entry-name left)
- (ss-crm--entry-name right)))))
- (insert "- " (ss-crm--display entry) "\n")))
- (goto-char (point-min))
- (read-only-mode 1)
- (view-mode 1))
- (pop-to-buffer (current-buffer)))))
-
-(defun ss-crm-report-by-role ()
- "Show people grouped by role."
- (interactive)
- (ss-crm--report-buffer "People by role" #'ss-crm--entry-role))
-
-(defun ss-crm-report-by-location ()
- "Show people grouped by location."
- (interactive)
- (ss-crm--report-buffer "People by location" #'ss-crm--entry-location))
-
-(defun ss-crm-read-string (prompt &optional default)
- "Read PROMPT and trim the result."
- (string-trim (read-string prompt nil nil default)))
-
-(defun ss-crm-read-required-string (prompt &optional default)
- "Read PROMPT and require a non-empty result."
- (let ((value (ss-crm-read-string prompt default)))
- (if (string-empty-p value)
- (user-error "%s is required" (string-remove-suffix ": " prompt))
- value)))
-
-(defun ss-crm-known-property-values (property)
- "Return sorted unique non-empty values for PROPERTY from CRM entries."
- (let ((key (intern (concat ":" (downcase property)))))
- (seq-sort
- #'string<
- (delete-dups
- (seq-filter
- (lambda (value)
- (and (stringp value)
- (not (string-empty-p (string-trim value)))))
- (mapcar
- (lambda (entry)
- (plist-get entry key))
- (ss-crm-entries)))))))
-
-(defun ss-crm-known-person-names ()
- "Return sorted top-level person names from CRM entries."
- (seq-sort
- #'string<
- (delete-dups
- (seq-filter
- (lambda (name)
- (and (stringp name)
- (not (string-empty-p (string-trim name)))))
- (mapcar #'ss-crm--entry-name (ss-crm-entries))))))
-
-(defun ss-crm-lookup-values (property &optional seeded)
- "Return sorted unique values for PROPERTY merged with SEEDED values."
- (seq-sort
- #'string<
- (delete-dups
- (seq-filter
- (lambda (value)
- (and (stringp value)
- (not (string-empty-p (string-trim value)))))
- (append seeded
- (ss-crm-known-property-values property))))))
-
-(defun ss-crm--read-choice-warning (prompt similar)
- "Warn that PROMPT value is similar to existing SIMILAR."
- (display-warning
- 'ss-crm
- (format "%sA similar existing value already exists: %s"
- prompt
- similar)
- :warning))
-
-(defun ss-crm-read-choice (prompt choices &rest plist)
- "Read a value for PROMPT from CHOICES using options in PLIST.
-Supported keywords are :allow-blank, :allow-new, and :require-match."
- (let* ((allow-blank (plist-get plist :allow-blank))
- (allow-new (plist-get plist :allow-new))
- (require-match (plist-get plist :require-match))
- (blank-choice "[none]")
- (collection (if allow-blank
- (cons blank-choice choices)
- choices))
- (completion-require-match (if allow-new nil require-match)))
- (catch 'done
- (while t
- (let* ((value (completing-read prompt collection nil completion-require-match))
- (existing
- (seq-find
- (lambda (choice)
- (string= value choice))
- choices)))
- (cond
- ((or (string-empty-p value)
- (and allow-blank
- (string= value blank-choice)))
- (throw 'done nil))
- (existing
- (throw 'done existing))
- ((not allow-new)
- (user-error "Please choose an existing value"))
- (t
- (when-let ((similar
- (seq-find
- (lambda (choice)
- (and (not (string= value choice))
- (string-equal (downcase value)
- (downcase choice))))
- choices)))
- (ss-crm--read-choice-warning prompt similar))
- (when (yes-or-no-p (format "Create new value `%s'? " value))
- (throw 'done value)))))))))
-
-(defun ss-crm--completion-values (extractor &optional include-empty)
- "Return sorted unique values using EXTRACTOR.
-When INCLUDE-EMPTY is non-nil, keep empty values."
- (let ((values (delete-dups (mapcar extractor (ss-crm-entries)))))
- (seq-sort
- #'string<
- (seq-filter
- (lambda (value)
- (or include-empty
- (not (string-empty-p (or value "")))))
- values))))
-
-(defun ss-crm-read-role ()
- "Read a CRM role using seeded and known role values."
- (ss-crm-read-choice "Role: "
- (ss-crm-lookup-values "ROLE" ss-crm-seeded-roles)
- :allow-blank t
- :allow-new t))
-
-(defun ss-crm-read-team ()
- "Read a CRM team using known team values."
- (ss-crm-read-choice "Team: "
- (ss-crm-lookup-values "TEAM")
- :allow-blank t
- :allow-new t))
-
-(defun ss-crm-read-manager ()
- "Read a CRM manager using known person names."
- (ss-crm-read-choice "Manager: "
- (ss-crm-known-person-names)
- :allow-blank t
- :require-match t))
-
-(defun ss-crm-read-engagement ()
- "Read a CRM engagement using seeded and known engagement values."
- (ss-crm-read-choice "Engagement: "
- (ss-crm-lookup-values "ENGAGEMENT"
- ss-crm-engagement-options)
- :allow-blank t
- :allow-new t))
-
-(defun ss-crm-read-supplier ()
- "Read a CRM supplier using seeded and known supplier values."
- (ss-crm-read-choice "Supplier: "
- (ss-crm-lookup-values "SUPPLIER"
- ss-crm-supplier-options)
- :allow-blank t
- :allow-new t))
-
-(defun ss-crm-read-location ()
- "Read a CRM location using known location values."
- (ss-crm-read-choice "Location: "
- (ss-crm-lookup-values "LOCATION")
- :allow-blank t
- :allow-new t))
-
-(defun ss-crm-add ()
- "Add a new compact person card to `ss-crm-file'."
- (interactive)
- (let ((name (ss-crm-read-required-string "Full name: "))
- (abbrev nil)
- (aliases nil)
- (role nil)
- (team nil)
- (manager nil)
- (engagement nil)
- (supplier nil)
- (location nil)
- (current-focus nil))
- (setq abbrev (ss-crm-read-string "Abbrev: " (ss-crm-default-abbrev name))
- aliases (ss-crm-read-string "Aliases (comma-separated, optional): ")
- role (ss-crm-read-role)
- team (ss-crm-read-team)
- manager (ss-crm-read-manager)
- engagement (ss-crm-read-engagement)
- supplier (ss-crm-read-supplier)
- location (ss-crm-read-location)
- current-focus (ss-crm-read-required-string "Current focus: "))
- (when (ss-crm--entry-by-name name)
- (user-error "A person card for %s already exists" name))
- (when (string-empty-p abbrev)
- (setq abbrev (ss-crm-default-abbrev name)))
- (find-file (ss-crm--require-file))
- (widen)
- (goto-char (point-max))
- (unless (bolp)
- (insert "\n"))
- (unless (looking-back "\n\n" nil)
- (insert "\n"))
- (insert "* " name "\n"
- ":PROPERTIES:\n"
- (ss-crm--property-line "ABBREV" abbrev)
- (ss-crm--property-line "ALIASES" aliases)
- (ss-crm--property-line "ROLE" role)
- (ss-crm--property-line "TEAM" team)
- (ss-crm--property-line "MANAGER" manager)
- (ss-crm--property-line "ENGAGEMENT" engagement)
- (ss-crm--property-line "SUPPLIER" supplier)
- (ss-crm--property-line "LOCATION" location)
- (ss-crm--property-line "CURRENT_FOCUS" current-focus)
- ":END:\n\n"
- "** Context\n\n"
- "** TODOs\n")
- (save-buffer)
- (ss-crm-reload)
- (ss-crm--open-entry (ss-crm--entry-by-name name))))
-
-(defun ss-crm--clear-installed-abbrevs ()
- "Remove people-specific abbrevs from the current local table."
- (mapatoms
- (lambda (symbol)
- (when (abbrev-get symbol :ss/crm)
- (define-abbrev local-abbrev-table (symbol-name symbol) nil)))
- local-abbrev-table))
-
-(defun ss-crm-install-abbrevs ()
- "Install people abbrevs into the current buffer."
- (unless (ss-crm--source-buffer-p)
- (setq-local local-abbrev-table (copy-abbrev-table local-abbrev-table))
- (ss-crm--clear-installed-abbrevs)
- (dolist (entry (ss-crm-entries))
- (let* ((name (ss-crm--entry-name entry))
- (abbrev (ss-crm--entry-abbrev entry))
- (abbrev-name
- (if (or (null abbrev) (string-empty-p abbrev))
- (ss-crm-default-abbrev name)
- abbrev)))
- (define-abbrev local-abbrev-table abbrev-name name)
- (when-let ((abbrev-symbol
- (abbrev-symbol abbrev-name local-abbrev-table)))
- (abbrev-put abbrev-symbol :ss/crm t))))))
-
-(defun ss-crm-refresh-buffers ()
- "Refresh people abbrevs in every prose buffer."
- (dolist (buffer (buffer-list))
- (with-current-buffer buffer
- (when (and (bound-and-true-p abbrev-mode)
- (derived-mode-p 'text-mode 'org-mode))
- (ss-crm-install-abbrevs)))))
-
-(defun ss-crm-capf ()
- "Return canonical people completions at a word boundary."
- (let ((end (point)))
- (save-excursion
- (skip-syntax-backward "w_")
- (let ((beg (point)))
- (when (< beg end)
- (let ((annotation
- (lambda (candidate)
- (when-let ((entry (ss-crm--entry-by-name candidate)))
- (concat " " (ss-crm--summary entry)))))
- (docsig
- (lambda (candidate)
- (when-let ((entry (ss-crm--entry-by-name candidate)))
- (ss-crm--summary entry)))))
- (list beg end #'ss-crm--completion-table
- :exclusive 'no
- :annotation-function annotation
- :company-docsig docsig)))))))
-
-(defun ss-enable-people-capf ()
- "Add `ss-crm-capf' once in prose buffers."
- (unless (or (ss-crm--source-buffer-p)
- (memq #'ss-crm-capf completion-at-point-functions))
- (add-hook 'completion-at-point-functions #'ss-crm-capf nil t)))
-
-(defun ss-crm--maybe-overview-buffer ()
- "Reset the people CRM buffer to overview when visiting it directly."
- (when (and buffer-file-name
- (string= (file-truename buffer-file-name)
- (file-truename ss-crm-file)))
- (widen)
- (goto-char (point-min))
- (org-overview)
- (org-cycle-hide-drawers 'all)))
-
-(defun ss-crm-setup ()
- "Initialize CRM hooks and helpers."
- (dolist (hook '(text-mode-hook org-mode-hook))
- (add-hook hook #'ss-enable-people-capf)
- (add-hook hook #'ss-crm-install-abbrevs))
- (add-hook 'find-file-hook #'ss-crm--track-buffer)
- (add-hook 'find-file-hook #'ss-crm--maybe-overview-buffer))
-
-(provide 'ss-crm)
-
-;;; ss-crm.el ends here
diff --git a/lisp/ss-denote.el b/lisp/ss-denote.el
deleted file mode 100644
index 65b9b53..0000000
--- a/lisp/ss-denote.el
+++ /dev/null
@@ -1,49 +0,0 @@
-;;; ss-denote.el --- Denote configuration -*- lexical-binding: t; -*-
-
-;;; Commentary:
-
-;; Durable note creation and Denote integration.
-
-;;; Code:
-
-(require 'ss-core)
-
-(declare-function denote-keywords-prompt "denote")
-(declare-function denote-org-capture "denote")
-(declare-function denote-org-capture-with-prompts "denote")
-
-(defun ss-denote-capture-in-directory (directory &optional keywords &rest prompts)
- "Start a Denote Org capture in DIRECTORY with KEYWORDS and PROMPTS.
-If PROMPTS is empty, rely on `denote-prompts'."
- (let* ((prompt-for-keywords (memq :keywords prompts))
- (directory (ss-require-existing-directory directory))
- (denote-directory directory)
- (denote-use-directory (unless (memq :subdirectory prompts) directory))
- (denote-use-keywords
- (if prompt-for-keywords
- (delete-dups (append keywords (denote-keywords-prompt)))
- keywords)))
- (if prompts
- (denote-org-capture-with-prompts
- (memq :title prompts)
- nil
- (memq :subdirectory prompts)
- (memq :date prompts)
- (memq :template prompts))
- (denote-org-capture))))
-
-(defun ss-denote-setup ()
- "Initialize Denote."
- (use-package denote
- :ensure t
- :after org
- :config
- (setq denote-directory ss-org-directory
- denote-known-keywords '("project")
- denote-prompts '(title keywords)
- denote-org-capture-specifiers "%?")
- (denote-rename-buffer-mode 1)))
-
-(provide 'ss-denote)
-
-;;; ss-denote.el ends here
diff --git a/lisp/ss-gptel.el b/lisp/ss-gptel.el
deleted file mode 100644
index bef5ce7..0000000
--- a/lisp/ss-gptel.el
+++ /dev/null
@@ -1,21 +0,0 @@
-;;; ss-gptel.el --- GPTel integration -*- lexical-binding: t; -*-
-
-;;; Commentary:
-
-;; Experimental gptel setup using the GitHub Copilot backend.
-
-;;; Code:
-
-(require 'ss-core)
-
-(defun ss-gptel-setup ()
- "Initialize gptel."
- (if (require 'gptel nil t)
- (setq gptel-default-mode 'org-mode
- gptel-model 'gpt-4o
- gptel-backend (gptel-make-gh-copilot "Copilot"))
- (message "Skipping gptel setup because the package is unavailable.")))
-
-(provide 'ss-gptel)
-
-;;; ss-gptel.el ends here
diff --git a/lisp/ss-keys.el b/lisp/ss-keys.el
deleted file mode 100644
index 4816eea..0000000
--- a/lisp/ss-keys.el
+++ /dev/null
@@ -1,60 +0,0 @@
-;;; ss-keys.el --- Global keybindings -*- lexical-binding: t; -*-
-
-;;; Commentary:
-
-;; Centralized global keybindings for enabled features.
-
-;;; Code:
-
-(require 'ss-core)
-
-(defun ss-keys-setup ()
- "Install global keybindings for enabled features."
- (when (ss-feature-enabled-p 'agenda)
- (global-set-key (kbd "C-c a") #'ss-open-agenda))
-
- (when (ss-feature-enabled-p 'capture)
- (global-set-key (kbd "C-c c") #'org-capture))
-
- (when (ss-feature-enabled-p 'org)
- (global-set-key (kbd "C-c b") #'ss-jump-back)
- (global-set-key (kbd "C-c f") #'ss-jump-forward))
-
- (when (and (ss-feature-enabled-p 'denote)
- (fboundp 'denote-open-or-create)
- (fboundp 'denote-link))
- (global-set-key (kbd "C-c n n") #'denote-open-or-create)
- (global-set-key (kbd "C-c n l") #'denote-link))
-
- (when (ss-feature-enabled-p 'org)
- (global-set-key (kbd "C-c n M") #'ss-open-moc)
- (global-set-key (kbd "C-c n j") #'ss-open-journal-full)
- (global-set-key (kbd "C-c n d") #'ss-open-journal))
-
- (when (ss-feature-enabled-p 'crm)
- (global-set-key (kbd "C-c n E") #'ss-crm-report-by-engagement)
- (global-set-key (kbd "C-c n f") #'ss-crm-find)
- (global-set-key (kbd "C-c n i") #'ss-crm-insert-name)
- (global-set-key (kbd "C-c n I") #'ss-crm-insert-summary)
- (global-set-key (kbd "C-c n L") #'ss-crm-report-by-location)
- (global-set-key (kbd "C-c n o") #'ss-crm-overview)
- (global-set-key (kbd "C-c n O") #'ss-crm-report-by-role)
- (global-set-key (kbd "C-c n p") #'ss-crm-open)
- (global-set-key (kbd "C-c n P") #'ss-crm-add)
- (global-set-key (kbd "C-c n R") #'ss-crm-report-by-manager)
- (global-set-key (kbd "C-c n S") #'ss-crm-report-by-supplier)
- (global-set-key (kbd "C-c n T") #'ss-crm-report-by-team))
-
- (when (and (ss-feature-enabled-p 'gptel)
- (fboundp 'gptel)
- (fboundp 'gptel-send)
- (fboundp 'gptel-rewrite)
- (fboundp 'gptel-add))
- (global-set-key (kbd "C-c n g") #'gptel)
- (global-set-key (kbd "C-c n s") #'gptel-send)
- (global-set-key (kbd "C-c n r") #'gptel-rewrite)
- (global-set-key (kbd "C-c n a") #'gptel-add)))
-
-(provide 'ss-keys)
-
-;;; ss-keys.el ends here
diff --git a/lisp/ss-org.el b/lisp/ss-org.el
deleted file mode 100644
index fc8fc9b..0000000
--- a/lisp/ss-org.el
+++ /dev/null
@@ -1,306 +0,0 @@
-;;; ss-org.el --- Base Org configuration -*- lexical-binding: t; -*-
-
-;;; Commentary:
-
-;; Shared Org setup and note-opening helpers.
-
-;;; Code:
-
-(require 'org)
-(require 'ss-core)
-
-(defvar ss-navigation-back-stack nil
- "Stack of older locations for `ss-jump-back'.")
-
-(defvar ss-navigation-forward-stack nil
- "Stack of newer locations for `ss-jump-forward'.")
-
-(defvar ss-navigation--inhibit-recording nil
- "When non-nil, suppress navigation history recording.")
-
-(defun ss-navigation--current-location ()
- "Return the current location as a marker-backed plist."
- (unless (minibufferp (current-buffer))
- (list :marker (copy-marker (point-marker))
- :window-start
- (when (window-live-p (selected-window))
- (copy-marker (window-start (selected-window)))))))
-
-(defun ss-navigation--location-valid-p (location)
- "Return non-nil when LOCATION still points to a live buffer position."
- (when-let ((marker (plist-get location :marker)))
- (and (markerp marker)
- (marker-buffer marker)
- (marker-position marker))))
-
-(defun ss-navigation--same-location-p (left right)
- "Return non-nil when LEFT and RIGHT identify the same buffer position."
- (and (ss-navigation--location-valid-p left)
- (ss-navigation--location-valid-p right)
- (eq (marker-buffer (plist-get left :marker))
- (marker-buffer (plist-get right :marker)))
- (= (marker-position (plist-get left :marker))
- (marker-position (plist-get right :marker)))))
-
-(defun ss-navigation--prune-stack (stack)
- "Return STACK with dead or duplicate locations removed."
- (let (pruned previous)
- (dolist (location stack (nreverse pruned))
- (when (and (ss-navigation--location-valid-p location)
- (not (ss-navigation--same-location-p location previous)))
- (push location pruned)
- (setq previous location)))))
-
-(defun ss-navigation--push-location (stack-symbol location &optional clear-forward)
- "Push LOCATION onto STACK-SYMBOL unless it duplicates the top entry.
-When CLEAR-FORWARD is non-nil, reset `ss-navigation-forward-stack'."
- (when (ss-navigation--location-valid-p location)
- (set stack-symbol (ss-navigation--prune-stack (symbol-value stack-symbol)))
- (unless (ss-navigation--same-location-p location (car (symbol-value stack-symbol)))
- (set stack-symbol (cons location (symbol-value stack-symbol))))
- (when clear-forward
- (setq ss-navigation-forward-stack nil))
- t))
-
-(defun ss-navigation-push-current-location ()
- "Push the current location onto the back stack.
-This is for significant navigation points only and clears forward history."
- (interactive)
- (ss-navigation--push-location
- 'ss-navigation-back-stack
- (ss-navigation--current-location)
- 'clear-forward))
-
-(defun ss-navigation--restore-location (location)
- "Restore LOCATION, returning non-nil when successful."
- (when (ss-navigation--location-valid-p location)
- (let* ((marker (plist-get location :marker))
- (buffer (marker-buffer marker))
- (window-start (plist-get location :window-start)))
- (switch-to-buffer buffer)
- (goto-char marker)
- (when (and (markerp window-start)
- (eq (marker-buffer window-start) buffer)
- (window-live-p (selected-window)))
- (set-window-start (selected-window)
- (marker-position window-start)
- t))
- t)))
-
-(defun ss-navigation--jump-from-stack (stack-symbol target-symbol)
- "Restore the next location from STACK-SYMBOL and push current onto TARGET-SYMBOL."
- (set stack-symbol (ss-navigation--prune-stack (symbol-value stack-symbol)))
- (when-let ((target (car (symbol-value stack-symbol))))
- (let ((current (ss-navigation--current-location)))
- (set stack-symbol (cdr (symbol-value stack-symbol)))
- (when (ss-navigation--location-valid-p current)
- (ss-navigation--push-location target-symbol current))
- (let ((ss-navigation--inhibit-recording t))
- (ss-navigation--restore-location target)))))
-
-(defun ss-navigation--jump-via-command (command)
- "Use COMMAND as a mark-ring fallback jump, recording forward history."
- (when (fboundp command)
- (let ((before (ss-navigation--current-location))
- after)
- (let ((ss-navigation--inhibit-recording t))
- (condition-case nil
- (call-interactively command)
- (error nil)))
- (setq after (ss-navigation--current-location))
- (when (and (ss-navigation--location-valid-p before)
- (ss-navigation--location-valid-p after)
- (not (ss-navigation--same-location-p before after)))
- (ss-navigation--push-location 'ss-navigation-forward-stack before)
- t))))
-
-(defun ss-jump-back ()
- "Move backward through navigation history."
- (interactive)
- (or (ss-navigation--jump-from-stack
- 'ss-navigation-back-stack
- 'ss-navigation-forward-stack)
- (ss-navigation--jump-via-command 'pop-global-mark)
- (ss-navigation--jump-via-command 'pop-to-mark-command)
- (progn
- (message "No back location available")
- nil)))
-
-(defun ss-jump-forward ()
- "Move forward through navigation history."
- (interactive)
- (or (ss-navigation--jump-from-stack
- 'ss-navigation-forward-stack
- 'ss-navigation-back-stack)
- (progn
- (message "No forward location available")
- nil)))
-
-(defun ss-navigation--record-jump (original &rest args)
- "Record the pre-jump location around ORIGINAL with ARGS."
- (if ss-navigation--inhibit-recording
- (apply original args)
- (let ((before (ss-navigation--current-location))
- result)
- (setq result (apply original args))
- (let ((after (ss-navigation--current-location)))
- (when (and (ss-navigation--location-valid-p before)
- (ss-navigation--location-valid-p after)
- (not (ss-navigation--same-location-p before after)))
- (ss-navigation--push-location
- 'ss-navigation-back-stack
- before
- 'clear-forward)))
- result)))
-
-(defun ss-navigation--advise-command (command)
- "Wrap COMMAND so significant jumps record navigation history."
- (unless (advice-member-p #'ss-navigation--record-jump command)
- (advice-add command :around #'ss-navigation--record-jump)))
-
-(defun ss-navigation-setup ()
- "Install navigation history advice for note-related jump commands."
- (dolist (command '(ss-open-journal
- ss-open-journal-today-session
- ss-open-journal-full
- ss-open-moc))
- (ss-navigation--advise-command command))
- (with-eval-after-load 'org
- (ss-navigation--advise-command 'org-open-at-point))
- (with-eval-after-load 'org-agenda
- (dolist (command '(org-agenda-goto
- org-agenda-switch-to
- org-agenda-open-link))
- (ss-navigation--advise-command command)))
- (with-eval-after-load 'denote
- (ss-navigation--advise-command 'denote-open-or-create))
- (with-eval-after-load 'ss-crm
- (dolist (command '(ss-crm-find
- ss-crm-open
- ss-crm-overview))
- (ss-navigation--advise-command command))))
-
-(defvar ss-journal-session-mode-map
- (let ((map (make-sparse-keymap)))
- (define-key map (kbd "C-c C-c") #'ss-journal-session-save-and-dismiss)
- (define-key map (kbd "C-c C-k") #'ss-journal-session-dismiss)
- map)
- "Keymap for focused journal editing sessions.")
-
-(defconst ss-journal-session-header-line
- "Journal session: C-c C-c save and dismiss, C-c C-k dismiss"
- "Header line shown during focused journal editing sessions.")
-
-(define-minor-mode ss-journal-session-mode
- "Minor mode for focused journal editing sessions."
- :lighter " Journal-Session"
- :keymap ss-journal-session-mode-map
- (if ss-journal-session-mode
- (setq-local header-line-format ss-journal-session-header-line)
- (kill-local-variable 'header-line-format)))
-
-(defun ss-journal-session-dismiss ()
- "End the focused journal session without saving automatically."
- (interactive)
- (widen)
- (ss-journal-session-mode -1)
- (quit-window nil (selected-window)))
-
-(defun ss-journal-session-save-and-dismiss ()
- "Save the journal buffer, then end the focused journal session."
- (interactive)
- (save-buffer)
- (ss-journal-session-dismiss))
-
-(defun ss-open-journal ()
- "Open today's journal entry in a focused session, creating it when needed."
- (interactive)
- (ss-open-journal-today-session))
-
-(defun ss-open-journal-today-session ()
- "Open today's journal entry in a focused, dismissable session."
- (interactive)
- (find-file (ss-require-existing-file ss-journal-file))
- (widen)
- (unless (fboundp 'ss-journal-goto-date)
- (user-error "Journal date navigation is unavailable"))
- (ss-journal-goto-date nil 'create)
- (when (fboundp 'ss-journal-ensure-day-sections)
- (ss-journal-ensure-day-sections))
- (org-fold-show-entry)
- (org-fold-show-subtree)
- (org-narrow-to-subtree)
- (ss-journal-session-mode 1))
-
-(defun ss-open-journal-full ()
- "Open `ss-journal-file' with the full buffer visible."
- (interactive)
- (find-file (ss-require-existing-file ss-journal-file))
- (widen))
-
-(defun ss-open-moc ()
- "Open the central MOC note."
- (interactive)
- (find-file (ss-require-existing-file ss-moc-file)))
-
-(defun ss-org-refresh-agenda-files-for-refile (&rest _args)
- "Refresh `org-agenda-files' before refile when agenda setup is available."
- (when (fboundp 'ss-refresh-org-agenda-files)
- (ss-refresh-org-agenda-files)))
-
-(defun ss-org-configure-refile ()
- "Configure Org refile to target any heading in `org-agenda-files'."
- (setq org-refile-targets '((org-agenda-files :regexp . "^\\*+ "))
- org-refile-use-outline-path 'file
- org-outline-path-complete-in-steps nil)
- (unless (advice-member-p #'ss-org-refresh-agenda-files-for-refile #'org-refile)
- (advice-add 'org-refile :before #'ss-org-refresh-agenda-files-for-refile)))
-
-(defun ss-org-setup ()
- "Initialize base Org configuration."
- (use-package org
- :ensure nil
- :config
- (setq org-directory ss-org-directory
- org-catch-invisible-edits 'error
- org-hide-emphasis-markers t
- org-agenda-search-headline-for-time t
- org-agenda-custom-commands
- '(
- ("d" "Daily Agenda"
- ((agenda "")
- (todo "CLARIFY"
- ((org-agenda-overriding-header "Open Questions"))))))
- org-todo-keywords
- '((sequence "TODO(t)" "CLARIFY(c)" "WAIT(w@/!)" "|"
- "DONE(d)" "CANCELLED(x@)"))
- org-log-done 'note
- org-log-into-drawer t)
- (ss-org-configure-refile)
- (add-hook 'org-mode-hook
- (lambda ()
- (setq-local org-hide-emphasis-markers t)
- (when (fboundp 'olivetti-mode)
- (olivetti-mode 1))
- (font-lock-flush)
- (font-lock-ensure))))
-
- (use-package git-auto-commit-mode
- :ensure t
- :pin melpa
- :commands (git-auto-commit-mode)
- :init
- (setq gac-shell-and
- (if (string-match-p "fish\\'" shell-file-name)
- " ; and "
- " && ")))
-
- (add-hook 'emacs-startup-hook
- (lambda ()
- (find-file (ss-require-existing-file ss-moc-file))))
-
- (ss-navigation-setup))
-
-(provide 'ss-org)
-
-;;; ss-org.el ends here
diff --git a/lisp/ss-ui.el b/lisp/ss-ui.el
deleted file mode 100644
index 3dd728c..0000000
--- a/lisp/ss-ui.el
+++ /dev/null
@@ -1,144 +0,0 @@
-;;; ss-ui.el --- Interface configuration -*- lexical-binding: t; -*-
-
-;;; Commentary:
-
-;; Theme, frame, completion, and interface setup.
-
-;;; Code:
-
-(require 'ss-core)
-
-(defun ss-ui--configure-frames ()
- "Apply GUI and terminal frame behavior."
- (when (display-graphic-p)
- (set-frame-size (selected-frame) 140 42)
- (menu-bar-mode -1)
- (tool-bar-mode -1)
- (scroll-bar-mode -1)
- (tooltip-mode -1)
- (set-face-attribute
- 'default nil
- :family "JetBrains Mono" :height 140 :weight 'medium)
- (set-face-attribute
- 'fixed-pitch nil
- :family "JetBrains Mono" :weight 'medium)
- (set-face-attribute
- 'fixed-pitch-serif nil
- :family "JetBrains Mono" :weight 'medium))
-
- (unless (display-graphic-p)
- ;; Terminal menu bar removal stays on startup hook to avoid tty regressions.
- (add-hook 'emacs-startup-hook (lambda () (menu-bar-mode -1)))))
-
-(defun ss-ui--setup-modeline ()
- "Configure the modeline."
- (use-package time
- :ensure nil
- :config
- (setq display-time-24hr-format t
- display-time-day-and-date t
- display-time-default-load-average nil
- calendar-latitude -37.7667
- calendar-longitude 145.0
- calendar-location-name "Melbourne, VIC")
- (display-time-mode 1))
-
- ;; Keep the theme's faces, but make the right edge alignment dynamic.
- (setq-default mode-line-format
- (list
- " "
- "%e"
- mode-line-front-space
- mode-line-mule-info
- mode-line-client
- mode-line-modified
- mode-line-remote
- mode-line-frame-identification
- mode-line-buffer-identification
- " "
- mode-line-position
- '(vc-mode vc-mode)
- " "
- mode-line-modes
- '(:eval (propertize
- " "
- 'display
- `((space :align-to
- (- right
- ,(+ 2 (string-width
- (format-mode-line mode-line-misc-info))))))))
- mode-line-misc-info
- " "
- mode-line-end-spaces)))
-
-(defun ss-ui--setup-completion ()
- "Configure minibuffer and in-buffer completion."
- (use-package vertico
- :ensure t
- :pin melpa
- :init
- (vertico-mode 1))
-
- (use-package orderless
- :ensure t
- :pin melpa
- :custom
- (completion-styles '(orderless basic))
- (completion-category-defaults nil)
- (completion-category-overrides '((file (styles basic partial-completion)))))
-
- (use-package marginalia
- :ensure t
- :pin melpa
- :after vertico
- :init
- (marginalia-mode 1))
-
- (use-package corfu
- :ensure t
- :pin gnu
- :init
- (global-corfu-mode 1)))
-
-(defun ss-ui--setup-writing-layout ()
- "Configure centered writing layout helpers."
- (use-package olivetti
- :ensure t
- :pin melpa
- :custom
- (olivetti-body-width 100)))
-
-(defun ss-ui-setup ()
- "Initialize interface and completion behavior."
- (setq inhibit-startup-message t
- inhibit-startup-screen t
- ring-bell-function 'ignore)
-
- (ss-ui--configure-frames)
-
- (use-package modus-themes
- :ensure nil
- :no-require t
- :config
- (load-theme 'modus-vivendi t))
-
- (use-package dired
- :ensure nil
- :custom
- (dired-use-ls-dired nil))
-
- (line-number-mode 1)
- (column-number-mode 1)
- (show-paren-mode 1)
-
- (setq-default indicate-empty-lines nil)
- (setq-default indicate-buffer-boundaries nil)
- (setq-default fringe-indicator-alist nil)
-
- (ss-ui--setup-modeline)
- (ss-ui--setup-completion)
- (ss-ui--setup-writing-layout))
-
-(provide 'ss-ui)
-
-;;; ss-ui.el ends here
diff --git a/reset b/reset
deleted file mode 100755
index f53c671..0000000
--- a/reset
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh
-
-set -eu
-
-rm -f custom.el
-rm -rf auto-save-list eln-cache elpa
diff --git a/tests/ss-capture-tests.el b/tests/ss-capture-tests.el
deleted file mode 100644
index 006c90d..0000000
--- a/tests/ss-capture-tests.el
+++ /dev/null
@@ -1,176 +0,0 @@
-;;; ss-capture-tests.el --- Tests for ss-capture -*- lexical-binding: t; -*-
-
-;;; Commentary:
-
-;; Focused ERT coverage for journal capture structure helpers.
-
-;;; Code:
-
-(add-to-list 'load-path (expand-file-name "../lisp" (file-name-directory load-file-name)))
-
-(require 'cl-lib)
-(require 'ert)
-(require 'ss-capture)
-(require 'ss-org)
-
-(ert-deftest ss-jump-back-restores-previous-location-and-enables-forward ()
- (let ((ss-navigation-back-stack nil)
- (ss-navigation-forward-stack nil)
- (buffer-a (generate-new-buffer " *ss-nav-a*"))
- (buffer-b (generate-new-buffer " *ss-nav-b*")))
- (unwind-protect
- (save-window-excursion
- (with-current-buffer buffer-a
- (insert "alpha")
- (goto-char 3))
- (with-current-buffer buffer-b
- (insert "bravo")
- (goto-char 5))
- (switch-to-buffer buffer-a)
- (ss-navigation-push-current-location)
- (switch-to-buffer buffer-b)
- (should (ss-jump-back))
- (should (eq (current-buffer) buffer-a))
- (should (= (point) 3))
- (should (ss-jump-forward))
- (should (eq (current-buffer) buffer-b))
- (should (= (point) 5)))
- (kill-buffer buffer-a)
- (kill-buffer buffer-b))))
-
-(ert-deftest ss-navigation-push-current-location-clears-forward-after-back ()
- (let ((ss-navigation-back-stack nil)
- (ss-navigation-forward-stack nil)
- (buffer-a (generate-new-buffer " *ss-nav-a*"))
- (buffer-b (generate-new-buffer " *ss-nav-b*")))
- (unwind-protect
- (save-window-excursion
- (with-current-buffer buffer-a
- (insert "alpha")
- (goto-char 2))
- (with-current-buffer buffer-b
- (insert "bravo")
- (goto-char 4))
- (switch-to-buffer buffer-a)
- (ss-navigation-push-current-location)
- (switch-to-buffer buffer-b)
- (should (ss-jump-back))
- (should ss-navigation-forward-stack)
- (ss-navigation-push-current-location)
- (should-not ss-navigation-forward-stack))
- (kill-buffer buffer-a)
- (kill-buffer buffer-b))))
-
-(ert-deftest ss-navigation-record-jump-skips-noop-movements ()
- (let ((ss-navigation-back-stack nil)
- (ss-navigation-forward-stack nil))
- (with-temp-buffer
- (switch-to-buffer (current-buffer))
- (insert "alpha")
- (goto-char 2)
- (ss-navigation--record-jump (lambda () nil))
- (should-not ss-navigation-back-stack)
- (should-not ss-navigation-forward-stack))))
-
-(ert-deftest ss-journal-ensure-day-sections-adds-all-standard-sections ()
- (with-temp-buffer
- (org-mode)
- (insert "#+title: Journal\n"
- "#+startup: overview\n\n"
- "* 2026\n"
- "** 2026-04-08 Wednesday\n"
- "*** Tasks\n"
- "**** TODO Existing\n\n"
- "** 2026-04-09 Thursday\n")
- (goto-char (point-min))
- (re-search-forward "^\\*\\* 2026-04-09 Thursday$")
- (goto-char (match-beginning 0))
- (ss-journal-ensure-day-sections)
- (should (string-match-p
- (regexp-quote
- "** 2026-04-09 Thursday\n*** Tasks\n*** Notes\n*** Meetings\n")
- (buffer-string)))))
-
-(ert-deftest ss-open-journal-narrows-to-today-when-entry-exists ()
- (let* ((file (make-temp-file "ss-journal" nil ".org"))
- (ss-journal-file file)
- (test-time (encode-time 0 0 12 9 4 2026)))
- (unwind-protect
- (cl-letf (((symbol-function 'current-time)
- (lambda ()
- test-time)))
- (with-temp-file file
- (insert "#+title: Journal\n"
- "* 2026\n"
- "** 2026-04-09 Thursday\n"
- "*** Notes\n"))
- (ss-open-journal)
- (should (buffer-narrowed-p))
- (should (equal (org-get-outline-path t)
- '("2026" "2026-04-09 Thursday")))
- (should (looking-at-p "^\\*\\* 2026-04-09 Thursday$")))
- (when-let ((buffer (get-file-buffer file)))
- (kill-buffer buffer))
- (when (file-exists-p file)
- (delete-file file)))))
-
-(ert-deftest ss-open-journal-reveals-folded-today-subtree ()
- (let* ((file (make-temp-file "ss-journal" nil ".org"))
- (ss-journal-file file)
- (test-time (encode-time 0 0 12 9 4 2026)))
- (unwind-protect
- (cl-letf (((symbol-function 'current-time)
- (lambda ()
- test-time)))
- (with-temp-file file
- (insert "#+title: Journal\n"
- "* 2026\n"
- "** 2026-04-09 Thursday\n"
- "*** Notes\n"
- "Body\n"))
- (with-current-buffer (find-file-noselect file)
- (org-overview))
- (ss-open-journal)
- (should (buffer-narrowed-p))
- (should-not
- (save-excursion
- (goto-char (point-min))
- (re-search-forward "^\\*\\*\\* Notes$" nil t)
- (invisible-p (point))))
- (should-not
- (save-excursion
- (goto-char (point-min))
- (re-search-forward "^Body$" nil t)
- (invisible-p (point)))))
- (when-let ((buffer (get-file-buffer file)))
- (kill-buffer buffer))
- (when (file-exists-p file)
- (delete-file file)))))
-
-(ert-deftest ss-open-journal-creates-missing-today-entry-with-standard-sections ()
- (let* ((file (make-temp-file "ss-journal" nil ".org"))
- (ss-journal-file file)
- (test-time (encode-time 0 0 12 9 4 2026)))
- (unwind-protect
- (cl-letf (((symbol-function 'current-time)
- (lambda ()
- test-time)))
- (with-temp-file file
- (insert "#+title: Journal\n"
- "* 2026\n"
- "** 2026-04-08 Wednesday\n"
- "*** Notes\n"))
- (ss-open-journal)
- (should (buffer-narrowed-p))
- (should (equal (org-get-outline-path t)
- '("2026" "2026-04-09 Thursday")))
- (should (string-match-p
- (regexp-quote
- "** 2026-04-09 Thursday\n*** Tasks\n*** Notes\n*** Meetings\n")
- (buffer-string))))
- (when-let ((buffer (get-file-buffer file)))
- (kill-buffer buffer))
- (when (file-exists-p file)
- (delete-file file)))))
-
-;;; ss-capture-tests.el ends here
diff --git a/tests/ss-crm-tests.el b/tests/ss-crm-tests.el
deleted file mode 100644
index bc88ee4..0000000
--- a/tests/ss-crm-tests.el
+++ /dev/null
@@ -1,92 +0,0 @@
-;;; ss-crm-tests.el --- Tests for ss-crm -*- lexical-binding: t; -*-
-
-;;; Commentary:
-
-;; Focused ERT coverage for CRM lookup and prompt helpers.
-
-;;; Code:
-
-(add-to-list 'load-path (expand-file-name "../lisp" (file-name-directory load-file-name)))
-
-(require 'ert)
-(require 'cl-lib)
-(require 'ss-crm)
-
-(ert-deftest ss-crm-known-property-values-sorts-and-deduplicates ()
- (cl-letf (((symbol-function 'ss-crm-entries)
- (lambda ()
- (list (list :role "Engineer")
- (list :role " engineer ")
- (list :role "Architect")
- (list :role "")
- (list :role nil)))))
- (should (equal (ss-crm-known-property-values "ROLE")
- '(" engineer " "Architect" "Engineer")))))
-
-(ert-deftest ss-crm-known-person-names-returns-sorted-top-level-names ()
- (cl-letf (((symbol-function 'ss-crm-entries)
- (lambda ()
- (list (list :name "Zoe")
- (list :name "Alice")
- (list :name "Bob")))))
- (should (equal (ss-crm-known-person-names)
- '("Alice" "Bob" "Zoe")))))
-
-(ert-deftest ss-crm-lookup-values-merges-seeded-and-derived-values ()
- (cl-letf (((symbol-function 'ss-crm-known-property-values)
- (lambda (_property)
- '("Team B" "Team A"))))
- (should (equal (ss-crm-lookup-values "TEAM" '("Team A" "Team C"))
- '("Team A" "Team B" "Team C")))))
-
-(ert-deftest ss-crm-read-choice-returns-nil-for-none-selection ()
- (cl-letf (((symbol-function 'completing-read)
- (lambda (&rest _args)
- "[none]")))
- (should-not (ss-crm-read-choice "Role: " '("Engineer")
- :allow-blank t
- :allow-new t))))
-
-(ert-deftest ss-crm-read-choice-warns-on-new-case-insensitive-duplicate ()
- (let (warning)
- (cl-letf (((symbol-function 'completing-read)
- (lambda (&rest _args)
- "sydney"))
- ((symbol-function 'yes-or-no-p)
- (lambda (&rest _args)
- t))
- ((symbol-function 'display-warning)
- (lambda (_type message &rest _args)
- (setq warning message))))
- (should (equal (ss-crm-read-choice "Location: " '("Sydney")
- :allow-blank t
- :allow-new t)
- "sydney"))
- (should (string-match-p "Sydney" warning)))))
-
-(ert-deftest ss-crm-read-choice-does-not-warn-for-existing-selection ()
- (let (warning)
- (cl-letf (((symbol-function 'completing-read)
- (lambda (&rest _args)
- "Sydney"))
- ((symbol-function 'display-warning)
- (lambda (_type message &rest _args)
- (setq warning message))))
- (should (equal (ss-crm-read-choice "Location: " '("Sydney" "sydney")
- :allow-blank t
- :allow-new t
- :require-match t)
- "Sydney"))
- (should-not warning))))
-
-(ert-deftest ss-crm-read-manager-uses-known-person-names ()
- (cl-letf (((symbol-function 'ss-crm-known-person-names)
- (lambda ()
- '("Alice" "Bob")))
- ((symbol-function 'ss-crm-read-choice)
- (lambda (_prompt choices &rest _plist)
- choices)))
- (should (equal (ss-crm-read-manager)
- '("Alice" "Bob")))))
-
-;;; ss-crm-tests.el ends here
diff --git a/tests/ss-org-tests.el b/tests/ss-org-tests.el
deleted file mode 100644
index b8e1eb5..0000000
--- a/tests/ss-org-tests.el
+++ /dev/null
@@ -1,88 +0,0 @@
-;;; ss-org-tests.el --- Tests for ss-org -*- lexical-binding: t; -*-
-
-;;; Commentary:
-
-;; Focused ERT coverage for Org refile configuration.
-
-;;; Code:
-
-(add-to-list 'load-path (expand-file-name "../lisp" (file-name-directory load-file-name)))
-
-(require 'ert)
-(require 'cl-lib)
-(require 'org)
-(require 'ss-org)
-
-(ert-deftest ss-org-configure-refile-sets-org-variables ()
- (let ((old-refile-targets (and (boundp 'org-refile-targets)
- org-refile-targets))
- (old-refile-use-outline-path (and (boundp 'org-refile-use-outline-path)
- org-refile-use-outline-path))
- (old-outline-path-complete-in-steps
- (and (boundp 'org-outline-path-complete-in-steps)
- org-outline-path-complete-in-steps)))
- (unwind-protect
- (progn
- (advice-remove 'org-refile #'ss-org-refresh-agenda-files-for-refile)
- (setq org-refile-targets nil
- org-refile-use-outline-path nil
- org-outline-path-complete-in-steps t)
- (ss-org-configure-refile)
- (should (equal org-refile-targets
- '((org-agenda-files :regexp . "^\\*+ "))))
- (should (eq org-refile-use-outline-path 'file))
- (should-not org-outline-path-complete-in-steps))
- (setq org-refile-targets old-refile-targets
- org-refile-use-outline-path old-refile-use-outline-path
- org-outline-path-complete-in-steps
- old-outline-path-complete-in-steps)
- (advice-remove 'org-refile #'ss-org-refresh-agenda-files-for-refile))))
-
-(ert-deftest ss-org-configure-refile-discovers-headings-in-agenda-files ()
- (let* ((file (make-temp-file "ss-org-refile-" nil ".org"
- "* Alpha\n** Beta\n"))
- (file-name (file-name-nondirectory file))
- (org-agenda-files (list file))
- (old-refile-targets (and (boundp 'org-refile-targets)
- org-refile-targets))
- (old-refile-use-outline-path (and (boundp 'org-refile-use-outline-path)
- org-refile-use-outline-path))
- (old-outline-path-complete-in-steps
- (and (boundp 'org-outline-path-complete-in-steps)
- org-outline-path-complete-in-steps))
- targets)
- (unwind-protect
- (progn
- (advice-remove 'org-refile #'ss-org-refresh-agenda-files-for-refile)
- (ss-org-configure-refile)
- (with-current-buffer (find-file-noselect file)
- (setq targets (org-refile-get-targets)))
- (should (assoc (format "%s/Alpha" file-name) targets))
- (should (assoc (format "%s/Alpha/Beta" file-name) targets)))
- (setq org-refile-targets old-refile-targets
- org-refile-use-outline-path old-refile-use-outline-path
- org-outline-path-complete-in-steps
- old-outline-path-complete-in-steps)
- (advice-remove 'org-refile #'ss-org-refresh-agenda-files-for-refile)
- (when-let ((buffer (get-file-buffer file)))
- (kill-buffer buffer))
- (delete-file file))))
-
-(ert-deftest ss-org-configure-refile-adds-refresh-advice ()
- (unwind-protect
- (progn
- (advice-remove 'org-refile #'ss-org-refresh-agenda-files-for-refile)
- (ss-org-configure-refile)
- (should (advice-member-p #'ss-org-refresh-agenda-files-for-refile
- 'org-refile)))
- (advice-remove 'org-refile #'ss-org-refresh-agenda-files-for-refile)))
-
-(ert-deftest ss-org-refresh-agenda-files-for-refile-reuses-agenda-helper ()
- (let (called)
- (cl-letf (((symbol-function 'ss-refresh-org-agenda-files)
- (lambda (&rest _args)
- (setq called t))))
- (ss-org-refresh-agenda-files-for-refile)
- (should called))))
-
-;;; ss-org-tests.el ends here \ No newline at end of file