taskledger 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 (67) hide show
  1. taskledger/__init__.py +5 -0
  2. taskledger/__main__.py +6 -0
  3. taskledger/_version.py +24 -0
  4. taskledger/api/__init__.py +13 -0
  5. taskledger/api/handoff.py +247 -0
  6. taskledger/api/introductions.py +9 -0
  7. taskledger/api/locks.py +4 -0
  8. taskledger/api/plans.py +31 -0
  9. taskledger/api/project.py +185 -0
  10. taskledger/api/questions.py +19 -0
  11. taskledger/api/search.py +87 -0
  12. taskledger/api/task_runs.py +38 -0
  13. taskledger/api/tasks.py +61 -0
  14. taskledger/cli.py +600 -0
  15. taskledger/cli_actor.py +196 -0
  16. taskledger/cli_common.py +617 -0
  17. taskledger/cli_implement.py +409 -0
  18. taskledger/cli_migrate.py +328 -0
  19. taskledger/cli_misc.py +984 -0
  20. taskledger/cli_plan.py +478 -0
  21. taskledger/cli_question.py +350 -0
  22. taskledger/cli_task.py +257 -0
  23. taskledger/cli_validate.py +285 -0
  24. taskledger/command_inventory.py +125 -0
  25. taskledger/domain/__init__.py +2 -0
  26. taskledger/domain/models.py +1697 -0
  27. taskledger/domain/policies.py +542 -0
  28. taskledger/domain/states.py +320 -0
  29. taskledger/errors.py +165 -0
  30. taskledger/exchange.py +343 -0
  31. taskledger/ids.py +19 -0
  32. taskledger/py.typed +0 -0
  33. taskledger/search.py +349 -0
  34. taskledger/services/__init__.py +1 -0
  35. taskledger/services/actors.py +245 -0
  36. taskledger/services/dashboard.py +306 -0
  37. taskledger/services/doctor.py +435 -0
  38. taskledger/services/handoff.py +1029 -0
  39. taskledger/services/handoff_lifecycle.py +154 -0
  40. taskledger/services/navigation.py +930 -0
  41. taskledger/services/phase5_lock_transfer.py +96 -0
  42. taskledger/services/plan_lint.py +397 -0
  43. taskledger/services/serve_read_model.py +852 -0
  44. taskledger/services/tasks.py +4224 -0
  45. taskledger/services/validation.py +221 -0
  46. taskledger/services/web_dashboard.py +1742 -0
  47. taskledger/storage/__init__.py +39 -0
  48. taskledger/storage/atomic.py +57 -0
  49. taskledger/storage/common.py +90 -0
  50. taskledger/storage/events.py +98 -0
  51. taskledger/storage/frontmatter.py +57 -0
  52. taskledger/storage/indexes.py +42 -0
  53. taskledger/storage/init.py +187 -0
  54. taskledger/storage/locks.py +83 -0
  55. taskledger/storage/meta.py +103 -0
  56. taskledger/storage/migrations.py +207 -0
  57. taskledger/storage/paths.py +166 -0
  58. taskledger/storage/project_config.py +393 -0
  59. taskledger/storage/repos.py +256 -0
  60. taskledger/storage/task_store.py +836 -0
  61. taskledger/timeutils.py +7 -0
  62. taskledger-0.1.0.dist-info/METADATA +411 -0
  63. taskledger-0.1.0.dist-info/RECORD +67 -0
  64. taskledger-0.1.0.dist-info/WHEEL +5 -0
  65. taskledger-0.1.0.dist-info/entry_points.txt +2 -0
  66. taskledger-0.1.0.dist-info/licenses/LICENSE +201 -0
  67. taskledger-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,154 @@
1
+ """Handoff lifecycle operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import replace
6
+ from pathlib import Path
7
+
8
+ from taskledger.domain.models import ActorRef, HarnessRef, TaskHandoffRecord
9
+ from taskledger.errors import LaunchError
10
+ from taskledger.services.actors import resolve_actor, resolve_harness
11
+ from taskledger.storage.task_store import (
12
+ resolve_handoff,
13
+ resolve_lock,
14
+ resolve_task,
15
+ save_handoff,
16
+ )
17
+ from taskledger.timeutils import utc_now_iso
18
+
19
+
20
+ def claim_handoff(
21
+ workspace_root: Path,
22
+ task_id: str,
23
+ handoff_id: str,
24
+ *,
25
+ actor: ActorRef | None = None,
26
+ harness: HarnessRef | None = None,
27
+ new_run_id: str | None = None,
28
+ ) -> TaskHandoffRecord:
29
+ """Claim a handoff, transitioning from 'open' to 'claimed'."""
30
+ handoff = resolve_handoff(workspace_root, task_id, handoff_id)
31
+
32
+ if handoff.status != "open":
33
+ raise LaunchError(f"Cannot claim handoff in status {handoff.status}")
34
+
35
+ resolved_actor = actor or resolve_actor()
36
+ resolved_harness = harness or resolve_harness()
37
+
38
+ # Check intent match if specified
39
+ if (
40
+ handoff.intended_actor_type
41
+ and handoff.intended_actor_type != resolved_actor.actor_type
42
+ ):
43
+ raise LaunchError(
44
+ f"Actor type mismatch: handoff intended for {handoff.intended_actor_type}, "
45
+ f"but claiming as {resolved_actor.actor_type}"
46
+ )
47
+ if (
48
+ handoff.intended_actor_name
49
+ and handoff.intended_actor_name != resolved_actor.actor_name
50
+ ):
51
+ raise LaunchError(
52
+ f"Actor name mismatch: handoff intended for {handoff.intended_actor_name}, "
53
+ f"but claiming as {resolved_actor.actor_name}"
54
+ )
55
+ if (
56
+ handoff.intended_harness
57
+ and handoff.intended_harness != "any"
58
+ and handoff.intended_harness != resolved_harness.name
59
+ ):
60
+ raise LaunchError(
61
+ f"Harness mismatch: handoff intended for {handoff.intended_harness}, "
62
+ f"but claiming in {resolved_harness.name}"
63
+ )
64
+
65
+ # Create new handoff with claim info
66
+ released_lock_id = handoff.released_lock_id
67
+ updated = replace(
68
+ handoff,
69
+ status="claimed",
70
+ claim_run_id=new_run_id,
71
+ released_lock_id=released_lock_id,
72
+ claimed_at=utc_now_iso(),
73
+ claimed_by=resolved_actor,
74
+ claimed_in_harness=resolved_harness,
75
+ )
76
+
77
+ # Handle lock transfer if applicable
78
+ if handoff.lock_policy == "transfer" and handoff.source_run_id:
79
+ task = resolve_task(workspace_root, task_id)
80
+ lock = resolve_lock(workspace_root, task.id)
81
+ if lock and lock.run_id == handoff.source_run_id:
82
+ from taskledger.services.phase5_lock_transfer import transfer_lock
83
+
84
+ transfer_lock(
85
+ workspace_root, task.id, lock.lock_id, resolved_actor, resolved_harness
86
+ )
87
+ released_lock_id = lock.lock_id
88
+ updated = replace(updated, released_lock_id=released_lock_id)
89
+ elif handoff.lock_policy == "release" and handoff.source_run_id:
90
+ task = resolve_task(workspace_root, task_id)
91
+ lock = resolve_lock(workspace_root, task.id)
92
+ if lock and lock.run_id == handoff.source_run_id:
93
+ from taskledger.services.phase5_lock_transfer import release_lock
94
+
95
+ release_lock(workspace_root, task.id, lock.lock_id)
96
+ released_lock_id = lock.lock_id
97
+ updated = replace(updated, released_lock_id=released_lock_id)
98
+
99
+ save_handoff(workspace_root, updated)
100
+ return updated
101
+
102
+
103
+ def close_handoff(
104
+ workspace_root: Path,
105
+ task_id: str,
106
+ handoff_id: str,
107
+ *,
108
+ actor: ActorRef | None = None,
109
+ reason: str | None = None,
110
+ ) -> TaskHandoffRecord:
111
+ """Close a handoff, transitioning from 'claimed' to 'closed'."""
112
+ handoff = resolve_handoff(workspace_root, task_id, handoff_id)
113
+
114
+ if handoff.status not in ("open", "claimed"):
115
+ raise LaunchError(f"Cannot close handoff in status {handoff.status}")
116
+
117
+ resolved_actor = actor or resolve_actor()
118
+ _ = resolved_actor # used for actor resolution side-effect
119
+
120
+ updated = replace(
121
+ handoff,
122
+ status="closed",
123
+ summary=reason or handoff.summary,
124
+ )
125
+
126
+ save_handoff(workspace_root, updated)
127
+ return updated
128
+
129
+
130
+ def cancel_handoff(
131
+ workspace_root: Path,
132
+ task_id: str,
133
+ handoff_id: str,
134
+ *,
135
+ actor: ActorRef | None = None,
136
+ reason: str | None = None,
137
+ ) -> TaskHandoffRecord:
138
+ """Cancel a handoff, transitioning from 'open' to 'cancelled'."""
139
+ handoff = resolve_handoff(workspace_root, task_id, handoff_id)
140
+
141
+ if handoff.status != "open":
142
+ raise LaunchError(f"Cannot cancel handoff in status {handoff.status}")
143
+
144
+ resolved_actor = actor or resolve_actor()
145
+ _ = resolved_actor # used for actor resolution side-effect
146
+
147
+ updated = replace(
148
+ handoff,
149
+ status="cancelled",
150
+ summary=reason or handoff.summary,
151
+ )
152
+
153
+ save_handoff(workspace_root, updated)
154
+ return updated