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,80 @@
|
|
|
1
|
+
"""Application service for running environment and provider diagnostics."""
|
|
2
|
+
|
|
3
|
+
import platform
|
|
4
|
+
from datetime import UTC, datetime
|
|
5
|
+
|
|
6
|
+
from cortexshift import __version__
|
|
7
|
+
from cortexshift.adapters.discovery import BuiltinProviderDiscovery
|
|
8
|
+
from cortexshift.domain.doctor import DoctorReport, PlatformInfo, ProviderDiagnostic
|
|
9
|
+
from cortexshift.domain.provider import ProviderId
|
|
10
|
+
from cortexshift.ports.discovery import ProviderDiscoveryPort
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class UnknownProviderError(ValueError):
|
|
14
|
+
"""Raised when an unrecognized provider ID is requested for diagnostics."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, provider_id: str, supported: list[str]) -> None:
|
|
17
|
+
self.provider_id = provider_id
|
|
18
|
+
self.supported = supported
|
|
19
|
+
super().__init__(
|
|
20
|
+
f"Unknown provider '{provider_id}'. Supported providers: {', '.join(supported)}"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class DoctorService:
|
|
25
|
+
"""Orchestrates environment discovery and provider diagnostics.
|
|
26
|
+
|
|
27
|
+
Collects platform metadata and delegates provider probing to a
|
|
28
|
+
ProviderDiscoveryPort implementation. Does not interact directly with CLI
|
|
29
|
+
frameworks, Rich rendering, or raw subprocess calls.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, discovery: ProviderDiscoveryPort | None = None) -> None:
|
|
33
|
+
self._discovery = discovery or BuiltinProviderDiscovery()
|
|
34
|
+
|
|
35
|
+
def run_diagnostics(
|
|
36
|
+
self,
|
|
37
|
+
provider_ids: list[ProviderId] | None = None,
|
|
38
|
+
) -> DoctorReport:
|
|
39
|
+
"""Run diagnostics for all or selected providers.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
provider_ids: Optional list of provider IDs to filter diagnostics by.
|
|
43
|
+
If None or empty, all supported providers are diagnosed.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
DoctorReport containing platform metadata and provider diagnostics.
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
UnknownProviderError: If any specified provider ID is not supported.
|
|
50
|
+
"""
|
|
51
|
+
supported_ids = self._discovery.get_supported_provider_ids()
|
|
52
|
+
supported_str_list = [str(pid) for pid in supported_ids]
|
|
53
|
+
|
|
54
|
+
provider_diagnostics: list[ProviderDiagnostic] = []
|
|
55
|
+
|
|
56
|
+
if provider_ids:
|
|
57
|
+
# Validate all requested providers first
|
|
58
|
+
for pid in provider_ids:
|
|
59
|
+
if pid not in supported_ids:
|
|
60
|
+
raise UnknownProviderError(str(pid), supported_str_list)
|
|
61
|
+
|
|
62
|
+
for pid in provider_ids:
|
|
63
|
+
provider_diagnostics.append(self._discovery.discover_provider(pid))
|
|
64
|
+
else:
|
|
65
|
+
provider_diagnostics = self._discovery.discover_all()
|
|
66
|
+
|
|
67
|
+
platform_info = PlatformInfo(
|
|
68
|
+
system=platform.system(),
|
|
69
|
+
release=platform.release(),
|
|
70
|
+
machine=platform.machine(),
|
|
71
|
+
python_version=platform.python_version(),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
return DoctorReport(
|
|
75
|
+
cortexshift_version=__version__,
|
|
76
|
+
python_version=platform.python_version(),
|
|
77
|
+
platform=platform_info,
|
|
78
|
+
timestamp=datetime.now(UTC),
|
|
79
|
+
providers=provider_diagnostics,
|
|
80
|
+
)
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"""Deterministic construction of canonical handoff payloads from durable local state.
|
|
2
|
+
|
|
3
|
+
The builder is pure: it performs no I/O, invokes no provider, and consumes no model
|
|
4
|
+
quota. Its only inputs are the canonical Project, the canonical Task, previous
|
|
5
|
+
CortexShift Session metadata, a live repository inspection, and the identifier of the
|
|
6
|
+
Git snapshot persisted at handoff time.
|
|
7
|
+
|
|
8
|
+
This is the mechanism that makes CortexShift work after the outgoing agent is already
|
|
9
|
+
gone: nothing here requires the outgoing provider to be installed, running, or able to
|
|
10
|
+
answer a question.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from collections.abc import Iterable
|
|
14
|
+
|
|
15
|
+
from cortexshift.domain.checkpoint import CheckpointRecord
|
|
16
|
+
from cortexshift.domain.git import RepositoryInspection, RepositoryInspectionStatus
|
|
17
|
+
from cortexshift.domain.handoff import (
|
|
18
|
+
HANDOFF_PROTOCOL_VERSION,
|
|
19
|
+
MAX_OPERATOR_NOTE_CHARS,
|
|
20
|
+
HandoffGitState,
|
|
21
|
+
HandoffPayload,
|
|
22
|
+
HandoffSourceSession,
|
|
23
|
+
HandoffTestStatus,
|
|
24
|
+
)
|
|
25
|
+
from cortexshift.domain.identifiers import utc_now
|
|
26
|
+
from cortexshift.domain.project import Project
|
|
27
|
+
from cortexshift.domain.provider import ProviderId
|
|
28
|
+
from cortexshift.domain.session import Session
|
|
29
|
+
from cortexshift.domain.task import Task
|
|
30
|
+
|
|
31
|
+
# Emitted only when the task's entire checkpoint history holds no structured decision.
|
|
32
|
+
# CortexShift encodes a genuine absence honestly rather than inferring facts it cannot
|
|
33
|
+
# support — and, equally, never claims absence over decisions it actually holds.
|
|
34
|
+
UNKNOWN_DECISIONS_STATEMENT = "No structured decisions are recorded in CortexShift state."
|
|
35
|
+
UNKNOWN_TEST_STATUS_STATEMENT = (
|
|
36
|
+
"No verified test result is recorded in CortexShift state. "
|
|
37
|
+
"The receiving agent must run relevant tests before relying on previous claims."
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
_GIT_NOTE_READY = (
|
|
41
|
+
"Live Git inspection succeeded at handoff time. "
|
|
42
|
+
"This is a historical observation, not current truth; re-check with `git status`."
|
|
43
|
+
)
|
|
44
|
+
_GIT_NOTE_NOT_INSTALLED = (
|
|
45
|
+
"Git was not found on this machine at handoff time. No repository state could be observed."
|
|
46
|
+
)
|
|
47
|
+
_GIT_NOTE_NOT_REPOSITORY = (
|
|
48
|
+
"This CortexShift project is not inside a Git repository. "
|
|
49
|
+
"No repository state could be observed."
|
|
50
|
+
)
|
|
51
|
+
_GIT_NOTE_PROBE_ERROR = (
|
|
52
|
+
"Git repository inspection failed at handoff time. "
|
|
53
|
+
"Treat any repository claim below as unverified and inspect the workspace directly."
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
_GIT_NOTES: dict[RepositoryInspectionStatus, str] = {
|
|
57
|
+
RepositoryInspectionStatus.READY: _GIT_NOTE_READY,
|
|
58
|
+
RepositoryInspectionStatus.GIT_NOT_INSTALLED: _GIT_NOTE_NOT_INSTALLED,
|
|
59
|
+
RepositoryInspectionStatus.NOT_GIT_REPOSITORY: _GIT_NOTE_NOT_REPOSITORY,
|
|
60
|
+
RepositoryInspectionStatus.PROBE_ERROR: _GIT_NOTE_PROBE_ERROR,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def aggregate_task_decisions(
|
|
65
|
+
checkpoints: Iterable[CheckpointRecord],
|
|
66
|
+
task_id: str,
|
|
67
|
+
) -> list[str]:
|
|
68
|
+
"""Aggregate the structured decisions recorded across one task's checkpoint history.
|
|
69
|
+
|
|
70
|
+
A checkpoint is an immutable point-in-time observation and is never rewritten, so a
|
|
71
|
+
decision recorded by `record_decision` lives only in the checkpoint that minted it.
|
|
72
|
+
Task-level durability is therefore reconstructed here, at handoff time, by reading the
|
|
73
|
+
task's checkpoint history instead of only its newest checkpoint.
|
|
74
|
+
|
|
75
|
+
Semantics:
|
|
76
|
+
- **Chronological first-seen order.** Callers supply checkpoints oldest first; the
|
|
77
|
+
resulting order follows the order in which each decision first entered state.
|
|
78
|
+
- **Exact-equality de-duplication.** A decision string repeated across checkpoints is
|
|
79
|
+
emitted once, at its first occurrence. Text is never normalised, trimmed for
|
|
80
|
+
comparison, or fuzzy-matched — two decisions differing by a single character stay
|
|
81
|
+
distinct.
|
|
82
|
+
- **Task isolation.** Records whose `task_id` does not match are dropped, so a store
|
|
83
|
+
query that ever widened its scope still could not leak another task's decisions.
|
|
84
|
+
|
|
85
|
+
This function is pure: it performs no I/O and mutates nothing it is given.
|
|
86
|
+
"""
|
|
87
|
+
aggregated: list[str] = []
|
|
88
|
+
seen: set[str] = set()
|
|
89
|
+
for record in checkpoints:
|
|
90
|
+
if record.task_id != task_id:
|
|
91
|
+
continue
|
|
92
|
+
for decision in record.payload.decisions:
|
|
93
|
+
if not decision.strip():
|
|
94
|
+
continue
|
|
95
|
+
if decision in seen:
|
|
96
|
+
continue
|
|
97
|
+
seen.add(decision)
|
|
98
|
+
aggregated.append(decision)
|
|
99
|
+
return aggregated
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def derive_files_touched(inspection: RepositoryInspection) -> list[str]:
|
|
103
|
+
"""Derive the FILES TOUCHED list from live Git inspection.
|
|
104
|
+
|
|
105
|
+
Deduplicates the union of staged, modified, untracked, and conflicted paths while
|
|
106
|
+
preserving a deterministic order. Source files are never opened to populate this
|
|
107
|
+
field, and full diffs are never read or stored.
|
|
108
|
+
"""
|
|
109
|
+
snapshot = inspection.snapshot
|
|
110
|
+
if snapshot is None:
|
|
111
|
+
return []
|
|
112
|
+
|
|
113
|
+
ordered: list[str] = []
|
|
114
|
+
seen: set[str] = set()
|
|
115
|
+
for group in (
|
|
116
|
+
snapshot.staged_files,
|
|
117
|
+
snapshot.modified_files,
|
|
118
|
+
snapshot.untracked_files,
|
|
119
|
+
snapshot.conflicted_files,
|
|
120
|
+
):
|
|
121
|
+
for path in group:
|
|
122
|
+
if path not in seen:
|
|
123
|
+
seen.add(path)
|
|
124
|
+
ordered.append(path)
|
|
125
|
+
return ordered
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def build_git_state(
|
|
129
|
+
inspection: RepositoryInspection,
|
|
130
|
+
snapshot_id: str | None = None,
|
|
131
|
+
) -> HandoffGitState:
|
|
132
|
+
"""Build the canonical GIT STATE section, honestly encoding unavailable Git."""
|
|
133
|
+
note = _GIT_NOTES.get(inspection.status, _GIT_NOTE_PROBE_ERROR)
|
|
134
|
+
snapshot = inspection.snapshot
|
|
135
|
+
|
|
136
|
+
if inspection.status != RepositoryInspectionStatus.READY or snapshot is None:
|
|
137
|
+
return HandoffGitState(status=inspection.status, available=False, note=note)
|
|
138
|
+
|
|
139
|
+
return HandoffGitState(
|
|
140
|
+
status=inspection.status,
|
|
141
|
+
available=True,
|
|
142
|
+
note=note,
|
|
143
|
+
branch=snapshot.branch,
|
|
144
|
+
head_sha=snapshot.head_sha,
|
|
145
|
+
detached_head=snapshot.detached_head,
|
|
146
|
+
dirty=snapshot.dirty,
|
|
147
|
+
staged_count=len(snapshot.staged_files),
|
|
148
|
+
modified_count=len(snapshot.modified_files),
|
|
149
|
+
untracked_count=len(snapshot.untracked_files),
|
|
150
|
+
conflicted_count=len(snapshot.conflicted_files),
|
|
151
|
+
working_tree_diff_summary=snapshot.working_tree_diff_summary,
|
|
152
|
+
staged_diff_summary=snapshot.staged_diff_summary,
|
|
153
|
+
snapshot_id=snapshot_id,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def derive_recommended_next_action(task: Task) -> str:
|
|
158
|
+
"""Derive the RECOMMENDED NEXT ACTION deterministically from canonical task state.
|
|
159
|
+
|
|
160
|
+
Priority: current work, then the first remaining item, then repository inspection.
|
|
161
|
+
No model call is ever used to produce this recommendation.
|
|
162
|
+
"""
|
|
163
|
+
current = (task.current_work or "").strip()
|
|
164
|
+
if current:
|
|
165
|
+
return f"Continue the work already in progress: {current}"
|
|
166
|
+
|
|
167
|
+
for item in task.remaining_items:
|
|
168
|
+
candidate = item.strip()
|
|
169
|
+
if candidate:
|
|
170
|
+
return f"Start the next recorded remaining item: {candidate}"
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
"Inspect the current repository state (git status, changed files, and the "
|
|
174
|
+
"project's relevant tests) and determine the next incomplete step toward the "
|
|
175
|
+
"original objective."
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def bound_operator_note(note: str | None) -> str | None:
|
|
180
|
+
"""Bound an optional operator note so it can never make the handoff unbounded."""
|
|
181
|
+
if note is None:
|
|
182
|
+
return None
|
|
183
|
+
cleaned = note.strip()
|
|
184
|
+
if not cleaned:
|
|
185
|
+
return None
|
|
186
|
+
if len(cleaned) > MAX_OPERATOR_NOTE_CHARS:
|
|
187
|
+
omitted = len(cleaned) - MAX_OPERATOR_NOTE_CHARS
|
|
188
|
+
return f"{cleaned[:MAX_OPERATOR_NOTE_CHARS]}… [{omitted} characters omitted]"
|
|
189
|
+
return cleaned
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class HandoffBuilder:
|
|
193
|
+
"""Builds canonical handoff payloads deterministically from durable state."""
|
|
194
|
+
|
|
195
|
+
def build(
|
|
196
|
+
self,
|
|
197
|
+
project: Project,
|
|
198
|
+
task: Task,
|
|
199
|
+
source_session: Session,
|
|
200
|
+
inspection: RepositoryInspection,
|
|
201
|
+
target_provider_id: ProviderId,
|
|
202
|
+
snapshot_id: str | None = None,
|
|
203
|
+
operator_note: str | None = None,
|
|
204
|
+
latest_checkpoint: CheckpointRecord | None = None,
|
|
205
|
+
task_decisions: list[str] | None = None,
|
|
206
|
+
) -> HandoffPayload:
|
|
207
|
+
"""Build the canonical handoff payload for a task moving to another provider.
|
|
208
|
+
|
|
209
|
+
`task_decisions` carries the decisions aggregated across the task's whole
|
|
210
|
+
checkpoint history (see `aggregate_task_decisions`) and is authoritative when
|
|
211
|
+
supplied — including when it is empty, which asserts that the task genuinely has
|
|
212
|
+
no recorded decision. `latest_checkpoint` remains the provenance reference for the
|
|
213
|
+
snapshot and reported test status; it is used as the decision source only when no
|
|
214
|
+
aggregate was supplied at all.
|
|
215
|
+
"""
|
|
216
|
+
important_decisions: list[str] = []
|
|
217
|
+
decisions_known: bool = False
|
|
218
|
+
if task_decisions is not None:
|
|
219
|
+
important_decisions = list(task_decisions)
|
|
220
|
+
decisions_known = bool(important_decisions)
|
|
221
|
+
elif latest_checkpoint is not None and latest_checkpoint.payload.decisions:
|
|
222
|
+
important_decisions = list(latest_checkpoint.payload.decisions)
|
|
223
|
+
decisions_known = True
|
|
224
|
+
|
|
225
|
+
test_status: HandoffTestStatus
|
|
226
|
+
if latest_checkpoint is not None and latest_checkpoint.payload.test_status.known:
|
|
227
|
+
test_status = HandoffTestStatus(
|
|
228
|
+
known=True,
|
|
229
|
+
summary=(
|
|
230
|
+
f"Checkpoint-reported test status:\n"
|
|
231
|
+
f"{latest_checkpoint.payload.test_status.summary}\n\n"
|
|
232
|
+
f"This result was not independently verified by CortexShift. "
|
|
233
|
+
f"Re-run relevant tests before relying on it."
|
|
234
|
+
),
|
|
235
|
+
)
|
|
236
|
+
else:
|
|
237
|
+
test_status = HandoffTestStatus(known=False, summary=UNKNOWN_TEST_STATUS_STATEMENT)
|
|
238
|
+
|
|
239
|
+
source_checkpoint_id = latest_checkpoint.id if latest_checkpoint else None
|
|
240
|
+
source_checkpoint_kind = latest_checkpoint.kind.value if latest_checkpoint else None
|
|
241
|
+
source_checkpoint_created_at = latest_checkpoint.created_at if latest_checkpoint else None
|
|
242
|
+
|
|
243
|
+
return HandoffPayload(
|
|
244
|
+
protocol_version=HANDOFF_PROTOCOL_VERSION,
|
|
245
|
+
generated_at=utc_now(),
|
|
246
|
+
project_name=project.name,
|
|
247
|
+
project_root=project.repo_path,
|
|
248
|
+
task_id=task.id,
|
|
249
|
+
task_title=task.title,
|
|
250
|
+
task_status=task.status.value,
|
|
251
|
+
original_objective=task.objective,
|
|
252
|
+
requirements=list(task.requirements),
|
|
253
|
+
constraints=list(task.constraints),
|
|
254
|
+
completed=list(task.completed_items),
|
|
255
|
+
current_work=task.current_work,
|
|
256
|
+
remaining=list(task.remaining_items),
|
|
257
|
+
important_decisions=important_decisions,
|
|
258
|
+
decisions_known=decisions_known,
|
|
259
|
+
files_touched=derive_files_touched(inspection),
|
|
260
|
+
test_status=test_status,
|
|
261
|
+
known_issues=list(task.known_issues),
|
|
262
|
+
git_state=build_git_state(inspection, snapshot_id=snapshot_id),
|
|
263
|
+
# Completed work is the only signal CortexShift durably holds about what
|
|
264
|
+
# should not be rebuilt. It remains advisory and must still be verified.
|
|
265
|
+
do_not_redo=list(task.completed_items),
|
|
266
|
+
recommended_next_action=derive_recommended_next_action(task),
|
|
267
|
+
source_session=HandoffSourceSession(
|
|
268
|
+
session_id=source_session.id,
|
|
269
|
+
provider_id=source_session.provider_id,
|
|
270
|
+
status=source_session.status,
|
|
271
|
+
started_at=source_session.started_at,
|
|
272
|
+
ended_at=source_session.ended_at,
|
|
273
|
+
exit_reason=source_session.exit_reason,
|
|
274
|
+
exit_code=source_session.exit_code,
|
|
275
|
+
),
|
|
276
|
+
target_provider_id=target_provider_id,
|
|
277
|
+
operator_note=bound_operator_note(operator_note),
|
|
278
|
+
source_checkpoint_id=source_checkpoint_id,
|
|
279
|
+
source_checkpoint_kind=source_checkpoint_kind,
|
|
280
|
+
source_checkpoint_created_at=source_checkpoint_created_at,
|
|
281
|
+
)
|