claude-dev-env 8.18.0 → 8.19.0

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.
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import sys
4
+ from datetime import datetime
4
5
  from pathlib import Path
5
6
 
6
7
  SCRIPTS_DIRECTORY = Path(__file__).resolve().parents[1]
@@ -13,6 +14,7 @@ from dev_env_scripts_constants.review_closure_constants import (
13
14
  APPROVALS_OPEN_REASON,
14
15
  RED_CIRCLE_MARKER,
15
16
  RED_CIRCLE_OPEN_REASON,
17
+ TOP_LEVEL_OPEN_REASON_TEMPLATE,
16
18
  UNANSWERED_OPEN_REASON,
17
19
  UNNAMED_THREAD_SUBJECT,
18
20
  )
@@ -134,7 +136,7 @@ def should_report_a_reply_from_the_driving_agent() -> None:
134
136
 
135
137
 
136
138
  def should_report_a_blocking_approvals_row_as_open() -> None:
137
- all_findings = model.all_open_findings((), DRIVING_AGENT, "failure")
139
+ all_findings = model.all_open_findings((), (), DRIVING_AGENT, "failure")
138
140
 
139
141
  assert all_findings == (
140
142
  model.OpenFinding(subject=APPROVALS_CHECK_NAME, reason=APPROVALS_OPEN_REASON),
@@ -144,11 +146,11 @@ def should_report_a_blocking_approvals_row_as_open() -> None:
144
146
  def should_pass_when_approvals_succeeds_and_no_thread_waits() -> None:
145
147
  answered = thread(all_comments=(bot_comment(), agent_comment()))
146
148
 
147
- assert model.all_open_findings((answered,), DRIVING_AGENT, "success") == ()
149
+ assert model.all_open_findings((answered,), (), DRIVING_AGENT, "success") == ()
148
150
 
149
151
 
150
152
  def should_pass_where_the_approvals_check_does_not_run() -> None:
151
- assert model.all_open_findings((), DRIVING_AGENT, None) == ()
153
+ assert model.all_open_findings((), (), DRIVING_AGENT, None) == ()
152
154
 
153
155
 
154
156
  def should_report_every_open_thread_and_the_approvals_row() -> None:
@@ -156,7 +158,7 @@ def should_report_every_open_thread_and_the_approvals_row() -> None:
156
158
  second = thread(all_comments=(bot_comment(),), subject="scripts/second.py")
157
159
 
158
160
  all_findings = model.all_open_findings(
159
- (first, second), DRIVING_AGENT, "action_required"
161
+ (first, second), (), DRIVING_AGENT, "action_required"
160
162
  )
161
163
 
162
164
  assert [each.subject for each in all_findings] == [
@@ -297,3 +299,138 @@ def should_key_the_review_comments_by_identifier() -> None:
297
299
  )
298
300
 
299
301
  assert all_comment_records == {11: {"id": 11, "body": "A finding."}}
302
+
303
+
304
+ BOT_SUMMARY_URL = "https://github.com/jl-cmd/claude-dev-env/pull/7#issuecomment-1"
305
+
306
+
307
+ def at_minute(minute: int) -> datetime:
308
+ return datetime.fromisoformat(f"2026-09-26T12:{minute:02d}:00+00:00")
309
+
310
+
311
+ def top_level_comment(
312
+ author_login: str,
313
+ posted_minute: int,
314
+ edited_minute: int | None = None,
315
+ url: str = BOT_SUMMARY_URL,
316
+ ) -> model.TopLevelComment:
317
+ return model.TopLevelComment(
318
+ identifier=posted_minute,
319
+ author_login=author_login,
320
+ created_at=at_minute(posted_minute),
321
+ updated_at=at_minute(posted_minute if edited_minute is None else edited_minute),
322
+ url=url,
323
+ )
324
+
325
+
326
+ def should_report_a_bot_top_level_comment_with_no_driver_comment() -> None:
327
+ all_findings = model.top_level_findings(
328
+ (top_level_comment(REVIEW_BOT, 1),), DRIVING_AGENT
329
+ )
330
+
331
+ assert all_findings == (
332
+ model.OpenFinding(
333
+ subject=BOT_SUMMARY_URL,
334
+ reason=TOP_LEVEL_OPEN_REASON_TEMPLATE.format(author=REVIEW_BOT),
335
+ ),
336
+ )
337
+
338
+
339
+ def should_close_a_bot_top_level_comment_the_driver_posted_after() -> None:
340
+ all_comments = (
341
+ top_level_comment(REVIEW_BOT, 1),
342
+ top_level_comment("claude[bot]", 2),
343
+ )
344
+
345
+ assert model.top_level_findings(all_comments, DRIVING_AGENT) == ()
346
+
347
+
348
+ def should_close_every_earlier_comment_with_one_driver_comment() -> None:
349
+ all_comments = (
350
+ top_level_comment(REVIEW_BOT, 1),
351
+ top_level_comment("graphite-app[bot]", 2),
352
+ top_level_comment("claude[bot]", 3),
353
+ )
354
+
355
+ assert model.top_level_findings(all_comments, DRIVING_AGENT) == ()
356
+
357
+
358
+ def should_reopen_a_bot_comment_edited_after_the_driver_reply() -> None:
359
+ all_comments = (
360
+ top_level_comment(REVIEW_BOT, 1, edited_minute=5),
361
+ top_level_comment("claude[bot]", 2),
362
+ )
363
+
364
+ all_findings = model.top_level_findings(all_comments, DRIVING_AGENT)
365
+
366
+ assert [each.subject for each in all_findings] == [BOT_SUMMARY_URL]
367
+
368
+
369
+ def should_leave_a_bot_comment_posted_after_the_driver_open() -> None:
370
+ all_comments = (
371
+ top_level_comment("claude[bot]", 1),
372
+ top_level_comment(REVIEW_BOT, 2),
373
+ )
374
+
375
+ assert len(model.top_level_findings(all_comments, DRIVING_AGENT)) == 1
376
+
377
+
378
+ def should_never_report_a_driver_top_level_comment() -> None:
379
+ all_comments = (top_level_comment("claude[bot]", 1, edited_minute=9),)
380
+
381
+ assert model.top_level_findings(all_comments, DRIVING_AGENT) == ()
382
+
383
+
384
+ def should_find_the_latest_driver_top_level_comment_time() -> None:
385
+ all_comments = (
386
+ top_level_comment("claude[bot]", 1),
387
+ top_level_comment(REVIEW_BOT, 2),
388
+ top_level_comment("claude[bot]", 3, edited_minute=9),
389
+ )
390
+
391
+ assert model.latest_driver_comment_time(all_comments, DRIVING_AGENT) == at_minute(3)
392
+
393
+
394
+ def should_find_no_driver_time_when_the_driver_posted_nothing() -> None:
395
+ all_comments = (top_level_comment(REVIEW_BOT, 1),)
396
+
397
+ assert model.latest_driver_comment_time(all_comments, DRIVING_AGENT) is None
398
+
399
+
400
+ def should_close_a_bot_comment_last_edited_at_the_driver_time() -> None:
401
+ comment = top_level_comment(REVIEW_BOT, 1, edited_minute=4)
402
+
403
+ assert model.top_level_finding(comment, DRIVING_AGENT, at_minute(4)) is None
404
+
405
+
406
+ def should_open_a_bot_comment_edited_after_the_driver_time() -> None:
407
+ comment = top_level_comment(REVIEW_BOT, 1, edited_minute=5)
408
+
409
+ assert model.top_level_finding(
410
+ comment, DRIVING_AGENT, at_minute(4)
411
+ ) == model.OpenFinding(
412
+ subject=BOT_SUMMARY_URL,
413
+ reason=TOP_LEVEL_OPEN_REASON_TEMPLATE.format(author=REVIEW_BOT),
414
+ )
415
+
416
+
417
+ def should_count_waiting_top_level_comments_among_the_findings() -> None:
418
+ all_findings = model.all_open_findings(
419
+ (), (top_level_comment(REVIEW_BOT, 1),), DRIVING_AGENT, None
420
+ )
421
+
422
+ assert [each.subject for each in all_findings] == [BOT_SUMMARY_URL]
423
+
424
+
425
+ def should_read_a_top_level_comment_record() -> None:
426
+ comment = model.parse_top_level_comment(
427
+ {
428
+ "id": 1,
429
+ "user": {"login": REVIEW_BOT},
430
+ "created_at": "2026-09-26T12:01:00Z",
431
+ "updated_at": "2026-09-26T12:05:00Z",
432
+ "html_url": BOT_SUMMARY_URL,
433
+ }
434
+ )
435
+
436
+ assert comment == top_level_comment(REVIEW_BOT, 1, edited_minute=5)