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/cli_plan.py
ADDED
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from taskledger.api.plans import (
|
|
9
|
+
approve_plan,
|
|
10
|
+
diff_plan,
|
|
11
|
+
lint_plan,
|
|
12
|
+
list_plan_versions,
|
|
13
|
+
materialize_plan_todos,
|
|
14
|
+
propose_plan,
|
|
15
|
+
regenerate_plan_from_answers,
|
|
16
|
+
reject_plan,
|
|
17
|
+
revise_plan,
|
|
18
|
+
run_planning_command,
|
|
19
|
+
show_plan,
|
|
20
|
+
start_planning,
|
|
21
|
+
upsert_plan,
|
|
22
|
+
)
|
|
23
|
+
from taskledger.cli_common import (
|
|
24
|
+
TaskOption,
|
|
25
|
+
cli_state_from_context,
|
|
26
|
+
emit_error,
|
|
27
|
+
emit_payload,
|
|
28
|
+
launch_error_exit_code,
|
|
29
|
+
read_text_input,
|
|
30
|
+
resolve_cli_task,
|
|
31
|
+
)
|
|
32
|
+
from taskledger.domain.states import EXIT_CODE_VALIDATION_FAILED
|
|
33
|
+
from taskledger.errors import LaunchError
|
|
34
|
+
from taskledger.services.actors import resolve_actor, resolve_harness
|
|
35
|
+
from taskledger.services.plan_lint import PlanLintPayload
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def register_plan_v2_commands(app: typer.Typer) -> None: # noqa: C901
|
|
39
|
+
@app.command("start")
|
|
40
|
+
def start_command(
|
|
41
|
+
ctx: typer.Context,
|
|
42
|
+
task_ref: TaskOption = None,
|
|
43
|
+
actor: Annotated[
|
|
44
|
+
str | None,
|
|
45
|
+
typer.Option("--actor", help="Actor type: user, agent, or system."),
|
|
46
|
+
] = None,
|
|
47
|
+
actor_name: Annotated[
|
|
48
|
+
str | None,
|
|
49
|
+
typer.Option("--actor-name", help="Actor name."),
|
|
50
|
+
] = None,
|
|
51
|
+
actor_role: Annotated[
|
|
52
|
+
str | None,
|
|
53
|
+
typer.Option("--actor-role", help="Actor role in task lifecycle."),
|
|
54
|
+
] = None,
|
|
55
|
+
harness: Annotated[
|
|
56
|
+
str | None,
|
|
57
|
+
typer.Option("--harness", help="Harness name."),
|
|
58
|
+
] = None,
|
|
59
|
+
session_id: Annotated[
|
|
60
|
+
str | None,
|
|
61
|
+
typer.Option("--session-id", help="Session identifier."),
|
|
62
|
+
] = None,
|
|
63
|
+
) -> None:
|
|
64
|
+
state = cli_state_from_context(ctx)
|
|
65
|
+
try:
|
|
66
|
+
task = resolve_cli_task(state.cwd, task_ref)
|
|
67
|
+
resolved_actor = resolve_actor(
|
|
68
|
+
actor_type=actor,
|
|
69
|
+
actor_name=actor_name,
|
|
70
|
+
role=actor_role,
|
|
71
|
+
session_id=session_id,
|
|
72
|
+
)
|
|
73
|
+
resolved_harness = resolve_harness(name=harness, session_id=session_id)
|
|
74
|
+
payload = start_planning(
|
|
75
|
+
state.cwd,
|
|
76
|
+
task.id,
|
|
77
|
+
actor=resolved_actor,
|
|
78
|
+
harness=resolved_harness,
|
|
79
|
+
)
|
|
80
|
+
except LaunchError as exc:
|
|
81
|
+
emit_error(ctx, exc)
|
|
82
|
+
raise typer.Exit(code=launch_error_exit_code(exc)) from exc
|
|
83
|
+
emit_payload(ctx, payload, human=f"started planning {payload['task_id']}")
|
|
84
|
+
|
|
85
|
+
@app.command("propose")
|
|
86
|
+
def propose_command(
|
|
87
|
+
ctx: typer.Context,
|
|
88
|
+
task_ref: TaskOption = None,
|
|
89
|
+
text: Annotated[str | None, typer.Option("--text")] = None,
|
|
90
|
+
from_file: Annotated[Path | None, typer.Option("--file")] = None,
|
|
91
|
+
criterion: Annotated[list[str] | None, typer.Option("--criterion")] = None,
|
|
92
|
+
) -> None:
|
|
93
|
+
state = cli_state_from_context(ctx)
|
|
94
|
+
try:
|
|
95
|
+
task = resolve_cli_task(state.cwd, task_ref)
|
|
96
|
+
payload = propose_plan(
|
|
97
|
+
state.cwd,
|
|
98
|
+
task.id,
|
|
99
|
+
body=read_text_input(text=text, from_file=from_file),
|
|
100
|
+
criteria=tuple(criterion or ()),
|
|
101
|
+
)
|
|
102
|
+
except LaunchError as exc:
|
|
103
|
+
emit_error(ctx, exc)
|
|
104
|
+
raise typer.Exit(code=launch_error_exit_code(exc)) from exc
|
|
105
|
+
emit_payload(
|
|
106
|
+
ctx,
|
|
107
|
+
payload,
|
|
108
|
+
human=f"proposed plan v{payload['plan_version']} for {payload['task_id']}",
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
@app.command("draft")
|
|
112
|
+
def draft_command(
|
|
113
|
+
ctx: typer.Context,
|
|
114
|
+
task_ref: TaskOption = None,
|
|
115
|
+
ask_questions: Annotated[bool, typer.Option("--ask-questions")] = False,
|
|
116
|
+
) -> None:
|
|
117
|
+
state = cli_state_from_context(ctx)
|
|
118
|
+
try:
|
|
119
|
+
task = resolve_cli_task(state.cwd, task_ref)
|
|
120
|
+
payload = {
|
|
121
|
+
"kind": "plan_draft_context",
|
|
122
|
+
"task_id": task.id,
|
|
123
|
+
"ask_questions": ask_questions,
|
|
124
|
+
"next_action": (
|
|
125
|
+
'taskledger question add --text "..." --required-for-plan'
|
|
126
|
+
if ask_questions
|
|
127
|
+
else "taskledger plan propose --file plan.md"
|
|
128
|
+
),
|
|
129
|
+
}
|
|
130
|
+
except LaunchError as exc:
|
|
131
|
+
emit_error(ctx, exc)
|
|
132
|
+
raise typer.Exit(code=launch_error_exit_code(exc)) from exc
|
|
133
|
+
emit_payload(ctx, payload, human=str(payload["next_action"]))
|
|
134
|
+
|
|
135
|
+
@app.command("regenerate")
|
|
136
|
+
def regenerate_command(
|
|
137
|
+
ctx: typer.Context,
|
|
138
|
+
task_ref: TaskOption = None,
|
|
139
|
+
from_answers: Annotated[bool, typer.Option("--from-answers")] = False,
|
|
140
|
+
text: Annotated[str | None, typer.Option("--text")] = None,
|
|
141
|
+
from_file: Annotated[Path | None, typer.Option("--file")] = None,
|
|
142
|
+
allow_open_questions: Annotated[
|
|
143
|
+
bool,
|
|
144
|
+
typer.Option("--allow-open-questions"),
|
|
145
|
+
] = False,
|
|
146
|
+
) -> None:
|
|
147
|
+
state = cli_state_from_context(ctx)
|
|
148
|
+
try:
|
|
149
|
+
if not from_answers:
|
|
150
|
+
raise LaunchError("plan regenerate requires --from-answers.")
|
|
151
|
+
task = resolve_cli_task(state.cwd, task_ref)
|
|
152
|
+
payload = regenerate_plan_from_answers(
|
|
153
|
+
state.cwd,
|
|
154
|
+
task.id,
|
|
155
|
+
body=read_text_input(text=text, from_file=from_file),
|
|
156
|
+
allow_open_questions=allow_open_questions,
|
|
157
|
+
)
|
|
158
|
+
except LaunchError as exc:
|
|
159
|
+
emit_error(ctx, exc)
|
|
160
|
+
raise typer.Exit(code=launch_error_exit_code(exc)) from exc
|
|
161
|
+
emit_payload(
|
|
162
|
+
ctx,
|
|
163
|
+
payload,
|
|
164
|
+
human=f"regenerated plan v{payload['plan_version']} "
|
|
165
|
+
f"for {payload['task_id']}",
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
@app.command("upsert")
|
|
169
|
+
def upsert_command(
|
|
170
|
+
ctx: typer.Context,
|
|
171
|
+
task_ref: TaskOption = None,
|
|
172
|
+
from_answers: Annotated[bool, typer.Option("--from-answers")] = False,
|
|
173
|
+
text: Annotated[str | None, typer.Option("--text")] = None,
|
|
174
|
+
from_file: Annotated[Path | None, typer.Option("--file")] = None,
|
|
175
|
+
criterion: Annotated[list[str] | None, typer.Option("--criterion")] = None,
|
|
176
|
+
allow_open_questions: Annotated[
|
|
177
|
+
bool,
|
|
178
|
+
typer.Option("--allow-open-questions"),
|
|
179
|
+
] = False,
|
|
180
|
+
) -> None:
|
|
181
|
+
state = cli_state_from_context(ctx)
|
|
182
|
+
try:
|
|
183
|
+
task = resolve_cli_task(state.cwd, task_ref)
|
|
184
|
+
payload = upsert_plan(
|
|
185
|
+
state.cwd,
|
|
186
|
+
task.id,
|
|
187
|
+
body=read_text_input(text=text, from_file=from_file),
|
|
188
|
+
criteria=tuple(criterion or ()),
|
|
189
|
+
from_answers=from_answers,
|
|
190
|
+
allow_open_questions=allow_open_questions,
|
|
191
|
+
)
|
|
192
|
+
except LaunchError as exc:
|
|
193
|
+
emit_error(ctx, exc)
|
|
194
|
+
raise typer.Exit(code=launch_error_exit_code(exc)) from exc
|
|
195
|
+
operation = str(payload.get("operation", "upserted"))
|
|
196
|
+
emit_payload(
|
|
197
|
+
ctx,
|
|
198
|
+
payload,
|
|
199
|
+
human=f"{operation} plan v{payload['plan_version']} "
|
|
200
|
+
f"for {payload['task_id']}",
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
@app.command("materialize-todos")
|
|
204
|
+
def materialize_todos_command(
|
|
205
|
+
ctx: typer.Context,
|
|
206
|
+
version: Annotated[int, typer.Option("--version")],
|
|
207
|
+
task_ref: TaskOption = None,
|
|
208
|
+
dry_run: Annotated[bool, typer.Option("--dry-run")] = False,
|
|
209
|
+
) -> None:
|
|
210
|
+
state = cli_state_from_context(ctx)
|
|
211
|
+
try:
|
|
212
|
+
task = resolve_cli_task(state.cwd, task_ref)
|
|
213
|
+
payload = materialize_plan_todos(
|
|
214
|
+
state.cwd,
|
|
215
|
+
task.id,
|
|
216
|
+
version=version,
|
|
217
|
+
dry_run=dry_run,
|
|
218
|
+
)
|
|
219
|
+
except LaunchError as exc:
|
|
220
|
+
emit_error(ctx, exc)
|
|
221
|
+
raise typer.Exit(code=launch_error_exit_code(exc)) from exc
|
|
222
|
+
emit_payload(
|
|
223
|
+
ctx,
|
|
224
|
+
payload,
|
|
225
|
+
human=f"materialized {payload['materialized_todos']} todos",
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
@app.command("show")
|
|
229
|
+
def show_command(
|
|
230
|
+
ctx: typer.Context,
|
|
231
|
+
task_ref: TaskOption = None,
|
|
232
|
+
version: Annotated[int | None, typer.Option("--version")] = None,
|
|
233
|
+
) -> None:
|
|
234
|
+
state = cli_state_from_context(ctx)
|
|
235
|
+
try:
|
|
236
|
+
task = resolve_cli_task(state.cwd, task_ref)
|
|
237
|
+
payload = show_plan(state.cwd, task.id, version=version)
|
|
238
|
+
except LaunchError as exc:
|
|
239
|
+
emit_error(ctx, exc)
|
|
240
|
+
raise typer.Exit(code=launch_error_exit_code(exc)) from exc
|
|
241
|
+
plan = payload["plan"]
|
|
242
|
+
assert isinstance(plan, dict)
|
|
243
|
+
emit_payload(
|
|
244
|
+
ctx,
|
|
245
|
+
payload,
|
|
246
|
+
human=f"plan v{plan['plan_version']} ({plan['status']})",
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
@app.command("list")
|
|
250
|
+
def list_command(
|
|
251
|
+
ctx: typer.Context,
|
|
252
|
+
task_ref: TaskOption = None,
|
|
253
|
+
) -> None:
|
|
254
|
+
state = cli_state_from_context(ctx)
|
|
255
|
+
try:
|
|
256
|
+
task = resolve_cli_task(state.cwd, task_ref)
|
|
257
|
+
payload = list_plan_versions(state.cwd, task.id)
|
|
258
|
+
except LaunchError as exc:
|
|
259
|
+
emit_error(ctx, exc)
|
|
260
|
+
raise typer.Exit(code=launch_error_exit_code(exc)) from exc
|
|
261
|
+
plans = payload["plans"]
|
|
262
|
+
assert isinstance(plans, list)
|
|
263
|
+
lines = ["PLANS"]
|
|
264
|
+
for item in plans:
|
|
265
|
+
if isinstance(item, dict):
|
|
266
|
+
lines.append(f"v{item['plan_version']} {item['status']}")
|
|
267
|
+
emit_payload(
|
|
268
|
+
ctx, payload, human="\n".join(lines) if plans else "PLANS\n(empty)"
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
@app.command("diff")
|
|
272
|
+
def diff_command(
|
|
273
|
+
ctx: typer.Context,
|
|
274
|
+
task_ref: TaskOption = None,
|
|
275
|
+
from_version: Annotated[int, typer.Option("--from")] = 1,
|
|
276
|
+
to_version: Annotated[int, typer.Option("--to")] = 1,
|
|
277
|
+
) -> None:
|
|
278
|
+
state = cli_state_from_context(ctx)
|
|
279
|
+
try:
|
|
280
|
+
task = resolve_cli_task(state.cwd, task_ref)
|
|
281
|
+
payload = diff_plan(
|
|
282
|
+
state.cwd,
|
|
283
|
+
task.id,
|
|
284
|
+
from_version=from_version,
|
|
285
|
+
to_version=to_version,
|
|
286
|
+
)
|
|
287
|
+
except LaunchError as exc:
|
|
288
|
+
emit_error(ctx, exc)
|
|
289
|
+
raise typer.Exit(code=launch_error_exit_code(exc)) from exc
|
|
290
|
+
emit_payload(ctx, payload, human=str(payload["diff"]))
|
|
291
|
+
|
|
292
|
+
@app.command("lint")
|
|
293
|
+
def lint_command(
|
|
294
|
+
ctx: typer.Context,
|
|
295
|
+
task_ref: TaskOption = None,
|
|
296
|
+
version: Annotated[int | None, typer.Option("--version")] = None,
|
|
297
|
+
strict: Annotated[bool, typer.Option("--strict")] = False,
|
|
298
|
+
) -> None:
|
|
299
|
+
state = cli_state_from_context(ctx)
|
|
300
|
+
try:
|
|
301
|
+
task = resolve_cli_task(state.cwd, task_ref)
|
|
302
|
+
payload = lint_plan(
|
|
303
|
+
state.cwd,
|
|
304
|
+
task.id,
|
|
305
|
+
version=version,
|
|
306
|
+
strict=strict,
|
|
307
|
+
)
|
|
308
|
+
except LaunchError as exc:
|
|
309
|
+
emit_error(ctx, exc)
|
|
310
|
+
raise typer.Exit(code=launch_error_exit_code(exc)) from exc
|
|
311
|
+
emit_payload(ctx, payload, human=_render_plan_lint(payload))
|
|
312
|
+
if not payload["passed"]:
|
|
313
|
+
raise typer.Exit(code=EXIT_CODE_VALIDATION_FAILED)
|
|
314
|
+
|
|
315
|
+
@app.command("approve")
|
|
316
|
+
def approve_command(
|
|
317
|
+
ctx: typer.Context,
|
|
318
|
+
version: Annotated[int, typer.Option("--version")],
|
|
319
|
+
actor: Annotated[str, typer.Option("--actor")] = "agent",
|
|
320
|
+
actor_name: Annotated[str | None, typer.Option("--actor-name")] = None,
|
|
321
|
+
note: Annotated[str | None, typer.Option("--note")] = None,
|
|
322
|
+
allow_agent_approval: Annotated[
|
|
323
|
+
bool, typer.Option("--allow-agent-approval")
|
|
324
|
+
] = False,
|
|
325
|
+
reason: Annotated[str | None, typer.Option("--reason")] = None,
|
|
326
|
+
allow_empty_criteria: Annotated[
|
|
327
|
+
bool, typer.Option("--allow-empty-criteria")
|
|
328
|
+
] = False,
|
|
329
|
+
no_materialize_todos: Annotated[
|
|
330
|
+
bool, typer.Option("--no-materialize-todos")
|
|
331
|
+
] = False,
|
|
332
|
+
allow_open_questions: Annotated[
|
|
333
|
+
bool, typer.Option("--allow-open-questions")
|
|
334
|
+
] = False,
|
|
335
|
+
allow_empty_todos: Annotated[bool, typer.Option("--allow-empty-todos")] = False,
|
|
336
|
+
allow_lint_errors: Annotated[bool, typer.Option("--allow-lint-errors")] = False,
|
|
337
|
+
task_ref: TaskOption = None,
|
|
338
|
+
) -> None:
|
|
339
|
+
state = cli_state_from_context(ctx)
|
|
340
|
+
try:
|
|
341
|
+
task = resolve_cli_task(state.cwd, task_ref)
|
|
342
|
+
payload = approve_plan(
|
|
343
|
+
state.cwd,
|
|
344
|
+
task.id,
|
|
345
|
+
version=version,
|
|
346
|
+
actor_type=actor,
|
|
347
|
+
actor_name=actor_name,
|
|
348
|
+
note=note,
|
|
349
|
+
allow_agent_approval=allow_agent_approval,
|
|
350
|
+
reason=reason,
|
|
351
|
+
allow_empty_criteria=allow_empty_criteria,
|
|
352
|
+
materialize_todos=not no_materialize_todos,
|
|
353
|
+
allow_open_questions=allow_open_questions,
|
|
354
|
+
allow_empty_todos=allow_empty_todos,
|
|
355
|
+
allow_lint_errors=allow_lint_errors,
|
|
356
|
+
)
|
|
357
|
+
except LaunchError as exc:
|
|
358
|
+
emit_error(ctx, exc)
|
|
359
|
+
raise typer.Exit(code=launch_error_exit_code(exc)) from exc
|
|
360
|
+
emit_payload(
|
|
361
|
+
ctx,
|
|
362
|
+
payload,
|
|
363
|
+
human=f"approved plan v{payload['plan_version']} for {payload['task_id']}",
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
@app.command("accept")
|
|
367
|
+
def accept_command(
|
|
368
|
+
ctx: typer.Context,
|
|
369
|
+
version: Annotated[int, typer.Option("--version")],
|
|
370
|
+
note: Annotated[str | None, typer.Option("--note")] = None,
|
|
371
|
+
task_ref: TaskOption = None,
|
|
372
|
+
allow_lint_errors: Annotated[bool, typer.Option("--allow-lint-errors")] = False,
|
|
373
|
+
) -> None:
|
|
374
|
+
state = cli_state_from_context(ctx)
|
|
375
|
+
try:
|
|
376
|
+
task = resolve_cli_task(state.cwd, task_ref)
|
|
377
|
+
payload = approve_plan(
|
|
378
|
+
state.cwd,
|
|
379
|
+
task.id,
|
|
380
|
+
version=version,
|
|
381
|
+
actor_type="user",
|
|
382
|
+
note=note,
|
|
383
|
+
allow_lint_errors=allow_lint_errors,
|
|
384
|
+
reason=note if allow_lint_errors else None,
|
|
385
|
+
)
|
|
386
|
+
except LaunchError as exc:
|
|
387
|
+
emit_error(ctx, exc)
|
|
388
|
+
raise typer.Exit(code=launch_error_exit_code(exc)) from exc
|
|
389
|
+
emit_payload(
|
|
390
|
+
ctx,
|
|
391
|
+
payload,
|
|
392
|
+
human=f"accepted plan v{payload['plan_version']} for {payload['task_id']}",
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
@app.command("reject")
|
|
396
|
+
def reject_command(
|
|
397
|
+
ctx: typer.Context,
|
|
398
|
+
task_ref: TaskOption = None,
|
|
399
|
+
reason: Annotated[str | None, typer.Option("--reason")] = None,
|
|
400
|
+
) -> None:
|
|
401
|
+
state = cli_state_from_context(ctx)
|
|
402
|
+
try:
|
|
403
|
+
task = resolve_cli_task(state.cwd, task_ref)
|
|
404
|
+
payload = reject_plan(state.cwd, task.id, reason=reason)
|
|
405
|
+
except LaunchError as exc:
|
|
406
|
+
emit_error(ctx, exc)
|
|
407
|
+
raise typer.Exit(code=launch_error_exit_code(exc)) from exc
|
|
408
|
+
emit_payload(ctx, payload, human=f"rejected plan for {payload['task_id']}")
|
|
409
|
+
|
|
410
|
+
@app.command("revise")
|
|
411
|
+
def revise_command(
|
|
412
|
+
ctx: typer.Context,
|
|
413
|
+
task_ref: TaskOption = None,
|
|
414
|
+
) -> None:
|
|
415
|
+
state = cli_state_from_context(ctx)
|
|
416
|
+
try:
|
|
417
|
+
task = resolve_cli_task(state.cwd, task_ref)
|
|
418
|
+
payload = revise_plan(state.cwd, task.id)
|
|
419
|
+
except LaunchError as exc:
|
|
420
|
+
emit_error(ctx, exc)
|
|
421
|
+
raise typer.Exit(code=launch_error_exit_code(exc)) from exc
|
|
422
|
+
emit_payload(ctx, payload, human=f"restarted planning {payload['task_id']}")
|
|
423
|
+
|
|
424
|
+
@app.command(
|
|
425
|
+
"command",
|
|
426
|
+
context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
|
|
427
|
+
)
|
|
428
|
+
def plan_command_command(
|
|
429
|
+
ctx: typer.Context,
|
|
430
|
+
task_ref: TaskOption = None,
|
|
431
|
+
) -> None:
|
|
432
|
+
state = cli_state_from_context(ctx)
|
|
433
|
+
argv = tuple(ctx.args)
|
|
434
|
+
try:
|
|
435
|
+
task = resolve_cli_task(state.cwd, task_ref)
|
|
436
|
+
payload = run_planning_command(
|
|
437
|
+
state.cwd,
|
|
438
|
+
task.id,
|
|
439
|
+
argv=argv,
|
|
440
|
+
)
|
|
441
|
+
except LaunchError as exc:
|
|
442
|
+
emit_error(ctx, exc)
|
|
443
|
+
raise typer.Exit(code=launch_error_exit_code(exc)) from exc
|
|
444
|
+
emit_payload(
|
|
445
|
+
ctx,
|
|
446
|
+
payload,
|
|
447
|
+
human=f"ran planning command exit={payload['exit_code']}",
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def _render_plan_lint(payload: PlanLintPayload) -> str:
|
|
452
|
+
passed = payload["passed"]
|
|
453
|
+
task_id = payload["task_id"]
|
|
454
|
+
plan_id = payload["plan_id"]
|
|
455
|
+
summary = payload["summary"]
|
|
456
|
+
issues = payload["issues"]
|
|
457
|
+
assert isinstance(summary, dict)
|
|
458
|
+
assert isinstance(issues, list)
|
|
459
|
+
|
|
460
|
+
status_word = "PASSED" if passed else "FAILED"
|
|
461
|
+
header = f"PLAN LINT {status_word} {task_id} {plan_id}"
|
|
462
|
+
lines = [header]
|
|
463
|
+
errors = summary.get("errors", 0)
|
|
464
|
+
warnings = summary.get("warnings", 0)
|
|
465
|
+
assert isinstance(errors, int) and isinstance(warnings, int)
|
|
466
|
+
lines.append(f"errors: {errors} warnings: {warnings}")
|
|
467
|
+
lines.append("")
|
|
468
|
+
for issue in issues:
|
|
469
|
+
assert isinstance(issue, dict)
|
|
470
|
+
sev = issue["severity"]
|
|
471
|
+
code = issue["code"]
|
|
472
|
+
location = issue["location"]
|
|
473
|
+
message = issue["message"]
|
|
474
|
+
assert isinstance(sev, str) and isinstance(code, str)
|
|
475
|
+
assert isinstance(location, str) and isinstance(message, str)
|
|
476
|
+
lines.append(f"[{sev}] {code} at {location}")
|
|
477
|
+
lines.append(f" {message}")
|
|
478
|
+
return "\n".join(lines)
|