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.
Files changed (140) hide show
  1. {drydock_cli-3.0.127/drydock_cli.egg-info → drydock_cli-3.0.129}/PKG-INFO +1 -1
  2. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/agent.py +2 -0
  3. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/events.py +51 -0
  4. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/tui/app.py +27 -1
  5. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/tui/widgets.py +1 -1
  6. {drydock_cli-3.0.127 → drydock_cli-3.0.129/drydock_cli.egg-info}/PKG-INFO +1 -1
  7. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/pyproject.toml +1 -1
  8. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_events.py +33 -0
  9. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_task_state.py +22 -0
  10. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/LICENSE +0 -0
  11. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/NOTICE +0 -0
  12. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/README.md +0 -0
  13. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/__init__.py +0 -0
  14. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/__main__.py +0 -0
  15. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/advisor.py +0 -0
  16. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/bash_safety.py +0 -0
  17. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/builtin_skills/__init__.py +0 -0
  18. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/builtin_skills/ml-data.md +0 -0
  19. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/builtin_skills/ml-debug.md +0 -0
  20. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/builtin_skills/ml-finetune.md +0 -0
  21. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/builtin_skills/ml-metrics.md +0 -0
  22. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/builtin_skills/ml-rl.md +0 -0
  23. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/builtin_skills/ml-train.md +0 -0
  24. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/builtin_skills/rmf-categorize.md +0 -0
  25. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/builtin_skills/rmf-control.md +0 -0
  26. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/builtin_skills/rmf-poam.md +0 -0
  27. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/builtin_skills/rmf-review.md +0 -0
  28. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/builtin_skills/stig-assess.md +0 -0
  29. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/builtin_skills/stig-remediate.md +0 -0
  30. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/cci.py +0 -0
  31. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/cli.py +0 -0
  32. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/compaction.py +0 -0
  33. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/config.py +0 -0
  34. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/detect.py +0 -0
  35. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/extract.py +0 -0
  36. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/gittools.py +0 -0
  37. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/graphrag.py +0 -0
  38. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/guards.py +0 -0
  39. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/loop_detect.py +0 -0
  40. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/mcp.py +0 -0
  41. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/poam.py +0 -0
  42. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/providers.py +0 -0
  43. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/recipes.py +0 -0
  44. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/rmf.py +0 -0
  45. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/rmf_graph.py +0 -0
  46. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/skills.py +0 -0
  47. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/stig.py +0 -0
  48. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/suggest.py +0 -0
  49. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/task_state.py +0 -0
  50. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/tool_registry.py +0 -0
  51. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/tools/__init__.py +0 -0
  52. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/tui/__init__.py +0 -0
  53. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/tui/approval.py +0 -0
  54. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/tui/messages.py +0 -0
  55. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/tuning.py +0 -0
  56. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/verification.py +0 -0
  57. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock/web.py +0 -0
  58. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock_cli.egg-info/SOURCES.txt +0 -0
  59. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock_cli.egg-info/dependency_links.txt +0 -0
  60. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock_cli.egg-info/entry_points.txt +0 -0
  61. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock_cli.egg-info/requires.txt +0 -0
  62. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/drydock_cli.egg-info/top_level.txt +0 -0
  63. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/setup.cfg +0 -0
  64. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_advisor.py +0 -0
  65. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_approval.py +0 -0
  66. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_back_command.py +0 -0
  67. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_bash_background.py +0 -0
  68. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_bash_binary_output.py +0 -0
  69. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_bash_crossplatform.py +0 -0
  70. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_bash_output_bounding.py +0 -0
  71. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_bash_process_group.py +0 -0
  72. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_bash_safety.py +0 -0
  73. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_bash_sanitize.py +0 -0
  74. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_bash_shell.py +0 -0
  75. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_bash_stdin.py +0 -0
  76. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_bash_stop_partial.py +0 -0
  77. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_bash_timeout_network.py +0 -0
  78. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_bash_timeout_param.py +0 -0
  79. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_cci.py +0 -0
  80. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_cli_agents.py +0 -0
  81. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_compact_command.py +0 -0
  82. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_compaction.py +0 -0
  83. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_config.py +0 -0
  84. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_config_migration.py +0 -0
  85. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_context_limit_config.py +0 -0
  86. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_detect.py +0 -0
  87. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_dispatch.py +0 -0
  88. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_e2e_connected.py +0 -0
  89. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_edit_replace_all.py +0 -0
  90. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_empty_response.py +0 -0
  91. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_extract.py +0 -0
  92. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_failure_loop.py +0 -0
  93. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_first_run_setup.py +0 -0
  94. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_gittools.py +0 -0
  95. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_glob_edit_edges.py +0 -0
  96. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_graphify_example.py +0 -0
  97. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_graphrag.py +0 -0
  98. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_graphrag_quoted_path.py +0 -0
  99. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_grep_and_read_robust.py +0 -0
  100. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_guards_and_tools.py +0 -0
  101. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_hallucinated_tools.py +0 -0
  102. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_leaked_tool_call.py +0 -0
  103. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_loop_detect.py +0 -0
  104. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_mcp.py +0 -0
  105. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_oneshot_unreachable.py +0 -0
  106. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_overthink_interrupt.py +0 -0
  107. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_plan_autocontinue.py +0 -0
  108. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_poam.py +0 -0
  109. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_providers_unreachable.py +0 -0
  110. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_read_index.py +0 -0
  111. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_recipes.py +0 -0
  112. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_repetition_interrupt.py +0 -0
  113. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_rmf.py +0 -0
  114. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_rmf_graph.py +0 -0
  115. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_rmf_stig_graph.py +0 -0
  116. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_runaway_repetition.py +0 -0
  117. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_screenshot.py +0 -0
  118. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_server_probe.py +0 -0
  119. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_skills.py +0 -0
  120. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_stall_retry.py +0 -0
  121. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_stig.py +0 -0
  122. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_stop.py +0 -0
  123. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_streaming_newlines.py +0 -0
  124. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_subagent.py +0 -0
  125. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_suggest.py +0 -0
  126. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_system_prompt_help.py +0 -0
  127. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_todo.py +0 -0
  128. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_tool_arg_coercion.py +0 -0
  129. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_tool_arg_coercion_more.py +0 -0
  130. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_tool_arg_parsing.py +0 -0
  131. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_tools_undo.py +0 -0
  132. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_tui.py +0 -0
  133. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_tuning.py +0 -0
  134. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_verification_gate.py +0 -0
  135. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_viewimage.py +0 -0
  136. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_vision_input.py +0 -0
  137. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_web_tools.py +0 -0
  138. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_windows_shell.py +0 -0
  139. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_write_content_coerce.py +0 -0
  140. {drydock_cli-3.0.127 → drydock_cli-3.0.129}/tests/test_xccdf.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drydock-cli
3
- Version: 3.0.127
3
+ Version: 3.0.129
4
4
  Summary: Drydock — a local, provider-agnostic terminal coding agent for local LLMs
5
5
  Author: Frank Bobe III
6
6
  License-Expression: Apache-2.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
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drydock-cli
3
- Version: 3.0.127
3
+ Version: 3.0.129
4
4
  Summary: Drydock — a local, provider-agnostic terminal coding agent for local LLMs
5
5
  Author: Frank Bobe III
6
6
  License-Expression: Apache-2.0
@@ -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.127"
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