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
taskledger/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ from __future__ import annotations
2
+
3
+ from taskledger._version import __version__
4
+
5
+ __all__ = ["__version__"]
taskledger/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ """Entry point for python -m taskledger."""
2
+
3
+ from taskledger.cli import cli_main
4
+
5
+ if __name__ == "__main__":
6
+ cli_main()
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,13 @@
1
+ from __future__ import annotations
2
+
3
+ __all__ = [
4
+ "handoff",
5
+ "introductions",
6
+ "locks",
7
+ "plans",
8
+ "project",
9
+ "questions",
10
+ "search",
11
+ "task_runs",
12
+ "tasks",
13
+ ]
@@ -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
+ ]
@@ -0,0 +1,4 @@
1
+ from taskledger.services.tasks import break_lock, list_locks, show_lock
2
+ from taskledger.storage.task_store import load_active_locks
3
+
4
+ __all__ = ["show_lock", "break_lock", "list_locks", "load_active_locks"]
@@ -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
+ ]
@@ -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
+ ]