Commit f9ccd73c06a6

Vincent Demeester <vincent@sbr.pm>
2026-01-16 18:41:00
feat(journelly): add comprehensive auto-tagging for Claude session entries
Implement intelligent auto-tagging system that detects tags from: - File types (git status): .nix → #nixos, .el → #emacs, .go → #golang, etc. - Git repository: home → #homelab, tekton* → #tekton - Keywords in summary text (70+ tag patterns across 16 categories) Tag categories: - Development: #debugging #development #refactoring #testing #documentation - Infrastructure: #kubernetes #docker #git #journelly - AI/LLM: #llm #ai #claude #anthropic #gemini #ollama #openai - Cloud: #cloud #digitalocean #gcp #oracle-cloud #aws #azure - Architecture: #arm #aarch64 #x86_64 #cross-compile #riscv - Monitoring: #monitoring #prometheus #grafana #alerting - Media: #media #jellyfin #plex - Desktop: #niri #sway #wayland #x11 - Networking: #networking #wireguard #vpn #dns - Security: #security #auth #encryption #secrets #yubikey - Backup: #backup #storage #syncthing #restic - Communication: #email #xmpp - Database: #database #postgres #sqlite - Web: #web #nginx #caddy - Hardware: #hardware #raspberry-pi #keyboard - Configuration: #home-manager #flakes #deployment #ci-cd Example: Input: "Fixed Wireguard VPN deployment on Raspberry Pi 4 with ARM" Output: #deployment #raspberry-pi #vpn #wireguard #arm #debugging #homelab Benefits: - Automatic categorization of technical work - Better searchability in journal entries - Context-aware tagging based on actual work being done - No manual tagging required for Claude sessions Manual journal entries can still use #tags naturally in their text. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 0fc2dd8
Changed files (2)
dots
.config
claude
skills
emacs
site-lisp
dots/.config/claude/skills/Org/SKILL.md
@@ -239,10 +239,15 @@ Productive day working on Claude skills.
 - **Only create when explicitly asked** by the user
 - Use the `journelly-claude-session` function via Emacs batch mode
 - Format: Brief, factual, technical summaries of work accomplished
+- **Auto-tagging**: Tags are automatically detected and appended based on:
+  - File types changed (`.nix` → `#nixos`, `.el` → `#emacs`, `.go` → `#golang`)
+  - Git repository (`home` → `#homelab`, `tekton*` → `#tekton`)
+  - Keywords (`fix/bug` → `#debugging`, `feature/implement` → `#development`)
+  - Tools mentioned (`docker`, `kubernetes`, etc.)
 - Examples:
-  - "Fixed bug in authentication handler"
-  - "Implemented feature X with tests"
-  - "Refactored module Y for better performance"
+  - "Fixed bug in authentication handler" → auto-tagged with `#debugging`
+  - "Implemented feature X with tests" → auto-tagged with `#development #testing`
+  - "Refactored module Y for better performance" → auto-tagged with `#refactoring`
 - **Never** write in first person or user's voice
 - Keep entries concise (1-2 lines per timestamp)
 
dots/.config/emacs/site-lisp/journelly.el
@@ -18,9 +18,9 @@
 ;;
 ;; Entry formats:
 ;; - Regular: * [YYYY-MM-DD Day HH:MM] @ Location (hostname)
-;;            - HH:MM :: entry content (appended entries)
+;;            - HH:MM :: entry content #tags (appended entries, tags optional)
 ;; - Claude:  * [YYYY-MM-DD Day HH:MM] @ Claude session
-;;            - HH:MM :: session summary (automated only)
+;;            - HH:MM :: session summary #auto-tagged (automated only)
 ;;
 ;; Usage:
 ;;   (require 'journelly)
@@ -180,17 +180,227 @@ Creates today's entry if it doesn't exist, or appends to existing entry."
     (save-buffer))
   (message "Journal entry added"))
 
+(defun journelly--detect-tags (summary)
+  "Auto-detect tags for Claude session based on context and SUMMARY.
+Returns a list of tag strings (without # prefix)."
+  (let ((tags '()))
+
+    ;; Detect from file extensions (git status)
+    (when (file-exists-p ".git")
+      (let ((changed-files (shell-command-to-string "git status --short 2>/dev/null")))
+        (when (string-match-p "\\.nix" changed-files)
+          (push "nixos" tags))
+        (when (string-match-p "\\.go" changed-files)
+          (push "golang" tags))
+        (when (string-match-p "\\.el" changed-files)
+          (push "emacs" tags))
+        (when (string-match-p "\\.py" changed-files)
+          (push "python" tags))
+        (when (string-match-p "\\.rs" changed-files)
+          (push "rust" tags))
+        (when (string-match-p "Dockerfile\\|docker-compose" changed-files)
+          (push "docker" tags))
+        (when (string-match-p "\\.ya?ml" changed-files)
+          (push "kubernetes" tags))
+        (when (string-match-p "skills/" changed-files)
+          (push "claude-skills" tags))))
+
+    ;; Detect from git repository name
+    (when (file-exists-p ".git")
+      (let* ((remote-url (shell-command-to-string "git remote get-url origin 2>/dev/null"))
+             (repo-name (when (string-match "/\\([^/]+\\)\\.git" remote-url)
+                         (match-string 1 remote-url))))
+        (cond
+         ((string= repo-name "home") (push "homelab" tags))
+         ((string-match-p "tekton" repo-name) (push "tekton" tags))
+         ((string-match-p "pipeline" repo-name) (push "tekton" tags)))))
+
+    ;; Detect from keywords in summary
+    (let ((summary-lower (downcase summary)))
+      ;; Development activities
+      (when (string-match-p "\\(bug\\|fix\\|debug\\)" summary-lower)
+        (push "debugging" tags))
+      (when (string-match-p "\\(feature\\|implement\\)" summary-lower)
+        (push "development" tags))
+      (when (string-match-p "refactor" summary-lower)
+        (push "refactoring" tags))
+      (when (string-match-p "\\(test\\|testing\\)" summary-lower)
+        (push "testing" tags))
+      (when (string-match-p "\\(doc\\|documentation\\)" summary-lower)
+        (push "documentation" tags))
+
+      ;; Infrastructure & tools
+      (when (string-match-p "\\(kubernetes\\|k8s\\)" summary-lower)
+        (push "kubernetes" tags))
+      (when (string-match-p "docker" summary-lower)
+        (push "docker" tags))
+      (when (string-match-p "\\(commit\\|push\\|git\\)" summary-lower)
+        (push "git" tags))
+      (when (string-match-p "\\(capture\\|journal\\)" summary-lower)
+        (push "journelly" tags))
+
+      ;; AI/LLM
+      (when (string-match-p "\\(llm\\|language model\\)" summary-lower)
+        (push "llm" tags))
+      (when (string-match-p "\\(ai\\|artificial intelligence\\)" summary-lower)
+        (push "ai" tags))
+      (when (string-match-p "claude" summary-lower)
+        (push "claude" tags))
+      (when (string-match-p "anthropic" summary-lower)
+        (push "anthropic" tags))
+      (when (string-match-p "gemini" summary-lower)
+        (push "gemini" tags))
+      (when (string-match-p "ollama" summary-lower)
+        (push "ollama" tags))
+      (when (string-match-p "\\(openai\\|chatgpt\\|gpt\\)" summary-lower)
+        (push "openai" tags))
+
+      ;; Cloud providers
+      (when (string-match-p "\\(cloud\\|infrastructure\\)" summary-lower)
+        (push "cloud" tags))
+      (when (string-match-p "\\(digitalocean\\|\\bdo\\b\\)" summary-lower)
+        (push "digitalocean" tags))
+      (when (string-match-p "\\(gcp\\|google cloud\\)" summary-lower)
+        (push "gcp" tags))
+      (when (string-match-p "oracle.*cloud" summary-lower)
+        (push "oracle-cloud" tags))
+      (when (string-match-p "\\(aws\\|amazon web services\\)" summary-lower)
+        (push "aws" tags))
+      (when (string-match-p "azure" summary-lower)
+        (push "azure" tags))
+
+      ;; Architecture
+      (when (string-match-p "\\(arm\\|aarch64\\)" summary-lower)
+        (push "arm" tags))
+      (when (string-match-p "x86.64" summary-lower)
+        (push "x86_64" tags))
+      (when (string-match-p "\\(cross.compile\\|cross compile\\)" summary-lower)
+        (push "cross-compile" tags))
+      (when (string-match-p "riscv" summary-lower)
+        (push "riscv" tags))
+
+      ;; Monitoring/Observability
+      (when (string-match-p "\\(monitoring\\|observability\\)" summary-lower)
+        (push "monitoring" tags))
+      (when (string-match-p "prometheus" summary-lower)
+        (push "prometheus" tags))
+      (when (string-match-p "grafana" summary-lower)
+        (push "grafana" tags))
+      (when (string-match-p "\\(alert\\|alerting\\)" summary-lower)
+        (push "alerting" tags))
+
+      ;; Media
+      (when (string-match-p "media" summary-lower)
+        (push "media" tags))
+      (when (string-match-p "jellyfin" summary-lower)
+        (push "jellyfin" tags))
+      (when (string-match-p "plex" summary-lower)
+        (push "plex" tags))
+
+      ;; Desktop/Window managers
+      (when (string-match-p "niri" summary-lower)
+        (push "niri" tags))
+      (when (string-match-p "sway" summary-lower)
+        (push "sway" tags))
+      (when (string-match-p "wayland" summary-lower)
+        (push "wayland" tags))
+      (when (string-match-p "x11" summary-lower)
+        (push "x11" tags))
+
+      ;; Networking
+      (when (string-match-p "\\(network\\|networking\\)" summary-lower)
+        (push "networking" tags))
+      (when (string-match-p "wireguard" summary-lower)
+        (push "wireguard" tags))
+      (when (string-match-p "\\(vpn\\|virtual private network\\)" summary-lower)
+        (push "vpn" tags))
+      (when (string-match-p "\\(dns\\|domain name\\)" summary-lower)
+        (push "dns" tags))
+
+      ;; Security
+      (when (string-match-p "security" summary-lower)
+        (push "security" tags))
+      (when (string-match-p "\\(auth\\|authentication\\)" summary-lower)
+        (push "auth" tags))
+      (when (string-match-p "\\(encryption\\|encrypt\\)" summary-lower)
+        (push "encryption" tags))
+      (when (string-match-p "\\(secret\\|agenix\\)" summary-lower)
+        (push "secrets" tags))
+      (when (string-match-p "yubikey" summary-lower)
+        (push "yubikey" tags))
+
+      ;; Backup/Storage
+      (when (string-match-p "backup" summary-lower)
+        (push "backup" tags))
+      (when (string-match-p "storage" summary-lower)
+        (push "storage" tags))
+      (when (string-match-p "syncthing" summary-lower)
+        (push "syncthing" tags))
+      (when (string-match-p "restic" summary-lower)
+        (push "restic" tags))
+
+      ;; Communication
+      (when (string-match-p "\\(email\\|mail\\)" summary-lower)
+        (push "email" tags))
+      (when (string-match-p "\\(mu4e\\|notmuch\\)" summary-lower)
+        (push "email" tags))
+      (when (string-match-p "xmpp" summary-lower)
+        (push "xmpp" tags))
+
+      ;; Databases
+      (when (string-match-p "\\(database\\|\\bdb\\b\\)" summary-lower)
+        (push "database" tags))
+      (when (string-match-p "\\(postgres\\|postgresql\\)" summary-lower)
+        (push "postgres" tags))
+      (when (string-match-p "sqlite" summary-lower)
+        (push "sqlite" tags))
+
+      ;; Web/HTTP
+      (when (string-match-p "\\(web\\|http\\|https\\)" summary-lower)
+        (push "web" tags))
+      (when (string-match-p "nginx" summary-lower)
+        (push "nginx" tags))
+      (when (string-match-p "caddy" summary-lower)
+        (push "caddy" tags))
+
+      ;; Hardware
+      (when (string-match-p "hardware" summary-lower)
+        (push "hardware" tags))
+      (when (string-match-p "\\(raspberry.pi\\|rpi\\)" summary-lower)
+        (push "raspberry-pi" tags))
+      (when (string-match-p "keyboard" summary-lower)
+        (push "keyboard" tags))
+
+      ;; Configuration
+      (when (string-match-p "home.manager" summary-lower)
+        (push "home-manager" tags))
+      (when (string-match-p "flake" summary-lower)
+        (push "flakes" tags))
+      (when (string-match-p "\\(deploy\\|deployment\\)" summary-lower)
+        (push "deployment" tags))
+      (when (string-match-p "\\(ci\\|cd\\|pipeline\\)" summary-lower)
+        (push "ci-cd" tags)))
+
+    ;; Remove duplicates and return
+    (delete-dups tags)))
+
 (defun journelly-claude-session (summary)
   "Add Claude session SUMMARY to today's Claude session entry.
+Auto-detects and appends tags based on context and content.
 Creates entry if it doesn't exist, or appends to existing entry."
   (interactive "sClaude session: ")
-  (let ((timestamp (format-time-string "%H:%M")))
+  (let* ((timestamp (format-time-string "%H:%M"))
+         (tags (journelly--detect-tags summary))
+         (tags-string (if tags
+                          (concat " " (mapconcat (lambda (tag) (concat "#" tag)) tags " "))
+                        ""))
+         (entry (format "- %s :: %s%s\n" timestamp summary tags-string)))
     (with-current-buffer (find-file-noselect org-journelly-file)
       (save-excursion
         (journelly-claude-capture-target)
-        (insert (format "- %s :: %s\n" timestamp summary)))
+        (insert entry))
       (save-buffer))
-    (message "Claude session logged")))
+    (message "Claude session logged%s" (if tags (format " with tags: %s" tags-string) ""))))
 
 (defun journelly-open ()
   "Open Journelly.org file and jump to today's entry or top."