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.
- taskledger/__init__.py +5 -0
- taskledger/__main__.py +6 -0
- taskledger/_version.py +24 -0
- taskledger/api/__init__.py +13 -0
- taskledger/api/handoff.py +247 -0
- taskledger/api/introductions.py +9 -0
- taskledger/api/locks.py +4 -0
- taskledger/api/plans.py +31 -0
- taskledger/api/project.py +185 -0
- taskledger/api/questions.py +19 -0
- taskledger/api/search.py +87 -0
- taskledger/api/task_runs.py +38 -0
- taskledger/api/tasks.py +61 -0
- taskledger/cli.py +600 -0
- taskledger/cli_actor.py +196 -0
- taskledger/cli_common.py +617 -0
- taskledger/cli_implement.py +409 -0
- taskledger/cli_migrate.py +328 -0
- taskledger/cli_misc.py +984 -0
- taskledger/cli_plan.py +478 -0
- taskledger/cli_question.py +350 -0
- taskledger/cli_task.py +257 -0
- taskledger/cli_validate.py +285 -0
- taskledger/command_inventory.py +125 -0
- taskledger/domain/__init__.py +2 -0
- taskledger/domain/models.py +1697 -0
- taskledger/domain/policies.py +542 -0
- taskledger/domain/states.py +320 -0
- taskledger/errors.py +165 -0
- taskledger/exchange.py +343 -0
- taskledger/ids.py +19 -0
- taskledger/py.typed +0 -0
- taskledger/search.py +349 -0
- taskledger/services/__init__.py +1 -0
- taskledger/services/actors.py +245 -0
- taskledger/services/dashboard.py +306 -0
- taskledger/services/doctor.py +435 -0
- taskledger/services/handoff.py +1029 -0
- taskledger/services/handoff_lifecycle.py +154 -0
- taskledger/services/navigation.py +930 -0
- taskledger/services/phase5_lock_transfer.py +96 -0
- taskledger/services/plan_lint.py +397 -0
- taskledger/services/serve_read_model.py +852 -0
- taskledger/services/tasks.py +4224 -0
- taskledger/services/validation.py +221 -0
- taskledger/services/web_dashboard.py +1742 -0
- taskledger/storage/__init__.py +39 -0
- taskledger/storage/atomic.py +57 -0
- taskledger/storage/common.py +90 -0
- taskledger/storage/events.py +98 -0
- taskledger/storage/frontmatter.py +57 -0
- taskledger/storage/indexes.py +42 -0
- taskledger/storage/init.py +187 -0
- taskledger/storage/locks.py +83 -0
- taskledger/storage/meta.py +103 -0
- taskledger/storage/migrations.py +207 -0
- taskledger/storage/paths.py +166 -0
- taskledger/storage/project_config.py +393 -0
- taskledger/storage/repos.py +256 -0
- taskledger/storage/task_store.py +836 -0
- taskledger/timeutils.py +7 -0
- taskledger-0.1.0.dist-info/METADATA +411 -0
- taskledger-0.1.0.dist-info/RECORD +67 -0
- taskledger-0.1.0.dist-info/WHEEL +5 -0
- taskledger-0.1.0.dist-info/entry_points.txt +2 -0
- taskledger-0.1.0.dist-info/licenses/LICENSE +201 -0
- taskledger-0.1.0.dist-info/top_level.txt +1 -0
taskledger/__init__.py
ADDED
taskledger/__main__.py
ADDED
taskledger/_version.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.1.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import cast
|
|
4
|
+
|
|
5
|
+
from taskledger.domain.models import ActorRef, HarnessRef, TaskHandoffRecord
|
|
6
|
+
from taskledger.domain.states import normalize_actor_type
|
|
7
|
+
from taskledger.errors import LaunchError
|
|
8
|
+
from taskledger.services.handoff import (
|
|
9
|
+
build_context_request,
|
|
10
|
+
build_handoff_payload,
|
|
11
|
+
render_handoff,
|
|
12
|
+
render_markdown_handoff,
|
|
13
|
+
)
|
|
14
|
+
from taskledger.storage.task_store import (
|
|
15
|
+
handoff_markdown_path,
|
|
16
|
+
list_handoffs,
|
|
17
|
+
resolve_handoff,
|
|
18
|
+
resolve_task,
|
|
19
|
+
resolve_v2_paths,
|
|
20
|
+
save_handoff,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def create_handoff(
|
|
25
|
+
workspace_root: Path,
|
|
26
|
+
task_ref: str,
|
|
27
|
+
*,
|
|
28
|
+
mode: str,
|
|
29
|
+
context_for: str | None = None,
|
|
30
|
+
scope: str | None = None,
|
|
31
|
+
todo_id: str | None = None,
|
|
32
|
+
focus_run_id: str | None = None,
|
|
33
|
+
intended_actor_type: str | None = None,
|
|
34
|
+
intended_actor_name: str | None = None,
|
|
35
|
+
intended_harness: str | None = None,
|
|
36
|
+
summary: str | None = None,
|
|
37
|
+
next_action: str | None = None,
|
|
38
|
+
actor: ActorRef | None = None,
|
|
39
|
+
harness: HarnessRef | None = None,
|
|
40
|
+
) -> dict[str, object]:
|
|
41
|
+
"""Create and save a handoff record."""
|
|
42
|
+
from taskledger.ids import next_project_id
|
|
43
|
+
from taskledger.services.actors import resolve_actor, resolve_harness
|
|
44
|
+
from taskledger.timeutils import utc_now_iso
|
|
45
|
+
|
|
46
|
+
resolved_actor = actor or resolve_actor()
|
|
47
|
+
resolved_harness = harness or resolve_harness()
|
|
48
|
+
|
|
49
|
+
task = resolve_task(workspace_root, task_ref)
|
|
50
|
+
request = build_context_request(
|
|
51
|
+
mode=mode,
|
|
52
|
+
context_for=context_for,
|
|
53
|
+
scope=scope,
|
|
54
|
+
todo_id=todo_id,
|
|
55
|
+
focus_run_id=focus_run_id,
|
|
56
|
+
format_name="markdown",
|
|
57
|
+
)
|
|
58
|
+
payload = build_handoff_payload(
|
|
59
|
+
workspace_root,
|
|
60
|
+
task.id,
|
|
61
|
+
mode=request.mode,
|
|
62
|
+
context_for=request.context_for,
|
|
63
|
+
scope=request.scope,
|
|
64
|
+
todo_id=request.todo_id,
|
|
65
|
+
focus_run_id=request.focus_run_id,
|
|
66
|
+
format_name="markdown",
|
|
67
|
+
)
|
|
68
|
+
context_body = render_markdown_handoff(payload)
|
|
69
|
+
context_hash = f"sha256:{hashlib.sha256(context_body.encode('utf-8')).hexdigest()}"
|
|
70
|
+
existing_handoffs = list_handoffs(workspace_root, task.id)
|
|
71
|
+
existing_ids = [h.handoff_id for h in existing_handoffs]
|
|
72
|
+
|
|
73
|
+
handoff_id = next_project_id("handoff", existing_ids)
|
|
74
|
+
handoff = TaskHandoffRecord(
|
|
75
|
+
handoff_id=handoff_id,
|
|
76
|
+
task_id=task.id,
|
|
77
|
+
mode=request.mode,
|
|
78
|
+
context_for=request.context_for,
|
|
79
|
+
scope=request.scope,
|
|
80
|
+
todo_id=request.todo_id,
|
|
81
|
+
focus_run_id=request.focus_run_id,
|
|
82
|
+
context_format="markdown",
|
|
83
|
+
context_hash=context_hash,
|
|
84
|
+
generated_at=utc_now_iso(),
|
|
85
|
+
status="open",
|
|
86
|
+
created_by=resolved_actor,
|
|
87
|
+
created_from_harness=resolved_harness,
|
|
88
|
+
intended_actor_type=(
|
|
89
|
+
normalize_actor_type(intended_actor_type) if intended_actor_type else None
|
|
90
|
+
),
|
|
91
|
+
intended_actor_name=intended_actor_name,
|
|
92
|
+
intended_harness=intended_harness,
|
|
93
|
+
summary=summary,
|
|
94
|
+
next_action=next_action,
|
|
95
|
+
context_body=context_body,
|
|
96
|
+
)
|
|
97
|
+
path = save_handoff(workspace_root, handoff)
|
|
98
|
+
result = handoff.to_dict()
|
|
99
|
+
result.pop("context_body", None)
|
|
100
|
+
try:
|
|
101
|
+
result["context_path"] = str(path.relative_to(workspace_root))
|
|
102
|
+
except ValueError:
|
|
103
|
+
result["context_path"] = str(path)
|
|
104
|
+
return result
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def list_all_handoffs(workspace_root: Path, task_ref: str) -> list[dict[str, object]]:
|
|
108
|
+
"""List all handoffs for a task."""
|
|
109
|
+
task = resolve_task(workspace_root, task_ref)
|
|
110
|
+
handoffs = list_handoffs(workspace_root, task.id)
|
|
111
|
+
return [h.to_dict() for h in handoffs]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def show_handoff(
|
|
115
|
+
workspace_root: Path,
|
|
116
|
+
task_ref: str,
|
|
117
|
+
handoff_ref: str,
|
|
118
|
+
*,
|
|
119
|
+
format_name: str = "text",
|
|
120
|
+
) -> str | dict[str, object]:
|
|
121
|
+
"""Get a specific handoff."""
|
|
122
|
+
task = resolve_task(workspace_root, task_ref)
|
|
123
|
+
handoff = resolve_handoff(workspace_root, task.id, handoff_ref)
|
|
124
|
+
path = handoff_markdown_path(
|
|
125
|
+
resolve_v2_paths(workspace_root), task.id, handoff.handoff_id
|
|
126
|
+
)
|
|
127
|
+
context_path: str
|
|
128
|
+
try:
|
|
129
|
+
context_path = str(path.relative_to(workspace_root))
|
|
130
|
+
except ValueError:
|
|
131
|
+
context_path = str(path)
|
|
132
|
+
if format_name == "markdown":
|
|
133
|
+
if handoff.context_body.strip():
|
|
134
|
+
body = handoff.context_body
|
|
135
|
+
return body if body.endswith("\n") else body + "\n"
|
|
136
|
+
return cast(
|
|
137
|
+
str,
|
|
138
|
+
render_handoff(
|
|
139
|
+
workspace_root,
|
|
140
|
+
task.id,
|
|
141
|
+
mode=handoff.mode,
|
|
142
|
+
context_for=handoff.context_for,
|
|
143
|
+
scope=handoff.scope,
|
|
144
|
+
todo_id=handoff.todo_id,
|
|
145
|
+
focus_run_id=handoff.focus_run_id,
|
|
146
|
+
format_name="markdown",
|
|
147
|
+
),
|
|
148
|
+
)
|
|
149
|
+
payload = handoff.to_dict()
|
|
150
|
+
payload["context_path"] = context_path
|
|
151
|
+
if format_name == "json":
|
|
152
|
+
return payload
|
|
153
|
+
if format_name != "text":
|
|
154
|
+
raise LaunchError(f"Unsupported handoff format: {format_name}")
|
|
155
|
+
lines = [
|
|
156
|
+
f"Handoff {handoff.handoff_id}",
|
|
157
|
+
f"mode: {handoff.mode}",
|
|
158
|
+
f"context_for: {handoff.context_for or 'none'}",
|
|
159
|
+
f"scope: {handoff.scope}",
|
|
160
|
+
f"status: {handoff.status}",
|
|
161
|
+
f"context_path: {context_path}",
|
|
162
|
+
f"context_hash: {handoff.context_hash or 'none'}",
|
|
163
|
+
]
|
|
164
|
+
if handoff.todo_id:
|
|
165
|
+
lines.append(f"todo_id: {handoff.todo_id}")
|
|
166
|
+
if handoff.focus_run_id:
|
|
167
|
+
lines.append(f"focus_run_id: {handoff.focus_run_id}")
|
|
168
|
+
return "\n".join(lines)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def claim_handoff_api(
|
|
172
|
+
workspace_root: Path,
|
|
173
|
+
task_ref: str,
|
|
174
|
+
handoff_ref: str,
|
|
175
|
+
*,
|
|
176
|
+
actor: ActorRef | None = None,
|
|
177
|
+
harness: HarnessRef | None = None,
|
|
178
|
+
new_run_id: str | None = None,
|
|
179
|
+
) -> dict[str, object]:
|
|
180
|
+
"""Claim a handoff, transitioning from 'open' to 'claimed'."""
|
|
181
|
+
from taskledger.services.handoff_lifecycle import claim_handoff
|
|
182
|
+
|
|
183
|
+
task = resolve_task(workspace_root, task_ref)
|
|
184
|
+
handoff = claim_handoff(
|
|
185
|
+
workspace_root,
|
|
186
|
+
task.id,
|
|
187
|
+
handoff_ref,
|
|
188
|
+
actor=actor,
|
|
189
|
+
harness=harness,
|
|
190
|
+
new_run_id=new_run_id,
|
|
191
|
+
)
|
|
192
|
+
return handoff.to_dict()
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def close_handoff_api(
|
|
196
|
+
workspace_root: Path,
|
|
197
|
+
task_ref: str,
|
|
198
|
+
handoff_ref: str,
|
|
199
|
+
*,
|
|
200
|
+
actor: ActorRef | None = None,
|
|
201
|
+
reason: str | None = None,
|
|
202
|
+
) -> dict[str, object]:
|
|
203
|
+
"""Close a handoff, transitioning from 'claimed' to 'closed'."""
|
|
204
|
+
from taskledger.services.handoff_lifecycle import close_handoff
|
|
205
|
+
|
|
206
|
+
task = resolve_task(workspace_root, task_ref)
|
|
207
|
+
handoff = close_handoff(
|
|
208
|
+
workspace_root,
|
|
209
|
+
task.id,
|
|
210
|
+
handoff_ref,
|
|
211
|
+
actor=actor,
|
|
212
|
+
reason=reason,
|
|
213
|
+
)
|
|
214
|
+
return handoff.to_dict()
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def cancel_handoff_api(
|
|
218
|
+
workspace_root: Path,
|
|
219
|
+
task_ref: str,
|
|
220
|
+
handoff_ref: str,
|
|
221
|
+
*,
|
|
222
|
+
actor: ActorRef | None = None,
|
|
223
|
+
reason: str | None = None,
|
|
224
|
+
) -> dict[str, object]:
|
|
225
|
+
"""Cancel a handoff, transitioning from 'open' to 'cancelled'."""
|
|
226
|
+
from taskledger.services.handoff_lifecycle import cancel_handoff
|
|
227
|
+
|
|
228
|
+
task = resolve_task(workspace_root, task_ref)
|
|
229
|
+
handoff = cancel_handoff(
|
|
230
|
+
workspace_root,
|
|
231
|
+
task.id,
|
|
232
|
+
handoff_ref,
|
|
233
|
+
actor=actor,
|
|
234
|
+
reason=reason,
|
|
235
|
+
)
|
|
236
|
+
return handoff.to_dict()
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
__all__ = [
|
|
240
|
+
"render_handoff",
|
|
241
|
+
"create_handoff",
|
|
242
|
+
"list_all_handoffs",
|
|
243
|
+
"show_handoff",
|
|
244
|
+
"claim_handoff_api",
|
|
245
|
+
"close_handoff_api",
|
|
246
|
+
"cancel_handoff_api",
|
|
247
|
+
]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from taskledger.services.tasks import create_introduction, link_introduction
|
|
2
|
+
from taskledger.storage.task_store import list_introductions, resolve_introduction
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"create_introduction",
|
|
6
|
+
"list_introductions",
|
|
7
|
+
"resolve_introduction",
|
|
8
|
+
"link_introduction",
|
|
9
|
+
]
|
taskledger/api/locks.py
ADDED
taskledger/api/plans.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from taskledger.services.plan_lint import lint_plan
|
|
2
|
+
from taskledger.services.tasks import (
|
|
3
|
+
approve_plan,
|
|
4
|
+
diff_plan,
|
|
5
|
+
list_plan_versions,
|
|
6
|
+
materialize_plan_todos,
|
|
7
|
+
propose_plan,
|
|
8
|
+
regenerate_plan_from_answers,
|
|
9
|
+
reject_plan,
|
|
10
|
+
revise_plan,
|
|
11
|
+
run_planning_command,
|
|
12
|
+
show_plan,
|
|
13
|
+
start_planning,
|
|
14
|
+
upsert_plan,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"start_planning",
|
|
19
|
+
"propose_plan",
|
|
20
|
+
"upsert_plan",
|
|
21
|
+
"list_plan_versions",
|
|
22
|
+
"show_plan",
|
|
23
|
+
"diff_plan",
|
|
24
|
+
"approve_plan",
|
|
25
|
+
"regenerate_plan_from_answers",
|
|
26
|
+
"materialize_plan_todos",
|
|
27
|
+
"reject_plan",
|
|
28
|
+
"revise_plan",
|
|
29
|
+
"run_planning_command",
|
|
30
|
+
"lint_plan",
|
|
31
|
+
]
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import cast
|
|
5
|
+
|
|
6
|
+
from taskledger.domain.policies import derive_active_stage
|
|
7
|
+
from taskledger.exchange import (
|
|
8
|
+
export_project_payload,
|
|
9
|
+
import_project_payload,
|
|
10
|
+
parse_project_import_payload,
|
|
11
|
+
write_project_snapshot,
|
|
12
|
+
)
|
|
13
|
+
from taskledger.services.doctor import inspect_v2_project
|
|
14
|
+
from taskledger.storage.init import init_project_state
|
|
15
|
+
from taskledger.storage.locks import lock_is_expired
|
|
16
|
+
from taskledger.storage.paths import resolve_project_paths
|
|
17
|
+
from taskledger.storage.task_store import (
|
|
18
|
+
list_changes,
|
|
19
|
+
list_introductions,
|
|
20
|
+
list_plans,
|
|
21
|
+
list_questions,
|
|
22
|
+
list_runs,
|
|
23
|
+
list_tasks,
|
|
24
|
+
load_active_locks,
|
|
25
|
+
load_active_task_state,
|
|
26
|
+
resolve_task,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def init_project(
|
|
31
|
+
workspace_root: Path,
|
|
32
|
+
*,
|
|
33
|
+
taskledger_dir: Path | None = None,
|
|
34
|
+
) -> dict[str, object]:
|
|
35
|
+
paths, created = init_project_state(workspace_root, taskledger_dir=taskledger_dir)
|
|
36
|
+
return {
|
|
37
|
+
"kind": "taskledger_init",
|
|
38
|
+
"root": str(paths.project_dir),
|
|
39
|
+
"project_dir": str(paths.project_dir),
|
|
40
|
+
"workspace_root": str(paths.workspace_root),
|
|
41
|
+
"config_path": str(paths.config_path),
|
|
42
|
+
"taskledger_dir": str(paths.taskledger_dir),
|
|
43
|
+
"created": created,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def project_status_summary(workspace_root: Path) -> dict[str, object]:
|
|
48
|
+
doctor = inspect_v2_project(workspace_root)
|
|
49
|
+
paths = resolve_project_paths(workspace_root)
|
|
50
|
+
return {
|
|
51
|
+
"kind": "taskledger_status",
|
|
52
|
+
"workspace_root": str(paths.workspace_root),
|
|
53
|
+
"config_path": str(paths.config_path),
|
|
54
|
+
"taskledger_dir": str(paths.taskledger_dir),
|
|
55
|
+
"project_dir": str(paths.project_dir),
|
|
56
|
+
"counts": _project_counts(workspace_root),
|
|
57
|
+
"healthy": bool(doctor["healthy"]),
|
|
58
|
+
"active_task": _active_task_status(workspace_root),
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def project_status(workspace_root: Path) -> dict[str, object]:
|
|
63
|
+
doctor = inspect_v2_project(workspace_root)
|
|
64
|
+
tasks = list_tasks(workspace_root)
|
|
65
|
+
locks = load_active_locks(workspace_root)
|
|
66
|
+
paths = resolve_project_paths(workspace_root)
|
|
67
|
+
return {
|
|
68
|
+
"kind": "taskledger_status",
|
|
69
|
+
"workspace_root": str(paths.workspace_root),
|
|
70
|
+
"config_path": str(paths.config_path),
|
|
71
|
+
"taskledger_dir": str(paths.taskledger_dir),
|
|
72
|
+
"project_dir": str(paths.project_dir),
|
|
73
|
+
"counts": _project_counts(workspace_root),
|
|
74
|
+
"healthy": bool(doctor["healthy"]),
|
|
75
|
+
"active_task": _active_task_status(workspace_root),
|
|
76
|
+
"errors": list(cast(list[object], doctor["errors"])),
|
|
77
|
+
"warnings": list(cast(list[object], doctor["warnings"])),
|
|
78
|
+
"repair_hints": list(cast(list[object], doctor["repair_hints"])),
|
|
79
|
+
"tasks": [
|
|
80
|
+
{
|
|
81
|
+
"id": task.id,
|
|
82
|
+
"slug": task.slug,
|
|
83
|
+
"title": task.title,
|
|
84
|
+
"status": task.status_stage,
|
|
85
|
+
"status_stage": task.status_stage,
|
|
86
|
+
"active_stage": derive_active_stage(
|
|
87
|
+
next(
|
|
88
|
+
(
|
|
89
|
+
lock
|
|
90
|
+
for lock in locks
|
|
91
|
+
if lock.task_id == task.id and not lock_is_expired(lock)
|
|
92
|
+
),
|
|
93
|
+
None,
|
|
94
|
+
),
|
|
95
|
+
list_runs(workspace_root, task.id),
|
|
96
|
+
),
|
|
97
|
+
"accepted_plan_version": task.accepted_plan_version,
|
|
98
|
+
"latest_plan_version": task.latest_plan_version,
|
|
99
|
+
}
|
|
100
|
+
for task in tasks
|
|
101
|
+
],
|
|
102
|
+
"doctor": doctor,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def project_doctor(workspace_root: Path) -> dict[str, object]:
|
|
107
|
+
return inspect_v2_project(workspace_root)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def project_export(
|
|
111
|
+
workspace_root: Path,
|
|
112
|
+
*,
|
|
113
|
+
include_bodies: bool = False,
|
|
114
|
+
include_run_artifacts: bool = False,
|
|
115
|
+
) -> dict[str, object]:
|
|
116
|
+
return export_project_payload(
|
|
117
|
+
workspace_root,
|
|
118
|
+
include_bodies=include_bodies,
|
|
119
|
+
include_run_artifacts=include_run_artifacts,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def project_import(
|
|
124
|
+
workspace_root: Path,
|
|
125
|
+
*,
|
|
126
|
+
text: str,
|
|
127
|
+
format_name: str = "json",
|
|
128
|
+
replace: bool = False,
|
|
129
|
+
) -> dict[str, object]:
|
|
130
|
+
payload = parse_project_import_payload(text, format_name=format_name)
|
|
131
|
+
return import_project_payload(workspace_root, payload=payload, replace=replace)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def project_snapshot(
|
|
135
|
+
workspace_root: Path,
|
|
136
|
+
*,
|
|
137
|
+
output_dir: Path,
|
|
138
|
+
include_bodies: bool = False,
|
|
139
|
+
include_run_artifacts: bool = False,
|
|
140
|
+
) -> dict[str, object]:
|
|
141
|
+
return write_project_snapshot(
|
|
142
|
+
workspace_root,
|
|
143
|
+
output_dir=output_dir,
|
|
144
|
+
include_bodies=include_bodies,
|
|
145
|
+
include_run_artifacts=include_run_artifacts,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _project_counts(workspace_root: Path) -> dict[str, int]:
|
|
150
|
+
tasks = list_tasks(workspace_root)
|
|
151
|
+
return {
|
|
152
|
+
"tasks": len(tasks),
|
|
153
|
+
"introductions": len(list_introductions(workspace_root)),
|
|
154
|
+
"plans": sum(len(list_plans(workspace_root, task.id)) for task in tasks),
|
|
155
|
+
"questions": sum(
|
|
156
|
+
len(list_questions(workspace_root, task.id)) for task in tasks
|
|
157
|
+
),
|
|
158
|
+
"runs": sum(len(list_runs(workspace_root, task.id)) for task in tasks),
|
|
159
|
+
"changes": sum(len(list_changes(workspace_root, task.id)) for task in tasks),
|
|
160
|
+
"locks": len(load_active_locks(workspace_root)),
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _active_task_status(workspace_root: Path) -> dict[str, object] | None:
|
|
165
|
+
state = load_active_task_state(workspace_root)
|
|
166
|
+
if state is None:
|
|
167
|
+
return None
|
|
168
|
+
task = resolve_task(workspace_root, state.task_id)
|
|
169
|
+
return {
|
|
170
|
+
"task_id": task.id,
|
|
171
|
+
"slug": task.slug,
|
|
172
|
+
"title": task.title,
|
|
173
|
+
"status_stage": task.status_stage,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
__all__ = [
|
|
178
|
+
"init_project",
|
|
179
|
+
"project_status_summary",
|
|
180
|
+
"project_status",
|
|
181
|
+
"project_doctor",
|
|
182
|
+
"project_export",
|
|
183
|
+
"project_import",
|
|
184
|
+
"project_snapshot",
|
|
185
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from taskledger.services.tasks import (
|
|
2
|
+
add_question,
|
|
3
|
+
answer_question,
|
|
4
|
+
answer_questions,
|
|
5
|
+
dismiss_question,
|
|
6
|
+
list_open_questions,
|
|
7
|
+
question_status,
|
|
8
|
+
)
|
|
9
|
+
from taskledger.storage.task_store import list_questions
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"add_question",
|
|
13
|
+
"list_questions",
|
|
14
|
+
"list_open_questions",
|
|
15
|
+
"answer_question",
|
|
16
|
+
"answer_questions",
|
|
17
|
+
"dismiss_question",
|
|
18
|
+
"question_status",
|
|
19
|
+
]
|
taskledger/api/search.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from taskledger.search import (
|
|
6
|
+
ProjectDependencyInfo,
|
|
7
|
+
ProjectSearchMatch,
|
|
8
|
+
)
|
|
9
|
+
from taskledger.search import (
|
|
10
|
+
grep_project as _grep_project,
|
|
11
|
+
)
|
|
12
|
+
from taskledger.search import (
|
|
13
|
+
module_dependencies as _module_dependencies,
|
|
14
|
+
)
|
|
15
|
+
from taskledger.search import (
|
|
16
|
+
search_project as _search_project,
|
|
17
|
+
)
|
|
18
|
+
from taskledger.search import (
|
|
19
|
+
symbols_project as _symbols_project,
|
|
20
|
+
)
|
|
21
|
+
from taskledger.storage.init import ensure_project_exists
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def search_workspace(
|
|
25
|
+
workspace_root: Path,
|
|
26
|
+
*,
|
|
27
|
+
query: str,
|
|
28
|
+
repo_refs: tuple[str, ...] = (),
|
|
29
|
+
limit: int = 50,
|
|
30
|
+
) -> list[ProjectSearchMatch]:
|
|
31
|
+
return _search_project(
|
|
32
|
+
ensure_project_exists(workspace_root),
|
|
33
|
+
query=query,
|
|
34
|
+
repo_refs=repo_refs,
|
|
35
|
+
limit=limit,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def grep_workspace(
|
|
40
|
+
workspace_root: Path,
|
|
41
|
+
*,
|
|
42
|
+
pattern: str,
|
|
43
|
+
repo_refs: tuple[str, ...] = (),
|
|
44
|
+
limit: int = 100,
|
|
45
|
+
) -> list[ProjectSearchMatch]:
|
|
46
|
+
return _grep_project(
|
|
47
|
+
ensure_project_exists(workspace_root),
|
|
48
|
+
pattern=pattern,
|
|
49
|
+
repo_refs=repo_refs,
|
|
50
|
+
limit=limit,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def symbols_workspace(
|
|
55
|
+
workspace_root: Path,
|
|
56
|
+
*,
|
|
57
|
+
query: str,
|
|
58
|
+
repo_refs: tuple[str, ...] = (),
|
|
59
|
+
limit: int = 50,
|
|
60
|
+
) -> list[ProjectSearchMatch]:
|
|
61
|
+
return _symbols_project(
|
|
62
|
+
ensure_project_exists(workspace_root),
|
|
63
|
+
query=query,
|
|
64
|
+
repo_refs=repo_refs,
|
|
65
|
+
limit=limit,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def dependencies_for_module(
|
|
70
|
+
workspace_root: Path,
|
|
71
|
+
*,
|
|
72
|
+
repo_ref: str,
|
|
73
|
+
module: str,
|
|
74
|
+
) -> ProjectDependencyInfo:
|
|
75
|
+
return _module_dependencies(
|
|
76
|
+
ensure_project_exists(workspace_root),
|
|
77
|
+
repo_ref=repo_ref,
|
|
78
|
+
module=module,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
__all__ = [
|
|
83
|
+
"search_workspace",
|
|
84
|
+
"grep_workspace",
|
|
85
|
+
"symbols_workspace",
|
|
86
|
+
"dependencies_for_module",
|
|
87
|
+
]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from taskledger.services.tasks import (
|
|
2
|
+
add_change,
|
|
3
|
+
add_implementation_artifact,
|
|
4
|
+
add_implementation_deviation,
|
|
5
|
+
add_validation_check,
|
|
6
|
+
finish_implementation,
|
|
7
|
+
finish_validation,
|
|
8
|
+
log_implementation,
|
|
9
|
+
restart_implementation,
|
|
10
|
+
run_implementation_command,
|
|
11
|
+
scan_changes,
|
|
12
|
+
show_task_run,
|
|
13
|
+
start_implementation,
|
|
14
|
+
start_validation,
|
|
15
|
+
validation_status,
|
|
16
|
+
waive_criterion,
|
|
17
|
+
)
|
|
18
|
+
from taskledger.storage.task_store import list_changes, list_runs
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"start_implementation",
|
|
22
|
+
"restart_implementation",
|
|
23
|
+
"log_implementation",
|
|
24
|
+
"add_implementation_deviation",
|
|
25
|
+
"add_implementation_artifact",
|
|
26
|
+
"add_change",
|
|
27
|
+
"finish_implementation",
|
|
28
|
+
"scan_changes",
|
|
29
|
+
"run_implementation_command",
|
|
30
|
+
"start_validation",
|
|
31
|
+
"add_validation_check",
|
|
32
|
+
"finish_validation",
|
|
33
|
+
"validation_status",
|
|
34
|
+
"waive_criterion",
|
|
35
|
+
"show_task_run",
|
|
36
|
+
"list_runs",
|
|
37
|
+
"list_changes",
|
|
38
|
+
]
|