Class: Ask::CodingProviders::ZCode::ZCodeDB

Inherits:
Object
  • Object
show all
Defined in:
lib/ask/coding_providers/zcode/zcode_db.rb

Overview

Wraps the ZCode SQLite database, centralizing all session/project queries.

Usage:

db = ZCodeDB.new
projects = db.list_projects
sessions = db.find_sessions(directory: "/path")

All methods return nil or empty arrays on error (never raise). This makes it safe to use from any context without wrapping every call.

Instance Method Summary collapse

Constructor Details

#initialize(db_path = nil) ⇒ ZCodeDB

Returns a new instance of ZCodeDB.



18
19
20
# File 'lib/ask/coding_providers/zcode/zcode_db.rb', line 18

def initialize(db_path = nil)
  @db_path = db_path || File.expand_path("~/.zcode/cli/db/db.sqlite")
end

Instance Method Details

#available?Boolean

Returns:

  • (Boolean)


22
23
24
# File 'lib/ask/coding_providers/zcode/zcode_db.rb', line 22

def available?
  File.exist?(@db_path)
end

#find_recent_sessionObject

Find the single most recent session across all projects. Returns directory: or nil.



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/ask/coding_providers/zcode/zcode_db.rb', line 70

def find_recent_session
  return nil unless available?

  db = open_db
  row = db.get_first_row(<<~SQL)
    SELECT id, directory FROM session
    WHERE time_archived IS NULL AND task_type = 'interactive'
    ORDER BY time_updated DESC LIMIT 1
  SQL
  row ? { session_id: row["id"], directory: row["directory"] } : nil
rescue => e
  nil
ensure
  db&.close
end

#find_recent_tui_session(workspace_path) ⇒ Object

Find the most recent TUI session in a workspace path. Returns title:, directory: or nil.



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/ask/coding_providers/zcode/zcode_db.rb', line 88

def find_recent_tui_session(workspace_path)
  return nil unless available?

  db = open_db
  row = db.get_first_row(<<~SQL, [workspace_path, workspace_path])
    SELECT id, title, directory
    FROM session
    WHERE (directory = ? OR ? LIKE directory || '/%')
      AND time_archived IS NULL AND task_type = 'interactive'
    ORDER BY time_updated DESC
    LIMIT 1
  SQL
  row ? { session_id: row["id"], title: row["title"], directory: row["directory"] } : nil
rescue => e
  nil
ensure
  db&.close
end

#find_sessions(directory:, limit: 20) ⇒ Object

Find sessions in a given directory (exact or prefix match). Returns [title:, updated:] or [].



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/ask/coding_providers/zcode/zcode_db.rb', line 49

def find_sessions(directory:, limit: 20)
  return [] unless available?

  db = open_db
  rows = db.execute(<<~SQL, [directory, directory, limit])
    SELECT id, title, time_updated
    FROM session
    WHERE (directory = ? OR ? LIKE directory || '/%')
      AND time_archived IS NULL AND task_type = 'interactive'
    ORDER BY time_updated DESC
    LIMIT ?
  SQL
  rows.map { |r| { session_id: r["id"], title: r["title"], updated: r["time_updated"] } }
rescue => e
  []
ensure
  db&.close
end

#list_projectsObject

List all projects with session counts, ordered by most recently updated. Returns [directory:, session_count:] or [].



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/ask/coding_providers/zcode/zcode_db.rb', line 28

def list_projects
  return [] unless available?

  db = open_db
  rows = db.execute(<<~SQL)
    SELECT project_id, directory, count(*) as session_count
    FROM session
    WHERE task_type = 'interactive' AND time_archived IS NULL
    GROUP BY project_id
    ORDER BY MAX(time_updated) DESC
    LIMIT 10
  SQL
  rows.map { |r| { project_id: r["project_id"], directory: r["directory"], session_count: r["session_count"].to_i } }
rescue => e
  nil
ensure
  db&.close
end

#recent_sessionsObject

List recent sessions across all projects. Returns [title:, updated:, msg_count:] or [].



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/ask/coding_providers/zcode/zcode_db.rb', line 157

def recent_sessions
  return [] unless available?

  db = open_db
  rows = db.execute(<<~SQL)
    SELECT s.id AS session_id, s.title AS title,
           s.time_updated AS updated,
           (SELECT count(*) FROM message m WHERE m.session_id = s.id) AS msg_count
    FROM session s
    WHERE s.task_type = 'interactive' AND s.time_archived IS NULL
    ORDER BY s.time_updated DESC
    LIMIT 20
  SQL
  rows.map do |r|
    {
      session_id: r["session_id"],
      title: r["title"],
      updated: r["updated"],
      msg_count: r["msg_count"].to_i
    }
  end
rescue => e
  []
ensure
  db&.close
end

#session_directory(session_id) ⇒ Object

Look up a session's workspace directory by its ID. Returns the directory string, or nil.



109
110
111
112
113
114
115
116
117
118
119
# File 'lib/ask/coding_providers/zcode/zcode_db.rb', line 109

def session_directory(session_id)
  return nil unless available?

  db = open_db
  row = db.get_first_row("SELECT directory FROM session WHERE id = ?", [session_id])
  row&.dig("directory")
rescue => e
  nil
ensure
  db&.close
end

#session_history(session_id, limit: 100) ⇒ Object

Load the last N text parts from a session, newest first. Returns [role:, origin:] or [].



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/ask/coding_providers/zcode/zcode_db.rb', line 123

def session_history(session_id, limit: 100)
  return [] unless available?

  db = open_db
  rows = db.execute(<<~SQL, [session_id, limit])
    SELECT p.data, m.data as msg_data FROM part p
    JOIN message m ON p.message_id = m.id
    WHERE p.session_id = ? AND p.data LIKE '%"type":"text"%'
    ORDER BY p.time_created DESC
    LIMIT ?
  SQL

  rows.filter_map do |r|
    part = JSON.parse(r["data"]) rescue next
    content = part["text"] || ""
    next if content.empty?

    msg_data = JSON.parse(r["msg_data"]) rescue {}
    role = msg_data["role"] == "user" ? "You" : "Agent"
    origin = msg_data.dig("semantics", "origin") rescue nil

    # Skip system-generated messages masquerading as user
    next if role == "You" && origin != "real_user" && origin != nil

    { text: content, role: role, origin: origin }
  end
rescue => e
  []
ensure
  db&.close
end