Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 18 additions & 17 deletions deps.edn
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
{:deps
{org.clojure/clojure {:mvn/version "1.12.3"}
metosin/malli {:mvn/version "0.19.2"}
cli-matic/cli-matic {:mvn/version "0.5.4"}
org.clojure/data.json {:mvn/version "2.5.1"}
metosin/jsonista {:mvn/version "0.3.11"}
hiccup/hiccup {:mvn/version "2.0.0"}
funcool/lentes {:mvn/version "1.3.3"}
clj-commons/clj-yaml {:mvn/version "1.0.29"}
org.clj-commons/pretty {:mvn/version "3.6.7"}
io.github.paintparty/bling {:mvn/version "0.8.8"}
mvxcvi/puget {:mvn/version "1.3.4"}
org.slf4j/slf4j-nop {:mvn/version "2.0.17"}
;; SQL database dependencies
com.github.seancorfield/next.jdbc {:mvn/version "1.3.955"}
com.github.seancorfield/honeysql {:mvn/version "2.7.1350"}
dev.weavejester/ragtime.next-jdbc {:mvn/version "0.9.4"}
org.xerial/sqlite-jdbc {:mvn/version "3.47.1.0"}}
{org.clojure/clojure {:mvn/version "1.12.3"}
metosin/malli {:mvn/version "0.19.2"}
cli-matic/cli-matic {:mvn/version "0.5.4"}
org.clojure/data.json {:mvn/version "2.5.1"}
metosin/jsonista {:mvn/version "0.3.11"}
hiccup/hiccup {:mvn/version "2.0.0"}
funcool/lentes {:mvn/version "1.3.3"}
clj-commons/clj-yaml {:mvn/version "1.0.29"}
org.clj-commons/pretty {:mvn/version "3.6.7"}
io.github.paintparty/bling {:mvn/version "0.8.8"}
mvxcvi/puget {:mvn/version "1.3.4"}
org.slf4j/slf4j-nop {:mvn/version "2.0.17"}
failjure/failjure {:mvn/version "2.3.0"}
;; SQL database dependencies
com.github.seancorfield/next.jdbc {:mvn/version "1.3.955"}
com.github.seancorfield/honeysql {:mvn/version "2.7.1350"}
dev.weavejester/ragtime.next-jdbc {:mvn/version "0.9.4"}
org.xerial/sqlite-jdbc {:mvn/version "3.47.1.0"}}

:paths ["src"
"classes"
Expand Down
28 changes: 18 additions & 10 deletions src/clojure_skills/cli.clj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
[clojure-skills.search :as search]
[clojure-skills.sync :as sync]
[clojure.string :as str]
[failjure.core :as f]
[next.jdbc :as jdbc]))

(set! *warn-on-reflection* true)
Expand Down Expand Up @@ -77,10 +78,13 @@
(*exit-fn* 1))))

(defn handle-command-errors
"Execute a command function with unified error handling."
"Execute a command function with unified error handling for both exceptions and failjure."
[operation-name f & args]
(try
(apply f args)
(let [result (apply f args)]
(when (f/failed? result)
(print-error (f/message result))
(*exit-fn* 1)))
(catch Exception e
(print-error-with-exception (str operation-name " failed") e)
(*exit-fn* 1))))
Expand Down Expand Up @@ -264,12 +268,12 @@
"List all skills associated with a prompt via fragments."
[db prompt-id]
(jdbc/execute! db
["SELECT pfs.position, s.*
["SELECT pfs.position, s.*
FROM prompt_references pr
JOIN prompt_fragments pf ON pr.target_fragment_id = pf.id
JOIN prompt_fragment_skills pfs ON pf.id = pfs.fragment_id
JOIN skills s ON pfs.skill_id = s.id
WHERE pr.source_prompt_id = ?
WHERE pr.source_prompt_id = ?
AND pr.reference_type = 'fragment'
ORDER BY pfs.position"
prompt-id]))
Expand All @@ -278,12 +282,12 @@
"List skills in embedded fragments (fragments that should be embedded in the prompt)."
[db prompt-id]
(jdbc/execute! db
["SELECT pfs.position, s.*
["SELECT pfs.position, s.*
FROM prompt_references pr
JOIN prompt_fragments pf ON pr.target_fragment_id = pf.id
JOIN prompt_fragment_skills pfs ON pf.id = pfs.fragment_id
JOIN skills s ON pfs.skill_id = s.id
WHERE pr.source_prompt_id = ?
WHERE pr.source_prompt_id = ?
AND pr.reference_type = 'fragment'
AND pf.name NOT LIKE '%-ref-%'
ORDER BY pfs.position"
Expand All @@ -293,24 +297,24 @@
"List skills in reference fragments (fragments that are tracked but not embedded)."
[db prompt-id]
(jdbc/execute! db
["SELECT pfs.position, s.*
["SELECT pfs.position, s.*
FROM prompt_references pr
JOIN prompt_fragments pf ON pr.target_fragment_id = pf.id
JOIN prompt_fragment_skills pfs ON pf.id = pfs.fragment_id
JOIN skills s ON pfs.skill_id = s.id
WHERE pr.source_prompt_id = ?
WHERE pr.source_prompt_id = ?
AND pr.reference_type = 'fragment'
AND pf.name LIKE '%-ref-%'
ORDER BY pfs.position"
prompt-id]))

(defn render-prompt-content
"Compose full prompt content by combining prompt intro, embedded skills, and references.

Args:
db - Database connection
prompt - Prompt map with :prompts/id and :prompts/content

Returns:
String with composed markdown content"
[db prompt]
Expand Down Expand Up @@ -409,11 +413,15 @@
(let [[config db] (load-config-and-db)
stats (search/get-stats db)
db-path (config/get-db-path config)
config-file-path (config/get-config-file-path)
project-config-path (config/get-project-config-file-path)
permissions (get config :permissions {})
format (output/get-output-format json human config)]
(output/output
{:type :stats
:configuration {:database-path db-path
:config-file-path config-file-path
:project-config-path project-config-path
:auto-migrate (get-in config [:database :auto-migrate] true)
:skills-directory (get-in config [:project :skills-dir] "skills")
:prompts-directory (get-in config [:project :prompts-dir] "prompts")
Expand Down
29 changes: 23 additions & 6 deletions src/clojure_skills/db/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,22 @@
"Database connection management and operations."
(:require
[clojure-skills.config :as config]
[clojure-skills.db.migrate :as migrate]))
[clojure-skills.db.migrate :as migrate]
[clojure.java.io :as io]
[failjure.core :as f]))

(defn ensure-db-dir
"Ensure the directory containing the database file exists."
[db-path]
(let [db-file (io/file db-path)]
(when-let [parent (.getParentFile db-file)]
(when-not (.exists parent)
(.mkdirs parent)))))

(defn get-db
"Get database connection spec from config.
Returns a simple db-spec map that next.jdbc can use directly.
Foreign keys are enabled for SQLite to support CASCADE deletes."
Returns a simple db-spec map that next.jdbc can use directly.
Foreign keys are enabled for SQLite to support CASCADE deletes."
([]
(get-db (config/load-config)))
([config]
Expand All @@ -20,7 +30,14 @@
(defn init-db
"Initialize database with Ragtime migrations."
([db]
(migrate/migrate-db db))
(let [result (migrate/migrate-db db)]
(if (f/failed? result)
(throw (ex-info "Database initialization failed"
{:reason (f/message result)}))
result)))
([]
(let [db (get-db)]
(init-db db))))
(let [config (config/load-config)
db-path (config/get-db-path config)]
(ensure-db-dir db-path)
(let [db (get-db config)]
(init-db db)))))
129 changes: 84 additions & 45 deletions src/clojure_skills/db/migrate.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,83 +2,122 @@
"Ragtime-based database migrations."
(:require
[clojure-skills.config :as config]
[failjure.core :as f]
[ragtime.next-jdbc :as ragtime-jdbc]
[ragtime.repl :as ragtime-repl]
[ragtime.strategy]))

(defn load-config
"Load Ragtime configuration from application config."
[]
(let [app-config (config/load-config)
db-path (config/get-db-path app-config)
db-spec {:dbtype "sqlite" :dbname db-path}]
{:datastore (ragtime-jdbc/sql-database db-spec)
:migrations (ragtime-jdbc/load-resources "migrations")
:strategy ragtime.strategy/apply-new}))
(f/try-all [app-config (config/load-config)
db-path (config/get-db-path app-config)
db-spec {:dbtype "sqlite" :dbname db-path}]
{:datastore (ragtime-jdbc/sql-database db-spec)
:migrations (ragtime-jdbc/load-resources "migrations")
:strategy ragtime.strategy/apply-new}
(f/when-failed [e]
(f/fail "Failed to load config: %s" (f/message e)))))

(defn migrate
"Run all pending migrations."
[]
(let [config (load-config)]
(println "Running migrations...")
(ragtime-repl/migrate config)
(println "Migrations complete.")))
(f/attempt-all [config (load-config)]
(do
(println "Running migrations...")
(ragtime-repl/migrate config)
(println "Migrations complete."))
(f/when-failed [e]
(do
(println "Migration failed:" (f/message e))
(f/fail "Migration failed: %s" (f/message e))))))

(defn rollback
"Rollback the last migration."
([]
(rollback 1))
([amount]
(let [config (load-config)]
(println (format "Rolling back %d migration(s)..." amount))
(ragtime-repl/rollback config amount)
(println "Rollback complete."))))
(f/attempt-all [config (load-config)]
(do
(println (format "Rolling back %d migration(s)..." amount))
(ragtime-repl/rollback config amount)
(println "Rollback complete."))
(f/when-failed [e]
(do
(println "Rollback failed:" (f/message e))
(f/fail "Rollback failed: %s" (f/message e)))))))

(defn rollback-all
"Rollback all migrations."
[]
(let [config (load-config)
migrations (:migrations config)]
(println (format "Rolling back all %d migration(s)..." (count migrations)))
(ragtime-repl/rollback config (count migrations))
(println "Rollback complete.")))
(f/attempt-all [config (load-config)
migrations (:migrations config)]
(do
(println (format "Rolling back all %d migration(s)..." (count migrations)))
(ragtime-repl/rollback config (count migrations))
(println "Rollback complete."))
(f/when-failed [e]
(do
(println "Rollback failed:" (f/message e))
(f/fail "Rollback failed: %s" (f/message e))))))

(defn -main
"Main entry point for migration CLI."
[& args]
(case (first args)
"migrate" (migrate)
"rollback" (if-let [amount (second args)]
(rollback (Integer/parseInt amount))
(rollback))
"rollback-all" (rollback-all)
(do
(println "Usage: clojure -M:migrate <command> [args]")
(println "Commands:")
(println " migrate - Run all pending migrations")
(println " rollback [n] - Rollback last n migrations (default: 1)")
(println " rollback-all - Rollback all migrations")
(System/exit 1))))
(f/attempt-all []
(case (first args)
"migrate" (do
(migrate)
(System/exit 0))
"rollback" (if-let [amount (second args)]
(do
(rollback (Integer/parseInt amount))
(System/exit 0))
(do
(rollback)
(System/exit 0)))
"rollback-all" (do
(rollback-all)
(System/exit 0))
(do
(println "Usage: clojure -M:migrate <command> [args]")
(println "Commands:")
(println " migrate - Run all pending migrations")
(println " rollback [n] - Rollback last n migrations (default: 1)")
(println " rollback-all - Rollback all migrations")
(System/exit 1)))
(f/when-failed [e]
(do
(println "Error in CLI:" (f/message e))
(System/exit 1)))))

(defn migrate-db
"Migrate a specific database connection (useful for testing).
Takes a db-spec map (e.g., {:dbtype \"sqlite\" :dbname \"test.db\"})."
[db-spec]
(let [config {:datastore (ragtime-jdbc/sql-database db-spec)
:migrations (ragtime-jdbc/load-resources "migrations")
:strategy ragtime.strategy/apply-new}]
(ragtime-repl/migrate config)))
(f/try-all [datastore (f/try* (ragtime-jdbc/sql-database db-spec))
migrations (f/try* (ragtime-jdbc/load-resources "migrations"))]
(let [config {:datastore datastore
:migrations migrations
:strategy ragtime.strategy/apply-new}]
(ragtime-repl/migrate config))
(f/when-failed [e]
(f/fail "Database migration failed: %s" (f/message e)))))

(defn reset-db
"Reset database by rolling back all migrations and re-applying them.
Takes a db-spec map (e.g., {:dbtype \"sqlite\" :dbname \"test.db\"})."
[db-spec]
(let [config {:datastore (ragtime-jdbc/sql-database db-spec)
:migrations (ragtime-jdbc/load-resources "migrations")
:strategy ragtime.strategy/apply-new}
migrations (:migrations config)]
;; Rollback all migrations
(when (pos? (count migrations))
(ragtime-repl/rollback config (count migrations)))
;; Re-apply all migrations
(ragtime-repl/migrate config)))
(f/try-all [datastore (f/try* (ragtime-jdbc/sql-database db-spec))
migrations (f/try* (ragtime-jdbc/load-resources "migrations"))]
(let [config {:datastore datastore
:migrations migrations
:strategy ragtime.strategy/apply-new}
migrations-count (count migrations)]
;; Rollback all migrations
(when (pos? migrations-count)
(ragtime-repl/rollback config migrations-count))
;; Re-apply all migrations
(ragtime-repl/migrate config))
(f/when-failed [e]
(f/fail "Database reset failed: %s" (f/message e)))))
3 changes: 3 additions & 0 deletions src/clojure_skills/output.clj
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,9 @@
(println (bling/bling [:bold "Database Statistics"]))
(println)
(println (bling/bling [:underline "Configuration:"]))
(println (str " Global config: " (:config-file-path config)))
(when (:project-config-path config)
(println (str " Project config: " (:project-config-path config))))
(println (str " Database: " (:database-path config)))
(println (str " Skills directory: " (:skills-directory config)))
(println (str " Prompts directory: " (:prompts-directory config)))
Expand Down
Loading
Loading