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.
Files changed (100) hide show
  1. cortexshift/__init__.py +10 -0
  2. cortexshift/__main__.py +6 -0
  3. cortexshift/adapters/__init__.py +22 -0
  4. cortexshift/adapters/command_runner.py +116 -0
  5. cortexshift/adapters/discovery.py +55 -0
  6. cortexshift/adapters/git/__init__.py +10 -0
  7. cortexshift/adapters/git/inspector.py +321 -0
  8. cortexshift/adapters/git/parser.py +140 -0
  9. cortexshift/adapters/headless_runner.py +92 -0
  10. cortexshift/adapters/process_runner.py +56 -0
  11. cortexshift/adapters/providers/__init__.py +4 -0
  12. cortexshift/adapters/providers/antigravity.py +530 -0
  13. cortexshift/adapters/providers/claude.py +375 -0
  14. cortexshift/adapters/providers/codex.py +434 -0
  15. cortexshift/adapters/sqlite/__init__.py +10 -0
  16. cortexshift/adapters/sqlite/migrations.py +268 -0
  17. cortexshift/adapters/sqlite/store.py +914 -0
  18. cortexshift/adapters/workspace_lease.py +123 -0
  19. cortexshift/application/__init__.py +42 -0
  20. cortexshift/application/checkpoint_builder.py +218 -0
  21. cortexshift/application/checkpoint_service.py +273 -0
  22. cortexshift/application/doctor.py +80 -0
  23. cortexshift/application/handoff_builder.py +281 -0
  24. cortexshift/application/handoff_renderer.py +430 -0
  25. cortexshift/application/handoff_service.py +66 -0
  26. cortexshift/application/init_service.py +86 -0
  27. cortexshift/application/locator.py +48 -0
  28. cortexshift/application/native_session.py +65 -0
  29. cortexshift/application/recovery_service.py +235 -0
  30. cortexshift/application/repository_service.py +146 -0
  31. cortexshift/application/resume_service.py +124 -0
  32. cortexshift/application/run_service.py +270 -0
  33. cortexshift/application/session_launcher.py +183 -0
  34. cortexshift/application/session_service.py +63 -0
  35. cortexshift/application/source_session.py +62 -0
  36. cortexshift/application/status_service.py +73 -0
  37. cortexshift/application/switch_service.py +671 -0
  38. cortexshift/application/task_service.py +201 -0
  39. cortexshift/application/task_workspace.py +152 -0
  40. cortexshift/cli/__init__.py +5 -0
  41. cortexshift/cli/app.py +2477 -0
  42. cortexshift/domain/__init__.py +153 -0
  43. cortexshift/domain/checkpoint.py +174 -0
  44. cortexshift/domain/doctor.py +68 -0
  45. cortexshift/domain/errors.py +277 -0
  46. cortexshift/domain/git.py +102 -0
  47. cortexshift/domain/handoff.py +241 -0
  48. cortexshift/domain/identifiers.py +27 -0
  49. cortexshift/domain/launch.py +58 -0
  50. cortexshift/domain/mcp_binding.py +81 -0
  51. cortexshift/domain/native_session.py +19 -0
  52. cortexshift/domain/project.py +37 -0
  53. cortexshift/domain/provider.py +67 -0
  54. cortexshift/domain/session.py +92 -0
  55. cortexshift/domain/status.py +40 -0
  56. cortexshift/domain/task.py +191 -0
  57. cortexshift/mcp/__init__.py +38 -0
  58. cortexshift/mcp/context.py +165 -0
  59. cortexshift/mcp/facade.py +513 -0
  60. cortexshift/mcp/models.py +178 -0
  61. cortexshift/mcp/resources.py +45 -0
  62. cortexshift/mcp/server.py +52 -0
  63. cortexshift/mcp/tools.py +176 -0
  64. cortexshift/ports/__init__.py +39 -0
  65. cortexshift/ports/checkpoint_store.py +45 -0
  66. cortexshift/ports/command_runner.py +56 -0
  67. cortexshift/ports/discovery.py +41 -0
  68. cortexshift/ports/handoff_delivery.py +91 -0
  69. cortexshift/ports/handoff_store.py +43 -0
  70. cortexshift/ports/headless_runner.py +58 -0
  71. cortexshift/ports/native_session.py +20 -0
  72. cortexshift/ports/process_runner.py +31 -0
  73. cortexshift/ports/provider.py +152 -0
  74. cortexshift/ports/repository.py +44 -0
  75. cortexshift/ports/session_store.py +27 -0
  76. cortexshift/ports/state_store.py +55 -0
  77. cortexshift/ports/workspace_lease.py +39 -0
  78. cortexshift/tui/__init__.py +24 -0
  79. cortexshift/tui/actions.py +58 -0
  80. cortexshift/tui/app.py +1051 -0
  81. cortexshift/tui/coordinator.py +173 -0
  82. cortexshift/tui/cortexshift.tcss +258 -0
  83. cortexshift/tui/facade.py +614 -0
  84. cortexshift/tui/modals.py +594 -0
  85. cortexshift/tui/models.py +503 -0
  86. cortexshift/tui/screens/__init__.py +81 -0
  87. cortexshift/tui/screens/checkpoints.py +188 -0
  88. cortexshift/tui/screens/handoffs.py +180 -0
  89. cortexshift/tui/screens/help.py +117 -0
  90. cortexshift/tui/screens/overview.py +200 -0
  91. cortexshift/tui/screens/providers.py +169 -0
  92. cortexshift/tui/screens/repository.py +143 -0
  93. cortexshift/tui/screens/sessions.py +146 -0
  94. cortexshift/tui/screens/task.py +174 -0
  95. cortexshift/tui/widgets.py +209 -0
  96. cortexshift-0.1.0.dist-info/METADATA +202 -0
  97. cortexshift-0.1.0.dist-info/RECORD +100 -0
  98. cortexshift-0.1.0.dist-info/WHEEL +4 -0
  99. cortexshift-0.1.0.dist-info/entry_points.txt +2 -0
  100. cortexshift-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,153 @@
1
+ """Core domain models for CortexShift."""
2
+
3
+ from cortexshift.domain.checkpoint import (
4
+ CHECKPOINT_PROTOCOL_VERSION,
5
+ Checkpoint,
6
+ CheckpointGitState,
7
+ CheckpointKind,
8
+ CheckpointPayload,
9
+ CheckpointRecord,
10
+ CheckpointSourceSession,
11
+ CheckpointTaskSnapshot,
12
+ CheckpointTestProvenance,
13
+ CheckpointTestStatus,
14
+ )
15
+ from cortexshift.domain.doctor import (
16
+ AuthenticationStatus,
17
+ DoctorReport,
18
+ PlatformInfo,
19
+ ProviderDiagnostic,
20
+ )
21
+ from cortexshift.domain.errors import (
22
+ CheckpointNotFoundError,
23
+ CortexShiftError,
24
+ DatabaseStateError,
25
+ GitNotInstalledError,
26
+ GitProbeError,
27
+ GitProbeTimeoutError,
28
+ HandoffDeliveryError,
29
+ HandoffNotFoundError,
30
+ InvalidCheckpointInputError,
31
+ NoActiveTaskError,
32
+ NoSourceSessionError,
33
+ NotAGitRepositoryError,
34
+ ProjectAlreadyInitializedError,
35
+ ProjectConflictError,
36
+ ProjectNotInitializedError,
37
+ ProviderNotFoundError,
38
+ RepositoryInspectionError,
39
+ SameProviderSwitchError,
40
+ SessionNotFoundError,
41
+ SessionRecoveryError,
42
+ SessionTaskMismatchError,
43
+ SnapshotNotFoundError,
44
+ StateCorruptionError,
45
+ TaskAlreadyCompletedError,
46
+ TaskNotActivatableError,
47
+ TaskNotFoundError,
48
+ TerminalRequiredError,
49
+ UnknownProviderError,
50
+ UnsupportedPromptError,
51
+ UnsupportedSchemaVersionError,
52
+ WorkspaceLockedError,
53
+ )
54
+ from cortexshift.domain.git import GitSnapshot, RepositoryInspection, RepositoryInspectionStatus
55
+ from cortexshift.domain.handoff import (
56
+ HANDOFF_PROTOCOL_VERSION,
57
+ HandoffFailureCode,
58
+ HandoffGitState,
59
+ HandoffPayload,
60
+ HandoffRecord,
61
+ HandoffSourceSession,
62
+ HandoffStatus,
63
+ HandoffTestStatus,
64
+ )
65
+ from cortexshift.domain.identifiers import generate_id, utc_now
66
+ from cortexshift.domain.launch import LaunchSpecification
67
+ from cortexshift.domain.project import Project
68
+ from cortexshift.domain.provider import (
69
+ PROVIDER_ANTIGRAVITY,
70
+ PROVIDER_CLAUDE,
71
+ PROVIDER_CODEX,
72
+ ProviderCapabilities,
73
+ ProviderId,
74
+ )
75
+ from cortexshift.domain.session import Session, SessionExitReason, SessionStatus
76
+ from cortexshift.domain.status import ActiveTaskSummary, ProgressSummary, ProjectStatus
77
+ from cortexshift.domain.task import Task, TaskStatus
78
+
79
+ __all__ = [
80
+ "ActiveTaskSummary",
81
+ "AuthenticationStatus",
82
+ "CHECKPOINT_PROTOCOL_VERSION",
83
+ "Checkpoint",
84
+ "CheckpointGitState",
85
+ "CheckpointKind",
86
+ "CheckpointNotFoundError",
87
+ "CheckpointPayload",
88
+ "CheckpointRecord",
89
+ "CheckpointSourceSession",
90
+ "CheckpointTaskSnapshot",
91
+ "CheckpointTestProvenance",
92
+ "CheckpointTestStatus",
93
+ "CortexShiftError",
94
+ "DatabaseStateError",
95
+ "DoctorReport",
96
+ "GitNotInstalledError",
97
+ "GitProbeError",
98
+ "GitProbeTimeoutError",
99
+ "GitSnapshot",
100
+ "HandoffDeliveryError",
101
+ "HandoffFailureCode",
102
+ "HandoffGitState",
103
+ "HandoffNotFoundError",
104
+ "HandoffPayload",
105
+ "HandoffRecord",
106
+ "HandoffSourceSession",
107
+ "HandoffStatus",
108
+ "HandoffTestStatus",
109
+ "InvalidCheckpointInputError",
110
+ "LaunchSpecification",
111
+ "NoActiveTaskError",
112
+ "NoSourceSessionError",
113
+ "NotAGitRepositoryError",
114
+ "PlatformInfo",
115
+ "ProgressSummary",
116
+ "Project",
117
+ "ProjectAlreadyInitializedError",
118
+ "ProjectConflictError",
119
+ "ProjectNotInitializedError",
120
+ "ProjectStatus",
121
+ "ProviderCapabilities",
122
+ "ProviderDiagnostic",
123
+ "ProviderId",
124
+ "ProviderNotFoundError",
125
+ "RepositoryInspection",
126
+ "RepositoryInspectionError",
127
+ "RepositoryInspectionStatus",
128
+ "SameProviderSwitchError",
129
+ "Session",
130
+ "SessionExitReason",
131
+ "SessionNotFoundError",
132
+ "SessionRecoveryError",
133
+ "SessionStatus",
134
+ "SessionTaskMismatchError",
135
+ "SnapshotNotFoundError",
136
+ "StateCorruptionError",
137
+ "Task",
138
+ "TaskAlreadyCompletedError",
139
+ "TaskNotActivatableError",
140
+ "TaskNotFoundError",
141
+ "TaskStatus",
142
+ "TerminalRequiredError",
143
+ "UnknownProviderError",
144
+ "UnsupportedPromptError",
145
+ "UnsupportedSchemaVersionError",
146
+ "WorkspaceLockedError",
147
+ "HANDOFF_PROTOCOL_VERSION",
148
+ "PROVIDER_ANTIGRAVITY",
149
+ "PROVIDER_CLAUDE",
150
+ "PROVIDER_CODEX",
151
+ "generate_id",
152
+ "utc_now",
153
+ ]
@@ -0,0 +1,174 @@
1
+ """Domain models for the canonical CortexShift checkpoint protocol (Protocol v1).
2
+
3
+ A Checkpoint is an immutable structured observation of the Task and repository at a
4
+ meaningful point in development. Checkpoints do not contain provider transcripts,
5
+ reasoning, full diffs, or file contents. They provide resilient, deterministic engineering
6
+ evidence that enables recovery from crashes, quota exhaustion, and unexpected terminations
7
+ without requiring the outgoing provider or model to still be available.
8
+ """
9
+
10
+ from datetime import datetime
11
+ from enum import StrEnum
12
+ from typing import Any
13
+
14
+ from pydantic import BaseModel, ConfigDict, Field
15
+
16
+ from cortexshift.domain.git import RepositoryInspectionStatus
17
+ from cortexshift.domain.identifiers import generate_id, utc_now
18
+ from cortexshift.domain.provider import ProviderId
19
+ from cortexshift.domain.session import SessionExitReason, SessionStatus
20
+
21
+ CHECKPOINT_PROTOCOL_VERSION = 1
22
+
23
+ MAX_OPERATOR_NOTE_CHARS = 2_000
24
+ MAX_DECISION_CHARS = 1_000
25
+ MAX_TEST_SUMMARY_CHARS = 1_000
26
+
27
+ # Structured provenance marker recorded in the extensible `metadata` mapping that both
28
+ # CheckpointRecord and CheckpointPayload already carry. ADR-0009 specifies that
29
+ # `record_decision` persists its checkpoint with `trigger="decision"`; the marker lives in
30
+ # existing free-form metadata, so Checkpoint Protocol v1 and SQLite schema v6 are unchanged.
31
+ CHECKPOINT_TRIGGER_KEY = "trigger"
32
+ CHECKPOINT_TRIGGER_DECISION = "decision"
33
+
34
+
35
+ class CheckpointKind(StrEnum):
36
+ """Classification of how a checkpoint was captured.
37
+
38
+ - MANUAL: Explicitly requested by user or agent via `cortexshift checkpoint create`.
39
+ - SESSION_END: Automatically captured when a launched provider process terminates.
40
+ - RECOVERY: Reconstructed after an interrupted session via `cortexshift recover`.
41
+ """
42
+
43
+ MANUAL = "manual"
44
+ SESSION_END = "session_end"
45
+ RECOVERY = "recovery"
46
+
47
+
48
+ class CheckpointTestProvenance(StrEnum):
49
+ """Origin and trustworthiness of recorded test status.
50
+
51
+ - UNKNOWN: No test execution was recorded or observed.
52
+ - REPORTED: Reported by human operator or agent, but not independently verified.
53
+ - VERIFIED: Directly observed and validated by a CortexShift verification subsystem.
54
+ """
55
+
56
+ UNKNOWN = "unknown"
57
+ REPORTED = "reported"
58
+ VERIFIED = "verified"
59
+
60
+
61
+ class CheckpointTestStatus(BaseModel):
62
+ """Structured test status with explicit provenance.
63
+
64
+ A provider process exiting with code 0 does NOT prove tests passed. Where no
65
+ independently verified execution occurred, provenance remains REPORTED or UNKNOWN.
66
+ """
67
+
68
+ model_config = ConfigDict(frozen=True)
69
+
70
+ known: bool = False
71
+ summary: str = "No independently verified test results recorded."
72
+ provenance: CheckpointTestProvenance = CheckpointTestProvenance.UNKNOWN
73
+
74
+
75
+ class CheckpointTaskSnapshot(BaseModel):
76
+ """Historical point-in-time snapshot of the Task state.
77
+
78
+ Checkpoints preserve the Task state as it was when the checkpoint was captured,
79
+ ensuring historical immutability when the Task is later modified.
80
+ """
81
+
82
+ model_config = ConfigDict(frozen=True)
83
+
84
+ task_id: str
85
+ task_title: str
86
+ task_status: str
87
+ objective: str
88
+ requirements: list[str] = Field(default_factory=list)
89
+ constraints: list[str] = Field(default_factory=list)
90
+ completed: list[str] = Field(default_factory=list)
91
+ current_work: str | None = None
92
+ remaining: list[str] = Field(default_factory=list)
93
+ known_issues: list[str] = Field(default_factory=list)
94
+
95
+
96
+ class CheckpointGitState(BaseModel):
97
+ """Historical point-in-time observation of Git state."""
98
+
99
+ model_config = ConfigDict(frozen=True)
100
+
101
+ status: RepositoryInspectionStatus
102
+ available: bool = False
103
+ note: str
104
+ branch: str | None = None
105
+ head_sha: str | None = None
106
+ detached_head: bool = False
107
+ dirty: bool = False
108
+ staged_count: int = 0
109
+ modified_count: int = 0
110
+ untracked_count: int = 0
111
+ conflicted_count: int = 0
112
+ working_tree_diff_summary: str | None = None
113
+ staged_diff_summary: str | None = None
114
+ snapshot_id: str | None = None
115
+
116
+
117
+ class CheckpointSourceSession(BaseModel):
118
+ """Compact summary of the CortexShift Session associated with this checkpoint.
119
+
120
+ Provider transcripts, conversations, and reasoning are strictly excluded.
121
+ """
122
+
123
+ model_config = ConfigDict(frozen=True)
124
+
125
+ session_id: str
126
+ provider_id: ProviderId
127
+ native_session_id: str | None = None
128
+ status: SessionStatus | None = None
129
+ exit_reason: SessionExitReason | None = None
130
+ exit_code: int | None = None
131
+
132
+
133
+ class CheckpointPayload(BaseModel):
134
+ """Canonical structured point-in-time engineering context for a checkpoint."""
135
+
136
+ model_config = ConfigDict(frozen=True)
137
+
138
+ protocol_version: int = CHECKPOINT_PROTOCOL_VERSION
139
+ generated_at: datetime = Field(default_factory=utc_now)
140
+
141
+ task: CheckpointTaskSnapshot
142
+ git_state: CheckpointGitState
143
+ files_touched: list[str] = Field(default_factory=list)
144
+ decisions: list[str] = Field(default_factory=list)
145
+ test_status: CheckpointTestStatus = Field(default_factory=CheckpointTestStatus)
146
+ operator_note: str | None = None
147
+ source_session: CheckpointSourceSession | None = None
148
+ metadata: dict[str, Any] = Field(default_factory=dict)
149
+
150
+
151
+ def generate_checkpoint_id() -> str:
152
+ """Generate default ID for checkpoints."""
153
+ return generate_id("cp")
154
+
155
+
156
+ class CheckpointRecord(BaseModel):
157
+ """Canonical persistent checkpoint entity wrapping an immutable payload."""
158
+
159
+ model_config = ConfigDict(frozen=True)
160
+
161
+ id: str = Field(default_factory=generate_checkpoint_id)
162
+ protocol_version: int = CHECKPOINT_PROTOCOL_VERSION
163
+ project_id: str
164
+ task_id: str
165
+ session_id: str | None = None
166
+ git_snapshot_id: str | None = None
167
+ kind: CheckpointKind
168
+ payload: CheckpointPayload
169
+ created_at: datetime = Field(default_factory=utc_now)
170
+ metadata: dict[str, Any] = Field(default_factory=dict)
171
+
172
+
173
+ # Canonical alias
174
+ Checkpoint = CheckpointRecord
@@ -0,0 +1,68 @@
1
+ """Domain models for environment inspection and provider diagnostics."""
2
+
3
+ from datetime import datetime
4
+ from enum import StrEnum
5
+ from typing import Any
6
+
7
+ from pydantic import BaseModel, ConfigDict, Field
8
+
9
+ from cortexshift.domain.provider import ProviderCapabilities, ProviderId
10
+
11
+
12
+ class AuthenticationStatus(StrEnum):
13
+ """Status of provider CLI authentication."""
14
+
15
+ AUTHENTICATED = "authenticated"
16
+ NOT_AUTHENTICATED = "not_authenticated"
17
+ UNKNOWN = "unknown"
18
+ NOT_PROBED = "not_probed"
19
+
20
+
21
+ class PlatformInfo(BaseModel):
22
+ """System and environment metadata for diagnostics."""
23
+
24
+ model_config = ConfigDict(frozen=True)
25
+
26
+ system: str
27
+ release: str
28
+ machine: str
29
+ python_version: str
30
+
31
+
32
+ class ProviderDiagnostic(BaseModel):
33
+ """Diagnostic health and capability record for a single provider CLI."""
34
+
35
+ model_config = ConfigDict(frozen=True)
36
+
37
+ provider_id: ProviderId
38
+ display_name: str
39
+ executable: str
40
+ installed: bool
41
+ resolved_path: str | None = None
42
+ version: str | None = None
43
+ authentication_status: AuthenticationStatus = AuthenticationStatus.UNKNOWN
44
+ capabilities: ProviderCapabilities
45
+ diagnostics: list[str] = Field(default_factory=list)
46
+ metadata: dict[str, Any] = Field(default_factory=dict)
47
+
48
+ @property
49
+ def id(self) -> ProviderId:
50
+ """Alias for provider_id for serialization convenience."""
51
+ return self.provider_id
52
+
53
+ @property
54
+ def authentication(self) -> AuthenticationStatus:
55
+ """Alias for authentication_status."""
56
+ return self.authentication_status
57
+
58
+
59
+ class DoctorReport(BaseModel):
60
+ """Aggregate diagnostic report generated by `cortexshift doctor`."""
61
+
62
+ model_config = ConfigDict(frozen=True)
63
+
64
+ cortexshift_version: str
65
+ python_version: str
66
+ platform: PlatformInfo
67
+ timestamp: datetime
68
+ providers: list[ProviderDiagnostic]
@@ -0,0 +1,277 @@
1
+ """Domain and application exception hierarchy for CortexShift."""
2
+
3
+ from pathlib import Path
4
+
5
+
6
+ class CortexShiftError(Exception):
7
+ """Base exception for all CortexShift domain and application errors."""
8
+
9
+
10
+ class NativeResumeError(CortexShiftError):
11
+ """An exact native resume cannot be performed safely."""
12
+
13
+
14
+ class ProjectNotInitializedError(CortexShiftError):
15
+ """Raised when an operation requires an initialized CortexShift project but none was found."""
16
+
17
+ def __init__(self, message: str = "CortexShift is not initialized here.") -> None:
18
+ super().__init__(message)
19
+
20
+
21
+ class ProjectAlreadyInitializedError(CortexShiftError):
22
+ """Raised when initialization is attempted on an already initialized project."""
23
+
24
+ def __init__(self, project_name: str, path: str) -> None:
25
+ super().__init__(f"CortexShift is already initialized for {project_name} at {path}.")
26
+ self.project_name = project_name
27
+ self.path = path
28
+
29
+
30
+ class ProjectConflictError(CortexShiftError):
31
+ """Raised when existing project state conflicts with the target directory."""
32
+
33
+
34
+ class TaskNotFoundError(CortexShiftError):
35
+ """Raised when a task with the specified identifier cannot be found."""
36
+
37
+ def __init__(self, task_id: str) -> None:
38
+ super().__init__(f"Task '{task_id}' was not found.")
39
+ self.task_id = task_id
40
+
41
+
42
+ class NoActiveTaskError(CortexShiftError):
43
+ """Raised when an active task operation is requested but no task is currently active."""
44
+
45
+ def __init__(self, message: str = "No active task found for this project.") -> None:
46
+ super().__init__(message)
47
+
48
+
49
+ class TaskNotActivatableError(CortexShiftError):
50
+ """Raised when attempting to activate a task in an ineligible or terminal state."""
51
+
52
+
53
+ class TaskAlreadyCompletedError(CortexShiftError):
54
+ """Raised when attempting to complete a task that is already completed."""
55
+
56
+ def __init__(self, task_id: str) -> None:
57
+ super().__init__(f"Task '{task_id}' is already completed.")
58
+ self.task_id = task_id
59
+
60
+
61
+ class DatabaseStateError(CortexShiftError):
62
+ """Base exception for persistent storage failures."""
63
+
64
+
65
+ class UnsupportedSchemaVersionError(DatabaseStateError):
66
+ """Raised when database schema version is newer than supported by this CortexShift version."""
67
+
68
+ def __init__(self, current_version: int, max_supported_version: int) -> None:
69
+ super().__init__(
70
+ f"This CortexShift state was created by a newer incompatible version "
71
+ f"(schema v{current_version}, supported up to v{max_supported_version})."
72
+ )
73
+ self.current_version = current_version
74
+ self.max_supported_version = max_supported_version
75
+
76
+
77
+ class StateCorruptionError(DatabaseStateError):
78
+ """Raised when database state is corrupted, malformed, or unreadable."""
79
+
80
+ def __init__(self, detail: str) -> None:
81
+ super().__init__(f"CortexShift state could not be opened: {detail}")
82
+ self.detail = detail
83
+
84
+
85
+ class RepositoryInspectionError(CortexShiftError):
86
+ """Base exception for repository inspection failures."""
87
+
88
+
89
+ class GitNotInstalledError(RepositoryInspectionError):
90
+ """Raised when Git is required but not installed or found in PATH."""
91
+
92
+ def __init__(self, message: str = "Git executable was not found in PATH.") -> None:
93
+ super().__init__(message)
94
+
95
+
96
+ class NotAGitRepositoryError(RepositoryInspectionError):
97
+ """Raised when the project is not inside a Git repository."""
98
+
99
+ def __init__(
100
+ self,
101
+ message: str = "This CortexShift project is not inside a Git repository.",
102
+ ) -> None:
103
+ super().__init__(message)
104
+
105
+
106
+ class GitProbeTimeoutError(RepositoryInspectionError):
107
+ """Raised when a Git command times out during inspection."""
108
+
109
+ def __init__(self, message: str = "Git inspection timed out.") -> None:
110
+ super().__init__(message)
111
+
112
+
113
+ class GitProbeError(RepositoryInspectionError):
114
+ """Raised when a Git inspection command fails unexpectedly."""
115
+
116
+ def __init__(self, message: str = "Git repository inspection failed.") -> None:
117
+ super().__init__(message)
118
+
119
+
120
+ class SnapshotNotFoundError(CortexShiftError):
121
+ """Raised when a repository snapshot with the specified identifier cannot be found."""
122
+
123
+ def __init__(self, snapshot_id: str) -> None:
124
+ super().__init__(f"Snapshot '{snapshot_id}' was not found.")
125
+ self.snapshot_id = snapshot_id
126
+
127
+
128
+ class ProviderNotFoundError(CortexShiftError):
129
+ """Raised when a requested provider executable is not found in PATH."""
130
+
131
+ def __init__(self, message: str) -> None:
132
+ super().__init__(message)
133
+
134
+
135
+ class UnknownProviderError(CortexShiftError, ValueError):
136
+ """Raised when an unrecognized provider ID is requested."""
137
+
138
+ def __init__(self, provider_id: str, supported: list[str]) -> None:
139
+ self.provider_id = provider_id
140
+ self.supported = supported
141
+ super().__init__(
142
+ f"Unknown provider '{provider_id}'. Supported providers: {', '.join(supported)}"
143
+ )
144
+
145
+
146
+ class UnsupportedPromptError(CortexShiftError):
147
+ """Raised when an initial prompt is provided to a provider that does not support it."""
148
+
149
+ def __init__(self, message: str) -> None:
150
+ super().__init__(message)
151
+
152
+
153
+ class WorkspaceLockedError(CortexShiftError):
154
+ """Raised when an exclusive workspace lease cannot be acquired."""
155
+
156
+ def __init__(
157
+ self,
158
+ message: str | None = None,
159
+ lock_path: Path | None = None,
160
+ ) -> None:
161
+ self.lock_path = lock_path
162
+ if message is None:
163
+ message = (
164
+ "Another CortexShift agent session is already active for this project.\n\n"
165
+ "Lock file: .cortexshift/agent.lock\n\n"
166
+ "Wait for that session to finish and try again."
167
+ )
168
+ super().__init__(message)
169
+
170
+
171
+ class TerminalRequiredError(CortexShiftError):
172
+ """Raised when an interactive provider session is attempted without a usable TTY."""
173
+
174
+ def __init__(
175
+ self,
176
+ message: str = (
177
+ "Interactive provider launch requires a terminal (TTY).\n\n"
178
+ "To simulate provider launch non-interactively, use: "
179
+ "cortexshift run <provider> --dry-run"
180
+ ),
181
+ ) -> None:
182
+ super().__init__(message)
183
+
184
+
185
+ class SessionNotFoundError(CortexShiftError):
186
+ """Raised when a session with the specified identifier cannot be found."""
187
+
188
+ def __init__(self, session_id: str) -> None:
189
+ super().__init__(f"Session '{session_id}' was not found.")
190
+ self.session_id = session_id
191
+
192
+
193
+ class NoSourceSessionError(CortexShiftError):
194
+ """Raised when a handoff is requested but no prior CortexShift Session exists.
195
+
196
+ `switch` is strictly a provider handoff operation; it never silently degrades into
197
+ a first-agent `run`.
198
+ """
199
+
200
+ def __init__(self, message: str | None = None) -> None:
201
+ if message is None:
202
+ message = (
203
+ "No previous CortexShift session exists for the active task.\n\n"
204
+ "There is nothing to hand off yet.\n\n"
205
+ "Start the first agent on this task with:\n\n"
206
+ " cortexshift run <provider>\n"
207
+ )
208
+ super().__init__(message)
209
+
210
+
211
+ class SessionTaskMismatchError(CortexShiftError):
212
+ """Raised when an explicitly selected source Session does not belong to the active task."""
213
+
214
+ def __init__(self, session_id: str, task_id: str) -> None:
215
+ super().__init__(
216
+ f"Session '{session_id}' does not belong to the active task '{task_id}'.\n\n"
217
+ "A handoff source session must belong to the currently active CortexShift task."
218
+ )
219
+ self.session_id = session_id
220
+ self.task_id = task_id
221
+
222
+
223
+ class SameProviderSwitchError(CortexShiftError):
224
+ """Raised when the handoff target provider equals the source session provider."""
225
+
226
+ def __init__(self, provider_display_name: str) -> None:
227
+ super().__init__(
228
+ f"The latest task session already uses {provider_display_name}.\n\n"
229
+ "CortexShift switch is intended for provider handoff.\n\n"
230
+ "Choose a different target provider, or select an explicit source session "
231
+ "with --from-session."
232
+ )
233
+ self.provider_display_name = provider_display_name
234
+
235
+
236
+ class HandoffNotFoundError(CortexShiftError):
237
+ """Raised when a handoff with the specified identifier cannot be found."""
238
+
239
+ def __init__(self, handoff_id: str) -> None:
240
+ super().__init__(f"Handoff '{handoff_id}' was not found.")
241
+ self.handoff_id = handoff_id
242
+
243
+
244
+ class HandoffDeliveryError(CortexShiftError):
245
+ """Raised when handoff context could not be delivered to the target provider.
246
+
247
+ Carries a safe machine classification. Raw provider stdout/stderr and any provider
248
+ bootstrap response are deliberately excluded from both the message and the code.
249
+ """
250
+
251
+ def __init__(self, failure_code: str, message: str) -> None:
252
+ super().__init__(message)
253
+ self.failure_code = failure_code
254
+
255
+
256
+ class CheckpointNotFoundError(CortexShiftError):
257
+ """Raised when a checkpoint with the specified identifier cannot be found."""
258
+
259
+ def __init__(self, checkpoint_id: str) -> None:
260
+ super().__init__(f"Checkpoint '{checkpoint_id}' was not found.")
261
+ self.checkpoint_id = checkpoint_id
262
+
263
+
264
+ class InvalidCheckpointInputError(CortexShiftError):
265
+ """Raised when user or agent checkpoint input exceeds bounds or fails validation."""
266
+
267
+
268
+ class SessionRecoveryError(CortexShiftError):
269
+ """Raised when crash recovery encounters an unrecoverable orchestration condition."""
270
+
271
+
272
+ class McpContextError(CortexShiftError):
273
+ """Raised when MCP execution context resolution or validation fails."""
274
+
275
+
276
+ class McpReadOnlyError(CortexShiftError):
277
+ """Raised when a state-mutating MCP tool is invoked in read-only or unmanaged mode."""