gobby 0.2.6__py3-none-any.whl → 0.2.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gobby/__init__.py +1 -1
- gobby/adapters/__init__.py +2 -1
- gobby/adapters/codex_impl/__init__.py +28 -0
- gobby/adapters/codex_impl/adapter.py +722 -0
- gobby/adapters/codex_impl/client.py +679 -0
- gobby/adapters/codex_impl/protocol.py +20 -0
- gobby/adapters/codex_impl/types.py +68 -0
- gobby/agents/definitions.py +11 -1
- gobby/agents/isolation.py +395 -0
- gobby/agents/sandbox.py +261 -0
- gobby/agents/spawn.py +42 -287
- gobby/agents/spawn_executor.py +385 -0
- gobby/agents/spawners/__init__.py +24 -0
- gobby/agents/spawners/command_builder.py +189 -0
- gobby/agents/spawners/embedded.py +21 -2
- gobby/agents/spawners/headless.py +21 -2
- gobby/agents/spawners/prompt_manager.py +125 -0
- gobby/cli/install.py +4 -4
- gobby/cli/installers/claude.py +6 -0
- gobby/cli/installers/gemini.py +6 -0
- gobby/cli/installers/shared.py +103 -4
- gobby/cli/sessions.py +1 -1
- gobby/cli/utils.py +9 -2
- gobby/config/__init__.py +12 -97
- gobby/config/app.py +10 -94
- gobby/config/extensions.py +2 -2
- gobby/config/features.py +7 -130
- gobby/config/tasks.py +4 -28
- gobby/hooks/__init__.py +0 -13
- gobby/hooks/event_handlers.py +45 -2
- gobby/hooks/hook_manager.py +2 -2
- gobby/hooks/plugins.py +1 -1
- gobby/hooks/webhooks.py +1 -1
- gobby/llm/resolver.py +3 -2
- gobby/mcp_proxy/importer.py +62 -4
- gobby/mcp_proxy/instructions.py +2 -0
- gobby/mcp_proxy/registries.py +1 -4
- gobby/mcp_proxy/services/recommendation.py +43 -11
- gobby/mcp_proxy/tools/agents.py +31 -731
- gobby/mcp_proxy/tools/clones.py +0 -385
- gobby/mcp_proxy/tools/memory.py +2 -2
- gobby/mcp_proxy/tools/sessions/__init__.py +14 -0
- gobby/mcp_proxy/tools/sessions/_commits.py +232 -0
- gobby/mcp_proxy/tools/sessions/_crud.py +253 -0
- gobby/mcp_proxy/tools/sessions/_factory.py +63 -0
- gobby/mcp_proxy/tools/sessions/_handoff.py +499 -0
- gobby/mcp_proxy/tools/sessions/_messages.py +138 -0
- gobby/mcp_proxy/tools/skills/__init__.py +14 -29
- gobby/mcp_proxy/tools/spawn_agent.py +417 -0
- gobby/mcp_proxy/tools/tasks/_lifecycle.py +52 -18
- gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +1 -1
- gobby/mcp_proxy/tools/worktrees.py +0 -343
- gobby/memory/ingestion/__init__.py +5 -0
- gobby/memory/ingestion/multimodal.py +221 -0
- gobby/memory/manager.py +62 -283
- gobby/memory/search/__init__.py +10 -0
- gobby/memory/search/coordinator.py +248 -0
- gobby/memory/services/__init__.py +5 -0
- gobby/memory/services/crossref.py +142 -0
- gobby/prompts/loader.py +5 -2
- gobby/servers/http.py +1 -4
- gobby/servers/routes/admin.py +14 -0
- gobby/servers/routes/mcp/endpoints/__init__.py +61 -0
- gobby/servers/routes/mcp/endpoints/discovery.py +405 -0
- gobby/servers/routes/mcp/endpoints/execution.py +568 -0
- gobby/servers/routes/mcp/endpoints/registry.py +378 -0
- gobby/servers/routes/mcp/endpoints/server.py +304 -0
- gobby/servers/routes/mcp/hooks.py +1 -1
- gobby/servers/routes/mcp/tools.py +48 -1506
- gobby/sessions/lifecycle.py +1 -1
- gobby/sessions/processor.py +10 -0
- gobby/sessions/transcripts/base.py +1 -0
- gobby/sessions/transcripts/claude.py +15 -5
- gobby/skills/parser.py +30 -2
- gobby/storage/migrations.py +159 -372
- gobby/storage/sessions.py +43 -7
- gobby/storage/skills.py +37 -4
- gobby/storage/tasks/_lifecycle.py +18 -3
- gobby/sync/memories.py +1 -1
- gobby/tasks/external_validator.py +1 -1
- gobby/tasks/validation.py +22 -20
- gobby/tools/summarizer.py +91 -10
- gobby/utils/project_context.py +2 -3
- gobby/utils/status.py +13 -0
- gobby/workflows/actions.py +221 -1217
- gobby/workflows/artifact_actions.py +31 -0
- gobby/workflows/autonomous_actions.py +11 -0
- gobby/workflows/context_actions.py +50 -1
- gobby/workflows/enforcement/__init__.py +47 -0
- gobby/workflows/enforcement/blocking.py +269 -0
- gobby/workflows/enforcement/commit_policy.py +283 -0
- gobby/workflows/enforcement/handlers.py +269 -0
- gobby/workflows/enforcement/task_policy.py +542 -0
- gobby/workflows/git_utils.py +106 -0
- gobby/workflows/llm_actions.py +30 -0
- gobby/workflows/mcp_actions.py +20 -1
- gobby/workflows/memory_actions.py +80 -0
- gobby/workflows/safe_evaluator.py +183 -0
- gobby/workflows/session_actions.py +44 -0
- gobby/workflows/state_actions.py +60 -1
- gobby/workflows/stop_signal_actions.py +55 -0
- gobby/workflows/summary_actions.py +94 -1
- gobby/workflows/task_sync_actions.py +347 -0
- gobby/workflows/todo_actions.py +34 -1
- gobby/workflows/webhook_actions.py +185 -0
- {gobby-0.2.6.dist-info → gobby-0.2.7.dist-info}/METADATA +6 -1
- {gobby-0.2.6.dist-info → gobby-0.2.7.dist-info}/RECORD +111 -111
- {gobby-0.2.6.dist-info → gobby-0.2.7.dist-info}/WHEEL +1 -1
- gobby/adapters/codex.py +0 -1332
- gobby/install/claude/commands/gobby/bug.md +0 -51
- gobby/install/claude/commands/gobby/chore.md +0 -51
- gobby/install/claude/commands/gobby/epic.md +0 -52
- gobby/install/claude/commands/gobby/eval.md +0 -235
- gobby/install/claude/commands/gobby/feat.md +0 -49
- gobby/install/claude/commands/gobby/nit.md +0 -52
- gobby/install/claude/commands/gobby/ref.md +0 -52
- gobby/mcp_proxy/tools/session_messages.py +0 -1055
- gobby/prompts/defaults/expansion/system.md +0 -119
- gobby/prompts/defaults/expansion/user.md +0 -48
- gobby/prompts/defaults/external_validation/agent.md +0 -72
- gobby/prompts/defaults/external_validation/external.md +0 -63
- gobby/prompts/defaults/external_validation/spawn.md +0 -83
- gobby/prompts/defaults/external_validation/system.md +0 -6
- gobby/prompts/defaults/features/import_mcp.md +0 -22
- gobby/prompts/defaults/features/import_mcp_github.md +0 -17
- gobby/prompts/defaults/features/import_mcp_search.md +0 -16
- gobby/prompts/defaults/features/recommend_tools.md +0 -32
- gobby/prompts/defaults/features/recommend_tools_hybrid.md +0 -35
- gobby/prompts/defaults/features/recommend_tools_llm.md +0 -30
- gobby/prompts/defaults/features/server_description.md +0 -20
- gobby/prompts/defaults/features/server_description_system.md +0 -6
- gobby/prompts/defaults/features/task_description.md +0 -31
- gobby/prompts/defaults/features/task_description_system.md +0 -6
- gobby/prompts/defaults/features/tool_summary.md +0 -17
- gobby/prompts/defaults/features/tool_summary_system.md +0 -6
- gobby/prompts/defaults/handoff/compact.md +0 -63
- gobby/prompts/defaults/handoff/session_end.md +0 -57
- gobby/prompts/defaults/memory/extract.md +0 -61
- gobby/prompts/defaults/research/step.md +0 -58
- gobby/prompts/defaults/validation/criteria.md +0 -47
- gobby/prompts/defaults/validation/validate.md +0 -38
- gobby/storage/migrations_legacy.py +0 -1359
- gobby/workflows/task_enforcement_actions.py +0 -1343
- {gobby-0.2.6.dist-info → gobby-0.2.7.dist-info}/entry_points.txt +0 -0
- {gobby-0.2.6.dist-info → gobby-0.2.7.dist-info}/licenses/LICENSE.md +0 -0
- {gobby-0.2.6.dist-info → gobby-0.2.7.dist-info}/top_level.txt +0 -0
gobby/storage/migrations.py
CHANGED
|
@@ -3,17 +3,27 @@
|
|
|
3
3
|
This module handles schema migrations for the Gobby database.
|
|
4
4
|
|
|
5
5
|
For new databases (version == 0):
|
|
6
|
-
|
|
6
|
+
By default (use_flattened_baseline=True), BASELINE_SCHEMA_V2 is applied,
|
|
7
|
+
jumping directly to version 75 with a clean schema definition.
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
To use the legacy path (use_flattened_baseline=False), the old BASELINE_SCHEMA
|
|
10
|
+
is applied at version 60, followed by incremental migrations.
|
|
11
|
+
|
|
12
|
+
For existing databases (0 < version < 75):
|
|
13
|
+
Upgrade is not supported without legacy migrations (removed).
|
|
14
|
+
|
|
15
|
+
For existing databases (version >= 75):
|
|
16
|
+
Any migrations in MIGRATIONS (v76+) are applied incrementally.
|
|
17
|
+
|
|
18
|
+
Troubleshooting:
|
|
19
|
+
If you experience issues with new database creation, ensure you are starting
|
|
20
|
+
fresh or have a database version >= 60.
|
|
10
21
|
|
|
11
|
-
For all databases:
|
|
12
|
-
Any migrations in MIGRATIONS (v61+) are applied after the baseline/legacy path.
|
|
13
22
|
|
|
14
23
|
To add a new migration:
|
|
15
|
-
1. Add it to the MIGRATIONS list below with version =
|
|
24
|
+
1. Add it to the MIGRATIONS list below with version = 76, 77, etc.
|
|
16
25
|
2. Use SQL strings for schema changes, callables for data migrations.
|
|
26
|
+
3. Also add the migration to BASELINE_SCHEMA_V2 for future fresh installs.
|
|
17
27
|
"""
|
|
18
28
|
|
|
19
29
|
import logging
|
|
@@ -23,22 +33,29 @@ from gobby.storage.database import LocalDatabase
|
|
|
23
33
|
|
|
24
34
|
logger = logging.getLogger(__name__)
|
|
25
35
|
|
|
36
|
+
|
|
37
|
+
class MigrationUnsupportedError(Exception):
|
|
38
|
+
"""Raised when database version is too old to migrate."""
|
|
39
|
+
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
26
43
|
# Migration can be SQL string or a callable that takes LocalDatabase
|
|
27
44
|
MigrationAction = str | Callable[[LocalDatabase], None]
|
|
28
45
|
|
|
29
46
|
# Baseline version - the schema state after all legacy migrations
|
|
30
|
-
|
|
47
|
+
# Baseline version - the schema state at v75 (flattened)
|
|
48
|
+
BASELINE_VERSION = 75
|
|
31
49
|
|
|
32
|
-
# Baseline schema -
|
|
33
|
-
# This
|
|
50
|
+
# Baseline schema - flattened from v75 production state
|
|
51
|
+
# This is applied for new databases directly
|
|
52
|
+
# Generated by: sqlite3 ~/.gobby/gobby-hub.db .schema
|
|
34
53
|
BASELINE_SCHEMA = """
|
|
35
|
-
-- Schema version tracking
|
|
36
54
|
CREATE TABLE schema_version (
|
|
37
55
|
version INTEGER PRIMARY KEY,
|
|
38
56
|
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
39
57
|
);
|
|
40
58
|
|
|
41
|
-
-- Projects
|
|
42
59
|
CREATE TABLE projects (
|
|
43
60
|
id TEXT PRIMARY KEY,
|
|
44
61
|
name TEXT NOT NULL UNIQUE,
|
|
@@ -57,7 +74,6 @@ VALUES ('00000000-0000-0000-0000-000000000000', '_orphaned', NULL, datetime('now
|
|
|
57
74
|
INSERT INTO projects (id, name, repo_path, created_at, updated_at)
|
|
58
75
|
VALUES ('00000000-0000-0000-0000-000000000001', '_migrated', NULL, datetime('now'), datetime('now'));
|
|
59
76
|
|
|
60
|
-
-- MCP Servers
|
|
61
77
|
CREATE TABLE mcp_servers (
|
|
62
78
|
id TEXT PRIMARY KEY,
|
|
63
79
|
name TEXT NOT NULL,
|
|
@@ -78,7 +94,6 @@ CREATE INDEX idx_mcp_servers_project_id ON mcp_servers(project_id);
|
|
|
78
94
|
CREATE INDEX idx_mcp_servers_enabled ON mcp_servers(enabled);
|
|
79
95
|
CREATE UNIQUE INDEX idx_mcp_servers_name_project ON mcp_servers(name, project_id);
|
|
80
96
|
|
|
81
|
-
-- Tools
|
|
82
97
|
CREATE TABLE tools (
|
|
83
98
|
id TEXT PRIMARY KEY,
|
|
84
99
|
mcp_server_id TEXT NOT NULL REFERENCES mcp_servers(id) ON DELETE CASCADE,
|
|
@@ -92,7 +107,6 @@ CREATE TABLE tools (
|
|
|
92
107
|
CREATE INDEX idx_tools_server_id ON tools(mcp_server_id);
|
|
93
108
|
CREATE INDEX idx_tools_name ON tools(name);
|
|
94
109
|
|
|
95
|
-
-- Tool embeddings for semantic search
|
|
96
110
|
CREATE TABLE tool_embeddings (
|
|
97
111
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
98
112
|
tool_id TEXT NOT NULL REFERENCES tools(id) ON DELETE CASCADE,
|
|
@@ -111,7 +125,6 @@ CREATE INDEX idx_tool_embeddings_server ON tool_embeddings(server_name);
|
|
|
111
125
|
CREATE INDEX idx_tool_embeddings_project ON tool_embeddings(project_id);
|
|
112
126
|
CREATE INDEX idx_tool_embeddings_hash ON tool_embeddings(text_hash);
|
|
113
127
|
|
|
114
|
-
-- Tool schema hashes for incremental re-indexing
|
|
115
128
|
CREATE TABLE tool_schema_hashes (
|
|
116
129
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
117
130
|
server_name TEXT NOT NULL,
|
|
@@ -127,7 +140,6 @@ CREATE INDEX idx_schema_hashes_server ON tool_schema_hashes(server_name);
|
|
|
127
140
|
CREATE INDEX idx_schema_hashes_project ON tool_schema_hashes(project_id);
|
|
128
141
|
CREATE INDEX idx_schema_hashes_verified ON tool_schema_hashes(last_verified_at);
|
|
129
142
|
|
|
130
|
-
-- Tool metrics
|
|
131
143
|
CREATE TABLE tool_metrics (
|
|
132
144
|
id TEXT PRIMARY KEY,
|
|
133
145
|
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
@@ -149,7 +161,6 @@ CREATE INDEX idx_tool_metrics_tool ON tool_metrics(tool_name);
|
|
|
149
161
|
CREATE INDEX idx_tool_metrics_call_count ON tool_metrics(call_count DESC);
|
|
150
162
|
CREATE INDEX idx_tool_metrics_last_called ON tool_metrics(last_called_at);
|
|
151
163
|
|
|
152
|
-
-- Tool metrics daily aggregates
|
|
153
164
|
CREATE TABLE tool_metrics_daily (
|
|
154
165
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
155
166
|
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
@@ -168,7 +179,6 @@ CREATE INDEX idx_tool_metrics_daily_project ON tool_metrics_daily(project_id);
|
|
|
168
179
|
CREATE INDEX idx_tool_metrics_daily_date ON tool_metrics_daily(date);
|
|
169
180
|
CREATE INDEX idx_tool_metrics_daily_server ON tool_metrics_daily(server_name);
|
|
170
181
|
|
|
171
|
-
-- Agent runs
|
|
172
182
|
CREATE TABLE agent_runs (
|
|
173
183
|
id TEXT PRIMARY KEY,
|
|
174
184
|
parent_session_id TEXT NOT NULL REFERENCES sessions(id),
|
|
@@ -192,7 +202,6 @@ CREATE INDEX idx_agent_runs_child_session ON agent_runs(child_session_id);
|
|
|
192
202
|
CREATE INDEX idx_agent_runs_status ON agent_runs(status);
|
|
193
203
|
CREATE INDEX idx_agent_runs_provider ON agent_runs(provider);
|
|
194
204
|
|
|
195
|
-
-- Sessions
|
|
196
205
|
CREATE TABLE sessions (
|
|
197
206
|
id TEXT PRIMARY KEY,
|
|
198
207
|
external_id TEXT NOT NULL,
|
|
@@ -221,6 +230,8 @@ CREATE TABLE sessions (
|
|
|
221
230
|
usage_total_cost_usd REAL DEFAULT 0.0,
|
|
222
231
|
terminal_context TEXT,
|
|
223
232
|
seq_num INTEGER,
|
|
233
|
+
model TEXT,
|
|
234
|
+
had_edits BOOLEAN DEFAULT 0,
|
|
224
235
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
225
236
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
226
237
|
);
|
|
@@ -235,10 +246,9 @@ CREATE INDEX idx_sessions_agent_depth ON sessions(agent_depth);
|
|
|
235
246
|
CREATE INDEX idx_sessions_spawned_by ON sessions(spawned_by_agent_id);
|
|
236
247
|
CREATE INDEX idx_sessions_workflow ON sessions(workflow_name);
|
|
237
248
|
CREATE INDEX idx_sessions_agent_run ON sessions(agent_run_id);
|
|
238
|
-
CREATE UNIQUE INDEX idx_sessions_seq_num ON sessions(seq_num);
|
|
249
|
+
CREATE UNIQUE INDEX idx_sessions_seq_num ON sessions(project_id, seq_num);
|
|
239
250
|
CREATE UNIQUE INDEX idx_sessions_unique ON sessions(external_id, machine_id, source, project_id);
|
|
240
251
|
|
|
241
|
-
-- Session messages
|
|
242
252
|
CREATE TABLE session_messages (
|
|
243
253
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
244
254
|
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
@@ -259,7 +269,6 @@ CREATE INDEX idx_session_messages_role ON session_messages(role);
|
|
|
259
269
|
CREATE INDEX idx_session_messages_timestamp ON session_messages(timestamp);
|
|
260
270
|
CREATE INDEX idx_session_messages_tool ON session_messages(tool_name);
|
|
261
271
|
|
|
262
|
-
-- Session message processing state
|
|
263
272
|
CREATE TABLE session_message_state (
|
|
264
273
|
session_id TEXT PRIMARY KEY REFERENCES sessions(id) ON DELETE CASCADE,
|
|
265
274
|
last_byte_offset INTEGER DEFAULT 0,
|
|
@@ -269,7 +278,6 @@ CREATE TABLE session_message_state (
|
|
|
269
278
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
270
279
|
);
|
|
271
280
|
|
|
272
|
-
-- Session artifacts with FTS
|
|
273
281
|
CREATE TABLE session_artifacts (
|
|
274
282
|
id TEXT PRIMARY KEY,
|
|
275
283
|
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
@@ -286,7 +294,6 @@ CREATE INDEX idx_session_artifacts_type ON session_artifacts(artifact_type);
|
|
|
286
294
|
CREATE INDEX idx_session_artifacts_created ON session_artifacts(created_at);
|
|
287
295
|
CREATE VIRTUAL TABLE session_artifacts_fts USING fts5(id UNINDEXED, content);
|
|
288
296
|
|
|
289
|
-
-- Session stop signals for autonomous stop
|
|
290
297
|
CREATE TABLE session_stop_signals (
|
|
291
298
|
session_id TEXT PRIMARY KEY REFERENCES sessions(id) ON DELETE CASCADE,
|
|
292
299
|
source TEXT NOT NULL,
|
|
@@ -297,7 +304,6 @@ CREATE TABLE session_stop_signals (
|
|
|
297
304
|
CREATE INDEX idx_stop_signals_pending ON session_stop_signals(acknowledged_at)
|
|
298
305
|
WHERE acknowledged_at IS NULL;
|
|
299
306
|
|
|
300
|
-
-- Loop progress for autonomous progress tracking
|
|
301
307
|
CREATE TABLE loop_progress (
|
|
302
308
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
303
309
|
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
@@ -311,7 +317,6 @@ CREATE INDEX idx_loop_progress_session ON loop_progress(session_id, recorded_at
|
|
|
311
317
|
CREATE INDEX idx_loop_progress_high_value ON loop_progress(session_id, is_high_value, recorded_at DESC)
|
|
312
318
|
WHERE is_high_value = 1;
|
|
313
319
|
|
|
314
|
-
-- Tasks
|
|
315
320
|
CREATE TABLE tasks (
|
|
316
321
|
id TEXT PRIMARY KEY,
|
|
317
322
|
project_id TEXT NOT NULL REFERENCES projects(id),
|
|
@@ -374,7 +379,6 @@ CREATE INDEX idx_tasks_closed_session ON tasks(closed_in_session_id);
|
|
|
374
379
|
CREATE UNIQUE INDEX idx_tasks_seq_num ON tasks(project_id, seq_num);
|
|
375
380
|
CREATE INDEX idx_tasks_path_cache ON tasks(path_cache);
|
|
376
381
|
|
|
377
|
-
-- Task dependencies
|
|
378
382
|
CREATE TABLE task_dependencies (
|
|
379
383
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
380
384
|
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
@@ -386,7 +390,6 @@ CREATE TABLE task_dependencies (
|
|
|
386
390
|
CREATE INDEX idx_deps_task ON task_dependencies(task_id);
|
|
387
391
|
CREATE INDEX idx_deps_depends_on ON task_dependencies(depends_on);
|
|
388
392
|
|
|
389
|
-
-- Session-task linkages
|
|
390
393
|
CREATE TABLE session_tasks (
|
|
391
394
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
392
395
|
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
@@ -398,7 +401,6 @@ CREATE TABLE session_tasks (
|
|
|
398
401
|
CREATE INDEX idx_session_tasks_session ON session_tasks(session_id);
|
|
399
402
|
CREATE INDEX idx_session_tasks_task ON session_tasks(task_id);
|
|
400
403
|
|
|
401
|
-
-- Task validation history
|
|
402
404
|
CREATE TABLE task_validation_history (
|
|
403
405
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
404
406
|
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
@@ -413,7 +415,6 @@ CREATE TABLE task_validation_history (
|
|
|
413
415
|
);
|
|
414
416
|
CREATE INDEX idx_validation_history_task ON task_validation_history(task_id);
|
|
415
417
|
|
|
416
|
-
-- Task selection history for stuck detection
|
|
417
418
|
CREATE TABLE task_selection_history (
|
|
418
419
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
419
420
|
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
@@ -424,7 +425,6 @@ CREATE TABLE task_selection_history (
|
|
|
424
425
|
CREATE INDEX idx_task_selection_session ON task_selection_history(session_id, selected_at DESC);
|
|
425
426
|
CREATE INDEX idx_task_selection_task ON task_selection_history(session_id, task_id, selected_at DESC);
|
|
426
427
|
|
|
427
|
-
-- Workflow states
|
|
428
428
|
CREATE TABLE workflow_states (
|
|
429
429
|
session_id TEXT PRIMARY KEY,
|
|
430
430
|
workflow_name TEXT NOT NULL,
|
|
@@ -445,7 +445,6 @@ CREATE TABLE workflow_states (
|
|
|
445
445
|
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
446
446
|
);
|
|
447
447
|
|
|
448
|
-
-- Workflow audit log
|
|
449
448
|
CREATE TABLE workflow_audit_log (
|
|
450
449
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
451
450
|
session_id TEXT NOT NULL,
|
|
@@ -465,7 +464,6 @@ CREATE INDEX idx_audit_timestamp ON workflow_audit_log(timestamp);
|
|
|
465
464
|
CREATE INDEX idx_audit_event_type ON workflow_audit_log(event_type);
|
|
466
465
|
CREATE INDEX idx_audit_result ON workflow_audit_log(result);
|
|
467
466
|
|
|
468
|
-
-- Memories
|
|
469
467
|
CREATE TABLE memories (
|
|
470
468
|
id TEXT PRIMARY KEY,
|
|
471
469
|
project_id TEXT REFERENCES projects(id),
|
|
@@ -485,7 +483,6 @@ CREATE INDEX idx_memories_project ON memories(project_id);
|
|
|
485
483
|
CREATE INDEX idx_memories_type ON memories(memory_type);
|
|
486
484
|
CREATE INDEX idx_memories_importance ON memories(importance DESC);
|
|
487
485
|
|
|
488
|
-
-- Session-memory linkages
|
|
489
486
|
CREATE TABLE session_memories (
|
|
490
487
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
491
488
|
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
@@ -497,7 +494,6 @@ CREATE TABLE session_memories (
|
|
|
497
494
|
CREATE INDEX idx_session_memories_session ON session_memories(session_id);
|
|
498
495
|
CREATE INDEX idx_session_memories_memory ON session_memories(memory_id);
|
|
499
496
|
|
|
500
|
-
-- Memory cross-references
|
|
501
497
|
CREATE TABLE memory_crossrefs (
|
|
502
498
|
source_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
|
|
503
499
|
target_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
|
|
@@ -509,7 +505,6 @@ CREATE INDEX idx_crossrefs_source ON memory_crossrefs(source_id);
|
|
|
509
505
|
CREATE INDEX idx_crossrefs_target ON memory_crossrefs(target_id);
|
|
510
506
|
CREATE INDEX idx_crossrefs_similarity ON memory_crossrefs(similarity DESC);
|
|
511
507
|
|
|
512
|
-
-- Worktrees
|
|
513
508
|
CREATE TABLE worktrees (
|
|
514
509
|
id TEXT PRIMARY KEY,
|
|
515
510
|
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
@@ -531,7 +526,6 @@ CREATE INDEX idx_worktrees_session ON worktrees(agent_session_id);
|
|
|
531
526
|
CREATE UNIQUE INDEX idx_worktrees_branch ON worktrees(project_id, branch_name);
|
|
532
527
|
CREATE UNIQUE INDEX idx_worktrees_path ON worktrees(worktree_path);
|
|
533
528
|
|
|
534
|
-
-- Merge resolutions
|
|
535
529
|
CREATE TABLE merge_resolutions (
|
|
536
530
|
id TEXT PRIMARY KEY,
|
|
537
531
|
worktree_id TEXT NOT NULL REFERENCES worktrees(id) ON DELETE CASCADE,
|
|
@@ -547,7 +541,6 @@ CREATE INDEX idx_merge_resolutions_status ON merge_resolutions(status);
|
|
|
547
541
|
CREATE INDEX idx_merge_resolutions_source_branch ON merge_resolutions(source_branch);
|
|
548
542
|
CREATE INDEX idx_merge_resolutions_target_branch ON merge_resolutions(target_branch);
|
|
549
543
|
|
|
550
|
-
-- Merge conflicts
|
|
551
544
|
CREATE TABLE merge_conflicts (
|
|
552
545
|
id TEXT PRIMARY KEY,
|
|
553
546
|
resolution_id TEXT NOT NULL REFERENCES merge_resolutions(id) ON DELETE CASCADE,
|
|
@@ -562,353 +555,148 @@ CREATE TABLE merge_conflicts (
|
|
|
562
555
|
CREATE INDEX idx_merge_conflicts_resolution ON merge_conflicts(resolution_id);
|
|
563
556
|
CREATE INDEX idx_merge_conflicts_file_path ON merge_conflicts(file_path);
|
|
564
557
|
CREATE INDEX idx_merge_conflicts_status ON merge_conflicts(status);
|
|
565
|
-
"""
|
|
566
|
-
|
|
567
|
-
# Future migrations (v61+)
|
|
568
|
-
# Add new migrations here. Do not modify the baseline schema above.
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
def _migrate_test_strategy_to_category(db: LocalDatabase) -> None:
|
|
572
|
-
"""Rename test_strategy column to category if it exists.
|
|
573
|
-
|
|
574
|
-
This is a no-op for fresh databases that already have category in the baseline schema.
|
|
575
|
-
Only runs the rename for databases upgraded from versions before the rename.
|
|
576
|
-
"""
|
|
577
|
-
# Check if test_strategy column exists
|
|
578
|
-
row = db.fetchone("SELECT sql FROM sqlite_master WHERE type='table' AND name='tasks'")
|
|
579
|
-
if row and "test_strategy" in row["sql"].lower():
|
|
580
|
-
db.execute("ALTER TABLE tasks RENAME COLUMN test_strategy TO category")
|
|
581
|
-
logger.info("Renamed test_strategy column to category")
|
|
582
|
-
else:
|
|
583
|
-
logger.debug("test_strategy column not found (fresh database), skipping rename")
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
def _migrate_add_agent_name(db: LocalDatabase) -> None:
|
|
587
|
-
"""Add agent_name column to tasks table for agent configuration."""
|
|
588
|
-
# Check if agent_name column already exists (fresh database)
|
|
589
|
-
row = db.fetchone("SELECT sql FROM sqlite_master WHERE type='table' AND name='tasks'")
|
|
590
|
-
if row and "agent_name" not in row["sql"].lower():
|
|
591
|
-
db.execute("ALTER TABLE tasks ADD COLUMN agent_name TEXT")
|
|
592
|
-
logger.info("Added agent_name column to tasks table")
|
|
593
|
-
else:
|
|
594
|
-
logger.debug("agent_name column already exists, skipping")
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
def _migrate_add_reference_doc(db: LocalDatabase) -> None:
|
|
598
|
-
"""Add reference_doc column to tasks table for spec traceability."""
|
|
599
|
-
# Check if reference_doc column already exists (fresh database)
|
|
600
|
-
row = db.fetchone("SELECT sql FROM sqlite_master WHERE type='table' AND name='tasks'")
|
|
601
|
-
if row and "reference_doc" not in row["sql"].lower():
|
|
602
|
-
db.execute("ALTER TABLE tasks ADD COLUMN reference_doc TEXT")
|
|
603
|
-
logger.info("Added reference_doc column to tasks table")
|
|
604
|
-
else:
|
|
605
|
-
logger.debug("reference_doc column already exists, skipping")
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
def _migrate_add_boolean_columns(db: LocalDatabase) -> None:
|
|
609
|
-
"""Add is_enriched and is_expanded columns to tasks table.
|
|
610
|
-
|
|
611
|
-
Note: is_tdd_applied was previously added here but is now deprecated and
|
|
612
|
-
removed in migration 74. This migration only adds is_enriched and is_expanded.
|
|
613
|
-
"""
|
|
614
|
-
row = db.fetchone("SELECT sql FROM sqlite_master WHERE type='table' AND name='tasks'")
|
|
615
|
-
if not row:
|
|
616
|
-
return
|
|
617
|
-
|
|
618
|
-
sql_lower = row["sql"].lower()
|
|
619
|
-
|
|
620
|
-
# Add each column if it doesn't exist
|
|
621
|
-
if "is_enriched" not in sql_lower:
|
|
622
|
-
db.execute("ALTER TABLE tasks ADD COLUMN is_enriched INTEGER DEFAULT 0")
|
|
623
|
-
logger.info("Added is_enriched column to tasks table")
|
|
624
|
-
|
|
625
|
-
if "is_expanded" not in sql_lower:
|
|
626
|
-
db.execute("ALTER TABLE tasks ADD COLUMN is_expanded INTEGER DEFAULT 0")
|
|
627
|
-
logger.info("Added is_expanded column to tasks table")
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
def _migrate_add_review_columns(db: LocalDatabase) -> None:
|
|
631
|
-
"""Add requires_user_review and accepted_by_user columns for review status support."""
|
|
632
|
-
row = db.fetchone("SELECT sql FROM sqlite_master WHERE type='table' AND name='tasks'")
|
|
633
|
-
if not row:
|
|
634
|
-
return
|
|
635
|
-
|
|
636
|
-
sql_lower = row["sql"].lower()
|
|
637
|
-
|
|
638
|
-
if "requires_user_review" not in sql_lower:
|
|
639
|
-
db.execute("ALTER TABLE tasks ADD COLUMN requires_user_review INTEGER DEFAULT 0")
|
|
640
|
-
logger.info("Added requires_user_review column to tasks table")
|
|
641
|
-
|
|
642
|
-
if "accepted_by_user" not in sql_lower:
|
|
643
|
-
db.execute("ALTER TABLE tasks ADD COLUMN accepted_by_user INTEGER DEFAULT 0")
|
|
644
|
-
logger.info("Added accepted_by_user column to tasks table")
|
|
645
558
|
|
|
559
|
+
CREATE TABLE inter_session_messages (
|
|
560
|
+
id TEXT PRIMARY KEY,
|
|
561
|
+
from_session TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
562
|
+
to_session TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
563
|
+
content TEXT NOT NULL,
|
|
564
|
+
priority TEXT NOT NULL DEFAULT 'normal',
|
|
565
|
+
sent_at TEXT NOT NULL,
|
|
566
|
+
read_at TEXT
|
|
567
|
+
);
|
|
568
|
+
CREATE INDEX idx_inter_session_messages_from_session ON inter_session_messages(from_session);
|
|
569
|
+
CREATE INDEX idx_inter_session_messages_to_session ON inter_session_messages(to_session);
|
|
570
|
+
CREATE INDEX idx_inter_session_messages_unread ON inter_session_messages(to_session, read_at)
|
|
571
|
+
WHERE read_at IS NULL;
|
|
646
572
|
|
|
647
|
-
|
|
648
|
-
|
|
573
|
+
CREATE TABLE skills (
|
|
574
|
+
id TEXT PRIMARY KEY,
|
|
575
|
+
name TEXT NOT NULL,
|
|
576
|
+
description TEXT NOT NULL,
|
|
577
|
+
content TEXT NOT NULL,
|
|
578
|
+
version TEXT,
|
|
579
|
+
license TEXT,
|
|
580
|
+
compatibility TEXT,
|
|
581
|
+
allowed_tools TEXT,
|
|
582
|
+
metadata TEXT,
|
|
583
|
+
source_path TEXT,
|
|
584
|
+
source_type TEXT,
|
|
585
|
+
source_ref TEXT,
|
|
586
|
+
enabled INTEGER DEFAULT 1,
|
|
587
|
+
project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
|
|
588
|
+
created_at TEXT NOT NULL,
|
|
589
|
+
updated_at TEXT NOT NULL
|
|
590
|
+
);
|
|
591
|
+
CREATE INDEX idx_skills_name ON skills(name);
|
|
592
|
+
CREATE INDEX idx_skills_project_id ON skills(project_id);
|
|
593
|
+
CREATE INDEX idx_skills_enabled ON skills(enabled);
|
|
594
|
+
CREATE UNIQUE INDEX idx_skills_name_project ON skills(name, project_id);
|
|
595
|
+
CREATE UNIQUE INDEX idx_skills_name_global ON skills(name) WHERE project_id IS NULL;
|
|
649
596
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
597
|
+
CREATE TABLE clones (
|
|
598
|
+
id TEXT PRIMARY KEY,
|
|
599
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
600
|
+
branch_name TEXT NOT NULL,
|
|
601
|
+
clone_path TEXT NOT NULL,
|
|
602
|
+
base_branch TEXT DEFAULT 'main',
|
|
603
|
+
task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
|
604
|
+
agent_session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
|
|
605
|
+
status TEXT DEFAULT 'active',
|
|
606
|
+
remote_url TEXT,
|
|
607
|
+
last_sync_at TEXT,
|
|
608
|
+
cleanup_after TEXT,
|
|
609
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
610
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
611
|
+
);
|
|
612
|
+
CREATE INDEX idx_clones_project ON clones(project_id);
|
|
613
|
+
CREATE INDEX idx_clones_status ON clones(status);
|
|
614
|
+
CREATE INDEX idx_clones_task ON clones(task_id);
|
|
615
|
+
CREATE INDEX idx_clones_session ON clones(agent_session_id);
|
|
616
|
+
CREATE UNIQUE INDEX idx_clones_path ON clones(clone_path);
|
|
617
|
+
"""
|
|
656
618
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
db.execute("ALTER TABLE tasks DROP COLUMN is_enriched")
|
|
660
|
-
logger.info("Dropped is_enriched column from tasks table")
|
|
661
|
-
except Exception as e:
|
|
662
|
-
# SQLite < 3.35.0 doesn't support DROP COLUMN
|
|
663
|
-
# Column will remain but be unused - not a problem
|
|
664
|
-
logger.warning(f"Could not drop is_enriched column (SQLite < 3.35?): {e}")
|
|
619
|
+
# Future migrations (v61+)
|
|
620
|
+
# Add new migrations here. Do not modify the baseline schema above.
|
|
665
621
|
|
|
666
622
|
|
|
667
|
-
def
|
|
668
|
-
"""
|
|
623
|
+
def _migrate_session_seq_num_project_scoped(db: LocalDatabase) -> None:
|
|
624
|
+
"""Change sessions.seq_num index from global to project-scoped.
|
|
669
625
|
|
|
670
|
-
This
|
|
671
|
-
|
|
672
|
-
receive status updates back.
|
|
626
|
+
This allows different projects to have independent session numbering (#1, #2, etc.)
|
|
627
|
+
matching how tasks already work.
|
|
673
628
|
"""
|
|
674
|
-
# Check if
|
|
629
|
+
# Check if the old global index exists
|
|
675
630
|
row = db.fetchone(
|
|
676
|
-
"SELECT
|
|
631
|
+
"SELECT sql FROM sqlite_master WHERE type='index' AND name='idx_sessions_seq_num'"
|
|
677
632
|
)
|
|
678
|
-
if row:
|
|
679
|
-
logger.debug("
|
|
633
|
+
if not row:
|
|
634
|
+
logger.debug("idx_sessions_seq_num index does not exist, skipping")
|
|
680
635
|
return
|
|
681
636
|
|
|
682
|
-
#
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
id TEXT PRIMARY KEY,
|
|
686
|
-
from_session TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
687
|
-
to_session TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
688
|
-
content TEXT NOT NULL,
|
|
689
|
-
priority TEXT NOT NULL DEFAULT 'normal',
|
|
690
|
-
sent_at TEXT NOT NULL,
|
|
691
|
-
read_at TEXT
|
|
692
|
-
)
|
|
693
|
-
""")
|
|
694
|
-
|
|
695
|
-
# Create indexes for efficient querying
|
|
696
|
-
db.execute(
|
|
697
|
-
"CREATE INDEX idx_inter_session_messages_from_session ON inter_session_messages(from_session)"
|
|
698
|
-
)
|
|
699
|
-
db.execute(
|
|
700
|
-
"CREATE INDEX idx_inter_session_messages_to_session ON inter_session_messages(to_session)"
|
|
701
|
-
)
|
|
702
|
-
db.execute(
|
|
703
|
-
"CREATE INDEX idx_inter_session_messages_unread ON inter_session_messages(to_session, read_at) "
|
|
704
|
-
"WHERE read_at IS NULL"
|
|
705
|
-
)
|
|
706
|
-
|
|
707
|
-
logger.info("Created inter_session_messages table with indexes")
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
def _migrate_add_media_column(db: LocalDatabase) -> None:
|
|
711
|
-
"""Add media column to memories table for multimodal support."""
|
|
712
|
-
# Check if media column already exists (fresh database from baseline)
|
|
713
|
-
row = db.fetchone("SELECT sql FROM sqlite_master WHERE type='table' AND name='memories'")
|
|
714
|
-
if row and "media" not in row["sql"].lower():
|
|
715
|
-
db.execute("ALTER TABLE memories ADD COLUMN media TEXT")
|
|
716
|
-
logger.info("Added media column to memories table")
|
|
717
|
-
else:
|
|
718
|
-
logger.debug("media column already exists, skipping")
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
def _migrate_add_expansion_status(db: LocalDatabase) -> None:
|
|
722
|
-
"""Add expansion_status column to tasks table for skill-based expansion."""
|
|
723
|
-
row = db.fetchone("SELECT sql FROM sqlite_master WHERE type='table' AND name='tasks'")
|
|
724
|
-
if row and "expansion_status" not in row["sql"].lower():
|
|
725
|
-
db.execute("ALTER TABLE tasks ADD COLUMN expansion_status TEXT DEFAULT 'none'")
|
|
726
|
-
logger.info("Added expansion_status column to tasks table")
|
|
727
|
-
else:
|
|
728
|
-
logger.debug("expansion_status column already exists, skipping")
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
def _migrate_add_skills_table(db: LocalDatabase) -> None:
|
|
732
|
-
"""Add skills table for Agent Skills spec compliant skill storage.
|
|
733
|
-
|
|
734
|
-
Skills provide structured instructions for AI agents following the
|
|
735
|
-
Agent Skills specification (agentskills.io) with Gobby-specific extensions.
|
|
736
|
-
"""
|
|
737
|
-
# Check if table already exists
|
|
738
|
-
row = db.fetchone("SELECT name FROM sqlite_master WHERE type='table' AND name='skills'")
|
|
739
|
-
if row:
|
|
740
|
-
logger.debug("skills table already exists, skipping")
|
|
637
|
+
# Check if it's already project-scoped (contains 'project_id')
|
|
638
|
+
if "project_id" in (row["sql"] or "").lower():
|
|
639
|
+
logger.debug("idx_sessions_seq_num is already project-scoped, skipping")
|
|
741
640
|
return
|
|
742
641
|
|
|
743
|
-
#
|
|
744
|
-
db.
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
name TEXT NOT NULL,
|
|
748
|
-
description TEXT NOT NULL,
|
|
749
|
-
content TEXT NOT NULL,
|
|
750
|
-
version TEXT,
|
|
751
|
-
license TEXT,
|
|
752
|
-
compatibility TEXT,
|
|
753
|
-
allowed_tools TEXT,
|
|
754
|
-
metadata TEXT,
|
|
755
|
-
source_path TEXT,
|
|
756
|
-
source_type TEXT,
|
|
757
|
-
source_ref TEXT,
|
|
758
|
-
enabled INTEGER DEFAULT 1,
|
|
759
|
-
project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
|
|
760
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
761
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
762
|
-
)
|
|
763
|
-
""")
|
|
764
|
-
|
|
765
|
-
# Create indexes
|
|
766
|
-
db.execute("CREATE INDEX idx_skills_name ON skills(name)")
|
|
767
|
-
db.execute("CREATE INDEX idx_skills_project_id ON skills(project_id)")
|
|
768
|
-
db.execute("CREATE INDEX idx_skills_enabled ON skills(enabled)")
|
|
769
|
-
# Unique constraint: name must be unique within a project scope
|
|
770
|
-
db.execute("CREATE UNIQUE INDEX idx_skills_name_project ON skills(name, project_id)")
|
|
771
|
-
# Partial unique index for global skills (project_id IS NULL)
|
|
772
|
-
# This enforces uniqueness for global skill names since NULL != NULL in SQL
|
|
773
|
-
db.execute(
|
|
774
|
-
"CREATE UNIQUE INDEX idx_skills_name_global ON skills(name) WHERE project_id IS NULL"
|
|
775
|
-
)
|
|
642
|
+
# Drop the old global index and create new project-scoped index atomically
|
|
643
|
+
with db.transaction() as conn:
|
|
644
|
+
conn.execute("DROP INDEX IF EXISTS idx_sessions_seq_num")
|
|
645
|
+
conn.execute("CREATE UNIQUE INDEX idx_sessions_seq_num ON sessions(project_id, seq_num)")
|
|
776
646
|
|
|
777
|
-
logger.info("
|
|
647
|
+
logger.info("Changed sessions.seq_num index from global to project-scoped")
|
|
778
648
|
|
|
779
649
|
|
|
780
|
-
def
|
|
781
|
-
"""
|
|
650
|
+
def _migrate_backfill_session_seq_num_per_project(db: LocalDatabase) -> None:
|
|
651
|
+
"""Re-backfill session seq_num values to be per-project.
|
|
782
652
|
|
|
783
|
-
This
|
|
784
|
-
|
|
653
|
+
This migration re-numbers sessions so each project has independent numbering
|
|
654
|
+
starting from 1. Required after changing the index to be project-scoped.
|
|
785
655
|
"""
|
|
786
|
-
#
|
|
787
|
-
|
|
788
|
-
"
|
|
656
|
+
# Get all sessions grouped by project, ordered by created_at
|
|
657
|
+
sessions = db.fetchall(
|
|
658
|
+
"""
|
|
659
|
+
SELECT id, project_id FROM sessions
|
|
660
|
+
ORDER BY project_id, created_at ASC, id ASC
|
|
661
|
+
"""
|
|
789
662
|
)
|
|
790
|
-
if row:
|
|
791
|
-
logger.debug("idx_skills_name_global index already exists, skipping")
|
|
792
|
-
return
|
|
793
663
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
if not row:
|
|
797
|
-
logger.debug("skills table does not exist, skipping")
|
|
664
|
+
if not sessions:
|
|
665
|
+
logger.debug("No sessions to re-number")
|
|
798
666
|
return
|
|
799
667
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
668
|
+
# Wrap the entire re-numbering in a transaction for atomicity
|
|
669
|
+
with db.transaction() as conn:
|
|
670
|
+
# First, clear all seq_num values to avoid unique constraint violations
|
|
671
|
+
# when the existing seq_num order doesn't match created_at order
|
|
672
|
+
conn.execute("UPDATE sessions SET seq_num = NULL")
|
|
804
673
|
|
|
674
|
+
# Assign seq_num per project
|
|
675
|
+
current_project: str | None = None
|
|
676
|
+
seq_num = 0
|
|
677
|
+
updated = 0
|
|
805
678
|
|
|
806
|
-
|
|
807
|
-
|
|
679
|
+
for session in sessions:
|
|
680
|
+
if session["project_id"] != current_project:
|
|
681
|
+
current_project = session["project_id"]
|
|
682
|
+
seq_num = 1
|
|
683
|
+
else:
|
|
684
|
+
seq_num += 1
|
|
808
685
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
row = db.fetchone("SELECT name FROM sqlite_master WHERE type='table' AND name='clones'")
|
|
815
|
-
if row:
|
|
816
|
-
logger.debug("clones table already exists, skipping")
|
|
817
|
-
return
|
|
686
|
+
conn.execute(
|
|
687
|
+
"UPDATE sessions SET seq_num = ? WHERE id = ?",
|
|
688
|
+
(seq_num, session["id"]),
|
|
689
|
+
)
|
|
690
|
+
updated += 1
|
|
818
691
|
|
|
819
|
-
|
|
820
|
-
db.execute("""
|
|
821
|
-
CREATE TABLE clones (
|
|
822
|
-
id TEXT PRIMARY KEY,
|
|
823
|
-
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
824
|
-
branch_name TEXT NOT NULL,
|
|
825
|
-
clone_path TEXT NOT NULL,
|
|
826
|
-
base_branch TEXT DEFAULT 'main',
|
|
827
|
-
task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
|
828
|
-
agent_session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
|
|
829
|
-
status TEXT DEFAULT 'active',
|
|
830
|
-
remote_url TEXT,
|
|
831
|
-
last_sync_at TEXT,
|
|
832
|
-
cleanup_after TEXT,
|
|
833
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
834
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
835
|
-
)
|
|
836
|
-
""")
|
|
837
|
-
|
|
838
|
-
# Create indexes
|
|
839
|
-
db.execute("CREATE INDEX idx_clones_project ON clones(project_id)")
|
|
840
|
-
db.execute("CREATE INDEX idx_clones_status ON clones(status)")
|
|
841
|
-
db.execute("CREATE INDEX idx_clones_task ON clones(task_id)")
|
|
842
|
-
db.execute("CREATE INDEX idx_clones_session ON clones(agent_session_id)")
|
|
843
|
-
db.execute("CREATE UNIQUE INDEX idx_clones_path ON clones(clone_path)")
|
|
844
|
-
|
|
845
|
-
logger.debug("Created clones table with indexes")
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
def _migrate_add_model_column(db: LocalDatabase) -> None:
|
|
849
|
-
"""Add model column to sessions table for cost tracking by model.
|
|
850
|
-
|
|
851
|
-
This enables the TokenTracker to aggregate usage by model and apply
|
|
852
|
-
model-specific pricing for budget tracking.
|
|
853
|
-
"""
|
|
854
|
-
row = db.fetchone("SELECT sql FROM sqlite_master WHERE type='table' AND name='sessions'")
|
|
855
|
-
if row and "model" not in row["sql"].lower():
|
|
856
|
-
db.execute("ALTER TABLE sessions ADD COLUMN model TEXT")
|
|
857
|
-
logger.info("Added model column to sessions table")
|
|
858
|
-
else:
|
|
859
|
-
logger.debug("model column already exists, skipping")
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
def _migrate_drop_is_tdd_applied(db: LocalDatabase) -> None:
|
|
863
|
-
"""Drop deprecated is_tdd_applied column from tasks table.
|
|
864
|
-
|
|
865
|
-
The is_tdd_applied flag is no longer used after removing the TDD sandwich pattern.
|
|
866
|
-
TDD instructions are now embedded directly in task descriptions for code/config categories.
|
|
867
|
-
SQLite 3.35.0+ supports ALTER TABLE DROP COLUMN.
|
|
868
|
-
"""
|
|
869
|
-
row = db.fetchone("SELECT sql FROM sqlite_master WHERE type='table' AND name='tasks'")
|
|
870
|
-
if not row:
|
|
871
|
-
return
|
|
872
|
-
|
|
873
|
-
if "is_tdd_applied" in row["sql"].lower():
|
|
874
|
-
try:
|
|
875
|
-
db.execute("ALTER TABLE tasks DROP COLUMN is_tdd_applied")
|
|
876
|
-
logger.info("Dropped is_tdd_applied column from tasks table")
|
|
877
|
-
except Exception as e:
|
|
878
|
-
# SQLite < 3.35.0 doesn't support DROP COLUMN
|
|
879
|
-
# Column will remain but be unused - not a problem
|
|
880
|
-
logger.warning(f"Could not drop is_tdd_applied column (SQLite < 3.35?): {e}")
|
|
692
|
+
logger.info(f"Re-numbered {updated} sessions with per-project seq_num")
|
|
881
693
|
|
|
882
694
|
|
|
883
695
|
MIGRATIONS: list[tuple[int, str, MigrationAction]] = [
|
|
884
|
-
#
|
|
885
|
-
(
|
|
886
|
-
#
|
|
887
|
-
(
|
|
888
|
-
# TDD Expansion Restructure: Add reference_doc column
|
|
889
|
-
(63, "Add reference_doc column to tasks", _migrate_add_reference_doc),
|
|
890
|
-
# TDD Expansion Restructure: Add boolean columns for idempotent operations
|
|
891
|
-
(64, "Add boolean columns to tasks", _migrate_add_boolean_columns),
|
|
892
|
-
# Review status: Add columns for HITL review workflow
|
|
893
|
-
(65, "Add review columns to tasks", _migrate_add_review_columns),
|
|
894
|
-
# Task Expansion V3: Drop unused is_enriched column
|
|
895
|
-
(66, "Drop is_enriched column from tasks", _migrate_drop_is_enriched),
|
|
896
|
-
# Inter-session messaging: Add table for parent-child agent communication
|
|
897
|
-
(67, "Add inter_session_messages table", _migrate_add_inter_session_messages),
|
|
898
|
-
# Memory V3 Phase 2: Add media column for multimodal support
|
|
899
|
-
(68, "Add media column to memories", _migrate_add_media_column),
|
|
900
|
-
# Skill-based expansion: Add expansion_status column to tasks
|
|
901
|
-
(69, "Add expansion_status column to tasks", _migrate_add_expansion_status),
|
|
902
|
-
# Skills storage: Add skills table for Agent Skills spec
|
|
903
|
-
(70, "Add skills table", _migrate_add_skills_table),
|
|
904
|
-
# Skills: Add partial unique index for global skills
|
|
905
|
-
(71, "Add global skills unique index", _migrate_add_skills_global_unique_index),
|
|
906
|
-
# Local clones: Add table for git clone management
|
|
907
|
-
(72, "Add clones table", _migrate_add_clones_table),
|
|
908
|
-
# Token tracking: Add model column to sessions for cost tracking by model
|
|
909
|
-
(73, "Add model column to sessions", _migrate_add_model_column),
|
|
910
|
-
# TDD cleanup: Drop unused is_tdd_applied column from tasks
|
|
911
|
-
(74, "Drop is_tdd_applied column from tasks", _migrate_drop_is_tdd_applied),
|
|
696
|
+
# Project-scoped session refs: Change seq_num index from global to project-scoped
|
|
697
|
+
(76, "Make sessions.seq_num project-scoped", _migrate_session_seq_num_project_scoped),
|
|
698
|
+
# Project-scoped session refs: Re-backfill seq_num per project
|
|
699
|
+
(77, "Backfill sessions.seq_num per project", _migrate_backfill_session_seq_num_per_project),
|
|
912
700
|
]
|
|
913
701
|
|
|
914
702
|
|
|
@@ -922,8 +710,8 @@ def get_current_version(db: LocalDatabase) -> int:
|
|
|
922
710
|
|
|
923
711
|
|
|
924
712
|
def _apply_baseline(db: LocalDatabase) -> None:
|
|
925
|
-
"""Apply baseline schema for new databases."""
|
|
926
|
-
logger.info("Applying baseline schema (
|
|
713
|
+
"""Apply baseline schema for new databases (flattened at v75)."""
|
|
714
|
+
logger.info("Applying baseline schema (v75)")
|
|
927
715
|
|
|
928
716
|
# Execute baseline schema
|
|
929
717
|
for statement in BASELINE_SCHEMA.strip().split(";"):
|
|
@@ -995,13 +783,10 @@ def run_migrations(db: LocalDatabase) -> int:
|
|
|
995
783
|
Run pending migrations.
|
|
996
784
|
|
|
997
785
|
For new databases (version == 0):
|
|
998
|
-
Applies baseline schema directly
|
|
999
|
-
|
|
1000
|
-
For existing databases (0 < version < 60):
|
|
1001
|
-
Imports and runs legacy migrations incrementally.
|
|
786
|
+
- Applies baseline schema (v75) directly.
|
|
1002
787
|
|
|
1003
|
-
For
|
|
1004
|
-
Runs any new migrations
|
|
788
|
+
For existing databases:
|
|
789
|
+
- Runs any new migrations from v76 onwards.
|
|
1005
790
|
|
|
1006
791
|
Args:
|
|
1007
792
|
db: LocalDatabase instance
|
|
@@ -1013,20 +798,22 @@ def run_migrations(db: LocalDatabase) -> int:
|
|
|
1013
798
|
total_applied = 0
|
|
1014
799
|
|
|
1015
800
|
if current_version == 0:
|
|
1016
|
-
# New database: apply
|
|
801
|
+
# New database with flattened baseline: apply schema directly
|
|
802
|
+
logger.info("Using flattened baseline for new database")
|
|
1017
803
|
_apply_baseline(db)
|
|
1018
804
|
total_applied = 1
|
|
1019
805
|
current_version = BASELINE_VERSION
|
|
1020
806
|
elif current_version < BASELINE_VERSION:
|
|
1021
|
-
#
|
|
1022
|
-
#
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
807
|
+
# Unsupported: Pre-v75 database without local migrations
|
|
808
|
+
# Since we removed legacy migrations, we can't upgrade.
|
|
809
|
+
msg = (
|
|
810
|
+
f"Database version {current_version} is older than baseline "
|
|
811
|
+
f"{BASELINE_VERSION}. Upgrade not supported without legacy migrations."
|
|
812
|
+
)
|
|
813
|
+
logger.error(msg)
|
|
814
|
+
raise MigrationUnsupportedError(msg)
|
|
1028
815
|
|
|
1029
|
-
# Run any new migrations (
|
|
816
|
+
# Run any new migrations (v76+)
|
|
1030
817
|
if MIGRATIONS:
|
|
1031
818
|
applied = _run_migration_list(db, current_version, MIGRATIONS)
|
|
1032
819
|
total_applied += applied
|