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,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."""
|