cortexshift 0.1.0__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.
- cortexshift/__init__.py +10 -0
- cortexshift/__main__.py +6 -0
- cortexshift/adapters/__init__.py +22 -0
- cortexshift/adapters/command_runner.py +116 -0
- cortexshift/adapters/discovery.py +55 -0
- cortexshift/adapters/git/__init__.py +10 -0
- cortexshift/adapters/git/inspector.py +321 -0
- cortexshift/adapters/git/parser.py +140 -0
- cortexshift/adapters/headless_runner.py +92 -0
- cortexshift/adapters/process_runner.py +56 -0
- cortexshift/adapters/providers/__init__.py +4 -0
- cortexshift/adapters/providers/antigravity.py +530 -0
- cortexshift/adapters/providers/claude.py +375 -0
- cortexshift/adapters/providers/codex.py +434 -0
- cortexshift/adapters/sqlite/__init__.py +10 -0
- cortexshift/adapters/sqlite/migrations.py +268 -0
- cortexshift/adapters/sqlite/store.py +914 -0
- cortexshift/adapters/workspace_lease.py +123 -0
- cortexshift/application/__init__.py +42 -0
- cortexshift/application/checkpoint_builder.py +218 -0
- cortexshift/application/checkpoint_service.py +273 -0
- cortexshift/application/doctor.py +80 -0
- cortexshift/application/handoff_builder.py +281 -0
- cortexshift/application/handoff_renderer.py +430 -0
- cortexshift/application/handoff_service.py +66 -0
- cortexshift/application/init_service.py +86 -0
- cortexshift/application/locator.py +48 -0
- cortexshift/application/native_session.py +65 -0
- cortexshift/application/recovery_service.py +235 -0
- cortexshift/application/repository_service.py +146 -0
- cortexshift/application/resume_service.py +124 -0
- cortexshift/application/run_service.py +270 -0
- cortexshift/application/session_launcher.py +183 -0
- cortexshift/application/session_service.py +63 -0
- cortexshift/application/source_session.py +62 -0
- cortexshift/application/status_service.py +73 -0
- cortexshift/application/switch_service.py +671 -0
- cortexshift/application/task_service.py +201 -0
- cortexshift/application/task_workspace.py +152 -0
- cortexshift/cli/__init__.py +5 -0
- cortexshift/cli/app.py +2477 -0
- cortexshift/domain/__init__.py +153 -0
- cortexshift/domain/checkpoint.py +174 -0
- cortexshift/domain/doctor.py +68 -0
- cortexshift/domain/errors.py +277 -0
- cortexshift/domain/git.py +102 -0
- cortexshift/domain/handoff.py +241 -0
- cortexshift/domain/identifiers.py +27 -0
- cortexshift/domain/launch.py +58 -0
- cortexshift/domain/mcp_binding.py +81 -0
- cortexshift/domain/native_session.py +19 -0
- cortexshift/domain/project.py +37 -0
- cortexshift/domain/provider.py +67 -0
- cortexshift/domain/session.py +92 -0
- cortexshift/domain/status.py +40 -0
- cortexshift/domain/task.py +191 -0
- cortexshift/mcp/__init__.py +38 -0
- cortexshift/mcp/context.py +165 -0
- cortexshift/mcp/facade.py +513 -0
- cortexshift/mcp/models.py +178 -0
- cortexshift/mcp/resources.py +45 -0
- cortexshift/mcp/server.py +52 -0
- cortexshift/mcp/tools.py +176 -0
- cortexshift/ports/__init__.py +39 -0
- cortexshift/ports/checkpoint_store.py +45 -0
- cortexshift/ports/command_runner.py +56 -0
- cortexshift/ports/discovery.py +41 -0
- cortexshift/ports/handoff_delivery.py +91 -0
- cortexshift/ports/handoff_store.py +43 -0
- cortexshift/ports/headless_runner.py +58 -0
- cortexshift/ports/native_session.py +20 -0
- cortexshift/ports/process_runner.py +31 -0
- cortexshift/ports/provider.py +152 -0
- cortexshift/ports/repository.py +44 -0
- cortexshift/ports/session_store.py +27 -0
- cortexshift/ports/state_store.py +55 -0
- cortexshift/ports/workspace_lease.py +39 -0
- cortexshift/tui/__init__.py +24 -0
- cortexshift/tui/actions.py +58 -0
- cortexshift/tui/app.py +1051 -0
- cortexshift/tui/coordinator.py +173 -0
- cortexshift/tui/cortexshift.tcss +258 -0
- cortexshift/tui/facade.py +614 -0
- cortexshift/tui/modals.py +594 -0
- cortexshift/tui/models.py +503 -0
- cortexshift/tui/screens/__init__.py +81 -0
- cortexshift/tui/screens/checkpoints.py +188 -0
- cortexshift/tui/screens/handoffs.py +180 -0
- cortexshift/tui/screens/help.py +117 -0
- cortexshift/tui/screens/overview.py +200 -0
- cortexshift/tui/screens/providers.py +169 -0
- cortexshift/tui/screens/repository.py +143 -0
- cortexshift/tui/screens/sessions.py +146 -0
- cortexshift/tui/screens/task.py +174 -0
- cortexshift/tui/widgets.py +209 -0
- cortexshift-0.1.0.dist-info/METADATA +202 -0
- cortexshift-0.1.0.dist-info/RECORD +100 -0
- cortexshift-0.1.0.dist-info/WHEEL +4 -0
- cortexshift-0.1.0.dist-info/entry_points.txt +2 -0
- cortexshift-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"""Lightweight deterministic schema migration system for CortexShift SQLite database."""
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import sqlite3
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
|
|
7
|
+
from cortexshift.domain.errors import DatabaseStateError, UnsupportedSchemaVersionError
|
|
8
|
+
from cortexshift.domain.identifiers import utc_now
|
|
9
|
+
|
|
10
|
+
CURRENT_SCHEMA_VERSION = 6
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _migrate_v1(conn: sqlite3.Connection) -> None:
|
|
14
|
+
"""Apply Schema Version 1: initial Project, Task, and ProjectRuntime tables."""
|
|
15
|
+
conn.execute(
|
|
16
|
+
"""
|
|
17
|
+
CREATE TABLE projects (
|
|
18
|
+
id TEXT PRIMARY KEY,
|
|
19
|
+
name TEXT NOT NULL,
|
|
20
|
+
repo_path TEXT NOT NULL,
|
|
21
|
+
created_at TEXT NOT NULL,
|
|
22
|
+
metadata TEXT NOT NULL
|
|
23
|
+
);
|
|
24
|
+
"""
|
|
25
|
+
)
|
|
26
|
+
conn.execute(
|
|
27
|
+
"""
|
|
28
|
+
CREATE TABLE tasks (
|
|
29
|
+
id TEXT PRIMARY KEY,
|
|
30
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
31
|
+
title TEXT NOT NULL,
|
|
32
|
+
objective TEXT NOT NULL,
|
|
33
|
+
requirements TEXT NOT NULL,
|
|
34
|
+
constraints TEXT NOT NULL,
|
|
35
|
+
status TEXT NOT NULL,
|
|
36
|
+
completed_items TEXT NOT NULL,
|
|
37
|
+
current_work TEXT,
|
|
38
|
+
remaining_items TEXT NOT NULL,
|
|
39
|
+
known_issues TEXT NOT NULL,
|
|
40
|
+
created_at TEXT NOT NULL,
|
|
41
|
+
updated_at TEXT NOT NULL,
|
|
42
|
+
metadata TEXT NOT NULL
|
|
43
|
+
);
|
|
44
|
+
"""
|
|
45
|
+
)
|
|
46
|
+
conn.execute("CREATE INDEX idx_tasks_project_id ON tasks(project_id);")
|
|
47
|
+
conn.execute(
|
|
48
|
+
"""
|
|
49
|
+
CREATE TABLE project_runtime (
|
|
50
|
+
project_id TEXT PRIMARY KEY REFERENCES projects(id) ON DELETE CASCADE,
|
|
51
|
+
active_task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL
|
|
52
|
+
);
|
|
53
|
+
"""
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _migrate_v2(conn: sqlite3.Connection) -> None:
|
|
58
|
+
"""Apply Schema Version 2: Git snapshots table for repository context tracking."""
|
|
59
|
+
conn.execute(
|
|
60
|
+
"""
|
|
61
|
+
CREATE TABLE git_snapshots (
|
|
62
|
+
id TEXT PRIMARY KEY,
|
|
63
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
64
|
+
project_root TEXT NOT NULL,
|
|
65
|
+
git_root TEXT NOT NULL,
|
|
66
|
+
git_version TEXT,
|
|
67
|
+
branch TEXT,
|
|
68
|
+
head_sha TEXT,
|
|
69
|
+
detached_head INTEGER NOT NULL DEFAULT 0,
|
|
70
|
+
dirty INTEGER NOT NULL DEFAULT 0,
|
|
71
|
+
staged_files TEXT NOT NULL,
|
|
72
|
+
modified_files TEXT NOT NULL,
|
|
73
|
+
untracked_files TEXT NOT NULL,
|
|
74
|
+
conflicted_files TEXT NOT NULL,
|
|
75
|
+
working_tree_diff_summary TEXT,
|
|
76
|
+
staged_diff_summary TEXT,
|
|
77
|
+
captured_at TEXT NOT NULL,
|
|
78
|
+
metadata TEXT NOT NULL
|
|
79
|
+
);
|
|
80
|
+
"""
|
|
81
|
+
)
|
|
82
|
+
conn.execute("CREATE INDEX idx_git_snapshots_project_id ON git_snapshots(project_id);")
|
|
83
|
+
conn.execute(
|
|
84
|
+
"CREATE INDEX idx_git_snapshots_captured_at ON git_snapshots(project_id, captured_at DESC);"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _migrate_v3(conn: sqlite3.Connection) -> None:
|
|
89
|
+
"""Apply Schema Version 3: sessions table for agent execution history."""
|
|
90
|
+
conn.execute(
|
|
91
|
+
"""
|
|
92
|
+
CREATE TABLE sessions (
|
|
93
|
+
id TEXT PRIMARY KEY,
|
|
94
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
95
|
+
provider_id TEXT NOT NULL,
|
|
96
|
+
native_session_id TEXT,
|
|
97
|
+
status TEXT NOT NULL,
|
|
98
|
+
started_at TEXT NOT NULL,
|
|
99
|
+
ended_at TEXT,
|
|
100
|
+
exit_reason TEXT,
|
|
101
|
+
exit_code INTEGER,
|
|
102
|
+
metadata TEXT NOT NULL
|
|
103
|
+
);
|
|
104
|
+
"""
|
|
105
|
+
)
|
|
106
|
+
conn.execute("CREATE INDEX idx_sessions_task_id ON sessions(task_id);")
|
|
107
|
+
conn.execute("CREATE INDEX idx_sessions_started_at ON sessions(started_at DESC);")
|
|
108
|
+
conn.execute("CREATE INDEX idx_sessions_provider_id ON sessions(provider_id);")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _migrate_v4(conn: sqlite3.Connection) -> None:
|
|
112
|
+
"""Apply Schema Version 4: handoffs table for canonical cross-provider handoffs.
|
|
113
|
+
|
|
114
|
+
``payload`` stores the canonical ``HandoffPayload`` as validated JSON text. The
|
|
115
|
+
payload is itself a strictly typed Pydantic schema, so canonical JSON keeps the
|
|
116
|
+
column extensible without repr/pickle/eval or a wide, brittle column set.
|
|
117
|
+
|
|
118
|
+
Deletion semantics: a handoff is meaningless without its project and task, so those
|
|
119
|
+
cascade. Optional target-session and snapshot references null out, allowing handoff
|
|
120
|
+
history to survive even if a referenced observation is removed.
|
|
121
|
+
"""
|
|
122
|
+
conn.execute(
|
|
123
|
+
"""
|
|
124
|
+
CREATE TABLE handoffs (
|
|
125
|
+
id TEXT PRIMARY KEY,
|
|
126
|
+
protocol_version INTEGER NOT NULL,
|
|
127
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
128
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
129
|
+
source_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
130
|
+
source_provider_id TEXT NOT NULL,
|
|
131
|
+
target_provider_id TEXT NOT NULL,
|
|
132
|
+
git_snapshot_id TEXT REFERENCES git_snapshots(id) ON DELETE SET NULL,
|
|
133
|
+
target_session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
|
|
134
|
+
status TEXT NOT NULL,
|
|
135
|
+
payload TEXT NOT NULL,
|
|
136
|
+
created_at TEXT NOT NULL,
|
|
137
|
+
delivered_at TEXT,
|
|
138
|
+
failure_code TEXT,
|
|
139
|
+
metadata TEXT NOT NULL
|
|
140
|
+
);
|
|
141
|
+
"""
|
|
142
|
+
)
|
|
143
|
+
conn.execute("CREATE INDEX idx_handoffs_project_id ON handoffs(project_id);")
|
|
144
|
+
conn.execute("CREATE INDEX idx_handoffs_task_id ON handoffs(task_id);")
|
|
145
|
+
conn.execute("CREATE INDEX idx_handoffs_created_at ON handoffs(created_at DESC);")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _migrate_v5(conn: sqlite3.Connection) -> None:
|
|
149
|
+
"""Add invocation lineage without rewriting any historical session."""
|
|
150
|
+
conn.execute(
|
|
151
|
+
"ALTER TABLE sessions ADD COLUMN resumed_from_session_id TEXT "
|
|
152
|
+
"REFERENCES sessions(id) ON DELETE SET NULL;"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _migrate_v6(conn: sqlite3.Connection) -> None:
|
|
157
|
+
"""Apply Schema Version 6: checkpoints table, session reconciliation,
|
|
158
|
+
and handoff source checkpoint reference.
|
|
159
|
+
"""
|
|
160
|
+
conn.execute(
|
|
161
|
+
"""
|
|
162
|
+
CREATE TABLE checkpoints (
|
|
163
|
+
id TEXT PRIMARY KEY,
|
|
164
|
+
protocol_version INTEGER NOT NULL,
|
|
165
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
166
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
167
|
+
session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
|
|
168
|
+
git_snapshot_id TEXT REFERENCES git_snapshots(id) ON DELETE SET NULL,
|
|
169
|
+
kind TEXT NOT NULL,
|
|
170
|
+
payload TEXT NOT NULL,
|
|
171
|
+
created_at TEXT NOT NULL,
|
|
172
|
+
metadata TEXT NOT NULL
|
|
173
|
+
);
|
|
174
|
+
"""
|
|
175
|
+
)
|
|
176
|
+
conn.execute("CREATE INDEX idx_checkpoints_project_id ON checkpoints(project_id);")
|
|
177
|
+
conn.execute("CREATE INDEX idx_checkpoints_task_id ON checkpoints(task_id);")
|
|
178
|
+
conn.execute(
|
|
179
|
+
"CREATE INDEX idx_checkpoints_created_at ON checkpoints(task_id, created_at DESC);"
|
|
180
|
+
)
|
|
181
|
+
conn.execute("CREATE INDEX idx_checkpoints_session_id ON checkpoints(session_id);")
|
|
182
|
+
conn.execute("ALTER TABLE sessions ADD COLUMN reconciled_at TEXT;")
|
|
183
|
+
conn.execute(
|
|
184
|
+
"ALTER TABLE handoffs ADD COLUMN source_checkpoint_id TEXT "
|
|
185
|
+
"REFERENCES checkpoints(id) ON DELETE SET NULL;"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
# Ordered registry of migration functions: index 0 is v1, index 1 is v2, index 2 is v3, etc.
|
|
190
|
+
MIGRATIONS: list[Callable[[sqlite3.Connection], None]] = [
|
|
191
|
+
_migrate_v1,
|
|
192
|
+
_migrate_v2,
|
|
193
|
+
_migrate_v3,
|
|
194
|
+
_migrate_v4,
|
|
195
|
+
_migrate_v5,
|
|
196
|
+
_migrate_v6,
|
|
197
|
+
]
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def get_current_schema_version(conn: sqlite3.Connection) -> int:
|
|
201
|
+
"""Inspect the database and return the highest applied schema version, or 0 if unversioned."""
|
|
202
|
+
try:
|
|
203
|
+
cursor = conn.cursor()
|
|
204
|
+
cursor.execute(
|
|
205
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='schema_metadata';"
|
|
206
|
+
)
|
|
207
|
+
if cursor.fetchone() is None:
|
|
208
|
+
return 0
|
|
209
|
+
|
|
210
|
+
cursor.execute("SELECT MAX(schema_version) FROM schema_metadata;")
|
|
211
|
+
row = cursor.fetchone()
|
|
212
|
+
if row is None or row[0] is None:
|
|
213
|
+
return 0
|
|
214
|
+
return int(row[0])
|
|
215
|
+
except sqlite3.Error as err:
|
|
216
|
+
raise DatabaseStateError(f"Failed to inspect database schema version: {err}") from err
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def run_migrations(conn: sqlite3.Connection) -> int:
|
|
220
|
+
"""Run all pending schema migrations up to CURRENT_SCHEMA_VERSION in a transaction.
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
The final schema version after migration.
|
|
224
|
+
|
|
225
|
+
Raises:
|
|
226
|
+
UnsupportedSchemaVersionError: If the database is at a higher version than supported.
|
|
227
|
+
DatabaseStateError: If any migration step fails.
|
|
228
|
+
"""
|
|
229
|
+
current_version = get_current_schema_version(conn)
|
|
230
|
+
|
|
231
|
+
if current_version > CURRENT_SCHEMA_VERSION:
|
|
232
|
+
raise UnsupportedSchemaVersionError(current_version, CURRENT_SCHEMA_VERSION)
|
|
233
|
+
|
|
234
|
+
if current_version == CURRENT_SCHEMA_VERSION:
|
|
235
|
+
return current_version
|
|
236
|
+
|
|
237
|
+
prev_isolation = conn.isolation_level
|
|
238
|
+
try:
|
|
239
|
+
conn.isolation_level = None
|
|
240
|
+
conn.execute("BEGIN;")
|
|
241
|
+
|
|
242
|
+
conn.execute(
|
|
243
|
+
"""
|
|
244
|
+
CREATE TABLE IF NOT EXISTS schema_metadata (
|
|
245
|
+
schema_version INTEGER PRIMARY KEY,
|
|
246
|
+
applied_at TEXT NOT NULL
|
|
247
|
+
);
|
|
248
|
+
"""
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
for target_ver in range(current_version + 1, CURRENT_SCHEMA_VERSION + 1):
|
|
252
|
+
migration_fn = MIGRATIONS[target_ver - 1]
|
|
253
|
+
migration_fn(conn)
|
|
254
|
+
conn.execute(
|
|
255
|
+
"INSERT INTO schema_metadata (schema_version, applied_at) VALUES (?, ?);",
|
|
256
|
+
(target_ver, utc_now().isoformat()),
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
conn.execute("COMMIT;")
|
|
260
|
+
except sqlite3.Error as err:
|
|
261
|
+
with contextlib.suppress(sqlite3.Error):
|
|
262
|
+
conn.execute("ROLLBACK;")
|
|
263
|
+
msg = f"Migration to schema v{current_version + 1} failed: {err}"
|
|
264
|
+
raise DatabaseStateError(msg) from err
|
|
265
|
+
finally:
|
|
266
|
+
conn.isolation_level = prev_isolation
|
|
267
|
+
|
|
268
|
+
return CURRENT_SCHEMA_VERSION
|