drydock-cli 3.0.127__tar.gz → 3.0.129__tar.gz
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.
- {drydock_cli-3.0.127/drydock_cli.egg-info → drydock_cli-3.0.129}/PKG-INFO +1 -1
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/agent.py +2 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/events.py +51 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/tui/app.py +27 -1
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/tui/widgets.py +1 -1
- {drydock_cli-3.0.127 → drydock_cli-3.0.129/drydock_cli.egg-info}/PKG-INFO +1 -1
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/pyproject.toml +1 -1
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_events.py +33 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_task_state.py +22 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/LICENSE +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/NOTICE +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/README.md +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/__init__.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/__main__.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/advisor.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/bash_safety.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/builtin_skills/__init__.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/builtin_skills/ml-data.md +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/builtin_skills/ml-debug.md +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/builtin_skills/ml-finetune.md +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/builtin_skills/ml-metrics.md +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/builtin_skills/ml-rl.md +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/builtin_skills/ml-train.md +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/builtin_skills/rmf-categorize.md +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/builtin_skills/rmf-control.md +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/builtin_skills/rmf-poam.md +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/builtin_skills/rmf-review.md +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/builtin_skills/stig-assess.md +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/builtin_skills/stig-remediate.md +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/cci.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/cli.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/compaction.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/config.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/detect.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/extract.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/gittools.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/graphrag.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/guards.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/loop_detect.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/mcp.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/poam.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/providers.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/recipes.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/rmf.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/rmf_graph.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/skills.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/stig.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/suggest.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/task_state.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/tool_registry.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/tools/__init__.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/tui/__init__.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/tui/approval.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/tui/messages.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/tuning.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/verification.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/web.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock_cli.egg-info/SOURCES.txt +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock_cli.egg-info/dependency_links.txt +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock_cli.egg-info/entry_points.txt +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock_cli.egg-info/requires.txt +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock_cli.egg-info/top_level.txt +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/setup.cfg +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_advisor.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_approval.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_back_command.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_bash_background.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_bash_binary_output.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_bash_crossplatform.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_bash_output_bounding.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_bash_process_group.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_bash_safety.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_bash_sanitize.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_bash_shell.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_bash_stdin.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_bash_stop_partial.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_bash_timeout_network.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_bash_timeout_param.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_cci.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_cli_agents.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_compact_command.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_compaction.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_config.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_config_migration.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_context_limit_config.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_detect.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_dispatch.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_e2e_connected.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_edit_replace_all.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_empty_response.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_extract.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_failure_loop.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_first_run_setup.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_gittools.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_glob_edit_edges.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_graphify_example.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_graphrag.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_graphrag_quoted_path.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_grep_and_read_robust.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_guards_and_tools.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_hallucinated_tools.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_leaked_tool_call.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_loop_detect.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_mcp.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_oneshot_unreachable.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_overthink_interrupt.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_plan_autocontinue.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_poam.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_providers_unreachable.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_read_index.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_recipes.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_repetition_interrupt.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_rmf.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_rmf_graph.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_rmf_stig_graph.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_runaway_repetition.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_screenshot.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_server_probe.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_skills.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_stall_retry.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_stig.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_stop.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_streaming_newlines.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_subagent.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_suggest.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_system_prompt_help.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_todo.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_tool_arg_coercion.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_tool_arg_coercion_more.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_tool_arg_parsing.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_tools_undo.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_tui.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_tuning.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_verification_gate.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_viewimage.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_vision_input.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_web_tools.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_windows_shell.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_write_content_coerce.py +0 -0
- {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_xccdf.py +0 -0
|
@@ -389,6 +389,8 @@ def run(
|
|
|
389
389
|
for tc in assistant_turn.tool_calls:
|
|
390
390
|
tool_call_count += 1
|
|
391
391
|
if tc["name"] in ("Edit", "Write"):
|
|
392
|
+
if not session_has_edited and state.task.phase in ("understand", "discover", "plan"):
|
|
393
|
+
state.task.phase = "implement" # first change → building
|
|
392
394
|
session_has_edited = True
|
|
393
395
|
|
|
394
396
|
# STOP pressed: don't run the remaining tools, but still record a
|
|
@@ -66,3 +66,54 @@ def default_event_log_path():
|
|
|
66
66
|
import time
|
|
67
67
|
from pathlib import Path
|
|
68
68
|
return Path.home() / ".drydock" / "events" / f"session-{int(time.time())}.jsonl"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _events(events_or_path):
|
|
72
|
+
return events_or_path if isinstance(events_or_path, list) else EventLog.read(events_or_path)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def reconstruct_task_state(events_or_path):
|
|
76
|
+
"""Rebuild a TaskState (objective, acceptance criteria, phase) from an event
|
|
77
|
+
trace — the PRD "a task can be reconstructed from serialized state" / resume.
|
|
78
|
+
Returns a fresh TaskState (empty if the trace has no task_start)."""
|
|
79
|
+
from drydock.task_state import TaskState
|
|
80
|
+
ts = TaskState()
|
|
81
|
+
for e in _events(events_or_path):
|
|
82
|
+
t = e.get("type")
|
|
83
|
+
if t == "task_start":
|
|
84
|
+
ts.objective = e.get("objective", "") or ts.objective
|
|
85
|
+
ts.acceptance_criteria = list(e.get("acceptance_criteria", []) or ts.acceptance_criteria)
|
|
86
|
+
elif t in ("verify_gate", "verification", "done") and e.get("phase"):
|
|
87
|
+
ts.phase = e.get("phase", ts.phase)
|
|
88
|
+
elif t == "verify_gate":
|
|
89
|
+
ts.phase = "repair" if e.get("kind") == "failed" else "verify"
|
|
90
|
+
elif t == "done":
|
|
91
|
+
ts.phase = e.get("phase", ts.phase)
|
|
92
|
+
return ts
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def summarize(events_or_path) -> dict:
|
|
96
|
+
"""A compact digest of a task's execution trace for inspection/diagnosis."""
|
|
97
|
+
evs = _events(events_or_path)
|
|
98
|
+
tools: dict[str, int] = {}
|
|
99
|
+
turns = in_tok = out_tok = verify_pass = verify_fail = 0
|
|
100
|
+
phase = "understand"
|
|
101
|
+
for e in evs:
|
|
102
|
+
t = e.get("type")
|
|
103
|
+
if t == "turn":
|
|
104
|
+
turns += 1; in_tok += e.get("in_tok", 0) or 0; out_tok += e.get("out_tok", 0) or 0
|
|
105
|
+
elif t == "tool":
|
|
106
|
+
tools[e.get("name", "?")] = tools.get(e.get("name", "?"), 0) + 1
|
|
107
|
+
elif t == "verification":
|
|
108
|
+
if e.get("status") == "pass": verify_pass += 1
|
|
109
|
+
elif e.get("status") == "fail": verify_fail += 1
|
|
110
|
+
elif t == "done":
|
|
111
|
+
phase = e.get("phase", phase)
|
|
112
|
+
ts = reconstruct_task_state(evs)
|
|
113
|
+
return {
|
|
114
|
+
"objective": ts.objective, "acceptance_criteria": ts.acceptance_criteria,
|
|
115
|
+
"final_phase": phase, "turns": turns, "tools": tools,
|
|
116
|
+
"in_tok": in_tok, "out_tok": out_tok,
|
|
117
|
+
"verifications": {"pass": verify_pass, "fail": verify_fail},
|
|
118
|
+
"event_count": len(evs),
|
|
119
|
+
}
|
|
@@ -362,8 +362,11 @@ class DrydockApp(App):
|
|
|
362
362
|
flag = "⚓ working" if self._busy else "⚓ ready"
|
|
363
363
|
# Busy → show the interrupt keys; idle → the quit hint.
|
|
364
364
|
keys = "Esc/Ctrl+C stop" if self._busy else "Ctrl+C×2 quit"
|
|
365
|
+
# Show the task phase once it's past the default (understand).
|
|
366
|
+
ph = getattr(self.state, "task", None)
|
|
367
|
+
phase = f" · {ph.phase}" if (ph and ph.is_set() and ph.phase != "understand") else ""
|
|
365
368
|
return (
|
|
366
|
-
f"{flag} · {model} · {keys} · PgUp/PgDn scroll · "
|
|
369
|
+
f"{flag} · {model}{phase} · {keys} · PgUp/PgDn scroll · "
|
|
367
370
|
f"Ctrl+O details · {ctx}"
|
|
368
371
|
)
|
|
369
372
|
|
|
@@ -531,6 +534,8 @@ class DrydockApp(App):
|
|
|
531
534
|
self._cmd_context(arg)
|
|
532
535
|
elif cmd == "/shell":
|
|
533
536
|
self._cmd_shell()
|
|
537
|
+
elif cmd == "/events":
|
|
538
|
+
self._cmd_events()
|
|
534
539
|
elif cmd == "/advisor":
|
|
535
540
|
self._cmd_advisor(arg)
|
|
536
541
|
elif cmd == "/ask":
|
|
@@ -837,6 +842,27 @@ class DrydockApp(App):
|
|
|
837
842
|
else:
|
|
838
843
|
self._begin(prompt)
|
|
839
844
|
|
|
845
|
+
def _cmd_events(self) -> None:
|
|
846
|
+
"""Show a digest of this session's durable execution trace (event log)."""
|
|
847
|
+
log = self.state.events
|
|
848
|
+
if log is None:
|
|
849
|
+
self._info("Event log is off (config event_log = false).")
|
|
850
|
+
return
|
|
851
|
+
from drydock.events import summarize
|
|
852
|
+
s = summarize(str(log.path))
|
|
853
|
+
tools = ", ".join(f"{k}×{v}" for k, v in sorted(s["tools"].items())) or "none"
|
|
854
|
+
v = s["verifications"]
|
|
855
|
+
lines = [
|
|
856
|
+
f"Task trace — {log.path}",
|
|
857
|
+
f" objective : {(s['objective'] or '(none yet)')[:80]}",
|
|
858
|
+
f" criteria : {len(s['acceptance_criteria'])}",
|
|
859
|
+
f" phase : {s['final_phase']}",
|
|
860
|
+
f" turns : {s['turns']} tools: {tools}",
|
|
861
|
+
f" verifications: {v['pass']} passed, {v['fail']} failed",
|
|
862
|
+
f" tokens : {s['in_tok']:,} in / {s['out_tok']:,} out ({s['event_count']} events)",
|
|
863
|
+
]
|
|
864
|
+
self._info("\n".join(lines))
|
|
865
|
+
|
|
840
866
|
def _cmd_shell(self) -> None:
|
|
841
867
|
"""Show exactly which shell the Bash tool runs commands through, plus the
|
|
842
868
|
platform signals — so a 'still using bash on Windows' report is instantly
|
|
@@ -153,7 +153,7 @@ class PromptHistory:
|
|
|
153
153
|
# completes the prefix.
|
|
154
154
|
SLASH_COMMANDS = [
|
|
155
155
|
"/help", "/model", "/cwd", "/undo", "/back", "/stop", "/status",
|
|
156
|
-
"/compact", "/context", "/shell", "/advisor", "/ask", "/ask!", "/graphrag", "/skills",
|
|
156
|
+
"/compact", "/context", "/shell", "/events", "/advisor", "/ask", "/ask!", "/graphrag", "/skills",
|
|
157
157
|
"/loop", "/mcp", "/rmf", "/stig",
|
|
158
158
|
"/clear", "/quit",
|
|
159
159
|
]
|
|
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
|
|
7
7
|
# PyPI distribution name is drydock-cli (the established install name, continued
|
|
8
8
|
# from the retired v2 fork); the import package + CLI command stay `drydock`.
|
|
9
9
|
name = "drydock-cli"
|
|
10
|
-
version = "3.0.
|
|
10
|
+
version = "3.0.129"
|
|
11
11
|
description = "Drydock — a local, provider-agnostic terminal coding agent for local LLMs"
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
requires-python = ">=3.11"
|
|
@@ -56,3 +56,36 @@ def test_run_writes_full_trace():
|
|
|
56
56
|
def test_disabled_no_events_attr():
|
|
57
57
|
st = agent.AgentState()
|
|
58
58
|
assert st.events is None # off by default on a bare AgentState
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_reconstruct_task_state_from_trace():
|
|
62
|
+
from drydock.events import EventLog, reconstruct_task_state
|
|
63
|
+
p = os.path.join(tempfile.mkdtemp(), "r.jsonl")
|
|
64
|
+
log = EventLog(p)
|
|
65
|
+
log.emit("task_start", objective="Recover the password", acceptance_criteria=["23 chars", "write out.txt"])
|
|
66
|
+
log.emit("tool", name="Write")
|
|
67
|
+
log.emit("done", phase="complete")
|
|
68
|
+
ts = reconstruct_task_state(p)
|
|
69
|
+
assert ts.objective == "Recover the password"
|
|
70
|
+
assert ts.acceptance_criteria == ["23 chars", "write out.txt"]
|
|
71
|
+
assert ts.phase == "complete"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_summarize_digest():
|
|
75
|
+
from drydock.events import EventLog, summarize
|
|
76
|
+
p = os.path.join(tempfile.mkdtemp(), "s.jsonl")
|
|
77
|
+
log = EventLog(p)
|
|
78
|
+
log.emit("task_start", objective="do X", acceptance_criteria=["a"])
|
|
79
|
+
log.emit("turn", in_tok=100, out_tok=50)
|
|
80
|
+
log.emit("tool", name="Bash"); log.emit("tool", name="Bash")
|
|
81
|
+
log.emit("verification", status="fail"); log.emit("verification", status="pass")
|
|
82
|
+
log.emit("done", phase="complete")
|
|
83
|
+
s = summarize(p)
|
|
84
|
+
assert s["turns"] == 1 and s["tools"] == {"Bash": 2}
|
|
85
|
+
assert s["verifications"] == {"pass": 1, "fail": 1} and s["final_phase"] == "complete"
|
|
86
|
+
assert s["in_tok"] == 100 and s["out_tok"] == 50
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_reconstruct_empty_trace():
|
|
90
|
+
from drydock.events import reconstruct_task_state
|
|
91
|
+
assert reconstruct_task_state([]).objective == ""
|
|
@@ -67,3 +67,25 @@ def test_disabled_when_task_anchor_false():
|
|
|
67
67
|
cfg = {"model": "gemma4", "max_tokens": 8192, "context_limit": 65536, "task_anchor": False}
|
|
68
68
|
sysp, _ = _run_capture(cfg, "Do the thing.\n1. a criterion")
|
|
69
69
|
assert "TASK" not in sysp
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_phase_transitions_understand_implement_complete():
|
|
73
|
+
from drydock.providers import AssistantTurn
|
|
74
|
+
cfg = {"model": "gemma4", "max_tokens": 8192, "context_limit": 65536}
|
|
75
|
+
turns = [
|
|
76
|
+
AssistantTurn("", [{"id": "1", "name": "Write",
|
|
77
|
+
"input": {"file_path": "/tmp/dd_ph.txt", "content": "x"}}], 5, 5),
|
|
78
|
+
AssistantTurn("", [{"id": "2", "name": "Bash", "input": {"command": "python -c 'print(1)'"}}], 5, 5),
|
|
79
|
+
AssistantTurn("done, verified", [], 5, 5),
|
|
80
|
+
]
|
|
81
|
+
i = {"n": 0}
|
|
82
|
+
def fake(model, system, messages, tool_schemas, config):
|
|
83
|
+
t = turns[min(i["n"], len(turns) - 1)]; i["n"] += 1; yield t
|
|
84
|
+
st = agent.AgentState()
|
|
85
|
+
assert st.task.phase == "understand"
|
|
86
|
+
orig = agent.stream; agent.stream = fake
|
|
87
|
+
try:
|
|
88
|
+
list(agent.run("Fix it and verify.\n1. tests pass", st, cfg, "SYS"))
|
|
89
|
+
finally:
|
|
90
|
+
agent.stream = orig
|
|
91
|
+
assert st.task.phase == "complete" # understand -> implement -> verify -> complete
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|