claude-dev-env 8.21.0 → 8.23.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.
- package/.agents/skills/eli5/SKILL.md +7 -3
- package/.agents/skills/eli5/reference/task-seeds.md +5 -4
- package/.agents/skills/sync-dev-env/SKILL.md +51 -0
- package/.agents/skills/sync-dev-env/scripts/link_plugin_skills.py +211 -0
- package/.agents/skills/sync-dev-env/scripts/sync_dev_env_constants/__init__.py +0 -0
- package/.agents/skills/sync-dev-env/scripts/sync_dev_env_constants/config/__init__.py +1 -0
- package/.agents/skills/sync-dev-env/scripts/sync_dev_env_constants/config/constants.py +26 -0
- package/.agents/skills/sync-dev-env/scripts/sync_dev_env_constants/config/test_constants.py +19 -0
- package/.agents/skills/sync-dev-env/scripts/sync_dev_env_constants/test___init__.py +5 -0
- package/.agents/skills/sync-dev-env/scripts/test_link_plugin_skills.py +142 -0
- package/bin/ever-shipped-skills.mjs +1 -0
- package/bin/merge_settings_defaults.test.mjs +43 -0
- package/hooks/blocking/edit_marker_gate.py +74 -0
- package/hooks/blocking/reply_length_gate.py +119 -0
- package/hooks/blocking/test_edit_marker_gate.py +110 -0
- package/hooks/blocking/test_reply_length_gate.py +193 -0
- package/hooks/hooks.json +25 -0
- package/hooks/hooks_constants/advisor_rules_prompt_constants.py +43 -0
- package/hooks/hooks_constants/edit_marker_gate_constants.py +18 -0
- package/hooks/hooks_constants/reply_length_gate_constants.py +28 -0
- package/hooks/hooks_constants/test_edit_marker_gate_constants.py +9 -0
- package/hooks/hooks_constants/test_reply_length_gate_constants.py +15 -0
- package/hooks/session/advisor_rules_prompt.py +76 -0
- package/hooks/session/test_advisor_rules_prompt.py +86 -0
- package/package.json +1 -1
- package/rules/asd-ste100-language.md +1 -0
- package/rules/ci-owns-the-gate.md +28 -0
- package/rules/memory-stores-durable-facts.md +57 -0
- package/rules/review-closure-is-a-check.md +1 -0
- package/scripts/dev_env_scripts_constants/grok_worker_constants.py +4 -0
- package/scripts/dev_env_scripts_constants/review_closure_constants.py +13 -0
- package/scripts/dev_env_scripts_constants/shared_tree_constants.py +4 -0
- package/scripts/invoke_code_review.py +4 -1
- package/scripts/local_report_cli.py +50 -53
- package/scripts/policy_lint/adapter_configuration.py +8 -0
- package/scripts/policy_lint/config/constants.py +2 -0
- package/scripts/resolve_worker_spawn.py +4 -1
- package/scripts/review_closure_model.py +16 -3
- package/scripts/shared_tree_paths.py +27 -0
- package/scripts/test_resolve_worker_spawn.py +28 -0
- package/scripts/test_shared_tree_paths.py +27 -0
- package/scripts/tests/test_adapter_configuration.py +16 -0
- package/scripts/tests/test_local_report_publisher_cli.py +54 -0
- package/scripts/tests/test_review_closure.py +1 -0
- package/scripts/tests/test_review_closure_model.py +70 -0
|
@@ -21,6 +21,10 @@ from dev_env_scripts_constants.grok_worker_constants import ( # noqa: E402
|
|
|
21
21
|
SCRIPTS_DIRECTORY_NAME,
|
|
22
22
|
SHARED_PACKAGE_DIRECTORY_NAME,
|
|
23
23
|
)
|
|
24
|
+
from dev_env_scripts_constants.shared_tree_constants import (
|
|
25
|
+
ADVISOR_DIRECTORY_NAME,
|
|
26
|
+
TIER_MODEL_IDS_MODULE_FILENAME,
|
|
27
|
+
)
|
|
24
28
|
|
|
25
29
|
|
|
26
30
|
def _create_directory_link(*, from_link: Path, to_target: Path) -> None:
|
|
@@ -143,6 +147,29 @@ def test_missing_module_returns_unresolved_candidate(tmp_path: Path) -> None:
|
|
|
143
147
|
).is_file()
|
|
144
148
|
|
|
145
149
|
|
|
150
|
+
def test_junction_scripts_dir_finds_advisor_scripts_on_unresolved_parent(
|
|
151
|
+
tmp_path: Path,
|
|
152
|
+
) -> None:
|
|
153
|
+
module_file = _build_junction_scripts_layout(tmp_path)
|
|
154
|
+
expected_scripts_directory = (
|
|
155
|
+
tmp_path
|
|
156
|
+
/ "root"
|
|
157
|
+
/ SHARED_PACKAGE_DIRECTORY_NAME
|
|
158
|
+
/ ADVISOR_DIRECTORY_NAME
|
|
159
|
+
/ SCRIPTS_DIRECTORY_NAME
|
|
160
|
+
)
|
|
161
|
+
expected_scripts_directory.mkdir(parents=True)
|
|
162
|
+
(expected_scripts_directory / TIER_MODEL_IDS_MODULE_FILENAME).write_text(
|
|
163
|
+
"", encoding="utf-8"
|
|
164
|
+
)
|
|
165
|
+
resolved_scripts_directory = (
|
|
166
|
+
shared_tree_paths.resolve_shared_advisor_scripts_directory(
|
|
167
|
+
module_file, all_environment={}
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
assert resolved_scripts_directory == expected_scripts_directory
|
|
171
|
+
|
|
172
|
+
|
|
146
173
|
def test_generalized_resolver_serves_a_deeper_anchor(tmp_path: Path) -> None:
|
|
147
174
|
"""A caller three levels under the root resolves its own sub-package.
|
|
148
175
|
|
|
@@ -58,6 +58,22 @@ def test_new_step_note_gate_registration_is_clean(tmp_path: Path) -> None:
|
|
|
58
58
|
assert adapters.hook_configuration_diagnostics(current_document, tmp_path) == ()
|
|
59
59
|
|
|
60
60
|
|
|
61
|
+
def test_new_reply_length_gate_registration_is_clean(tmp_path: Path) -> None:
|
|
62
|
+
current_document = _hook_document(
|
|
63
|
+
["hooks/blocking/reply_length_gate.py"],
|
|
64
|
+
'{"hooks": {}}',
|
|
65
|
+
)
|
|
66
|
+
assert adapters.hook_configuration_diagnostics(current_document, tmp_path) == ()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_new_edit_marker_gate_registration_is_clean(tmp_path: Path) -> None:
|
|
70
|
+
current_document = _hook_document(
|
|
71
|
+
["hooks/blocking/edit_marker_gate.py"],
|
|
72
|
+
'{"hooks": {}}',
|
|
73
|
+
)
|
|
74
|
+
assert adapters.hook_configuration_diagnostics(current_document, tmp_path) == ()
|
|
75
|
+
|
|
76
|
+
|
|
61
77
|
def test_new_blocker_in_staged_change_is_rejected(tmp_path: Path) -> None:
|
|
62
78
|
current_document = _hook_document(
|
|
63
79
|
["hooks/blocking/new.py"],
|
|
@@ -6,6 +6,8 @@ import subprocess
|
|
|
6
6
|
import sys
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
9
11
|
SCRIPTS_DIRECTORY = Path(__file__).resolve().parents[1]
|
|
10
12
|
if str(SCRIPTS_DIRECTORY) not in sys.path:
|
|
11
13
|
sys.path.insert(0, str(SCRIPTS_DIRECTORY))
|
|
@@ -83,3 +85,55 @@ def test_publisher_script_displays_help_without_authentication() -> None:
|
|
|
83
85
|
|
|
84
86
|
assert completed_process.returncode == 0
|
|
85
87
|
assert "usage:" in completed_process.stdout
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _token_arguments(tmp_path: Path, variable_name: str) -> list[str]:
|
|
91
|
+
return [
|
|
92
|
+
"--api-url",
|
|
93
|
+
"http://127.0.0.1:9",
|
|
94
|
+
"--token-environment",
|
|
95
|
+
variable_name,
|
|
96
|
+
"--repository",
|
|
97
|
+
"owner/repository",
|
|
98
|
+
"--pull-number",
|
|
99
|
+
"7",
|
|
100
|
+
"--local-repo",
|
|
101
|
+
str(tmp_path),
|
|
102
|
+
"--manifest",
|
|
103
|
+
str(tmp_path / "manifest.json"),
|
|
104
|
+
"--report",
|
|
105
|
+
str(tmp_path / "report.json"),
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_cli_names_missing_token_variable_without_network_access(
|
|
110
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
111
|
+
) -> None:
|
|
112
|
+
monkeypatch.delenv("PUBLISHER_TEST_TOKEN", raising=False)
|
|
113
|
+
standard_output = io.StringIO()
|
|
114
|
+
standard_error = io.StringIO()
|
|
115
|
+
|
|
116
|
+
exit_code = main(
|
|
117
|
+
_token_arguments(tmp_path, "PUBLISHER_TEST_TOKEN"),
|
|
118
|
+
stdout=standard_output,
|
|
119
|
+
stderr=standard_error,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
assert exit_code == 3
|
|
123
|
+
assert standard_output.getvalue() == ""
|
|
124
|
+
assert "PUBLISHER_TEST_TOKEN" in standard_error.getvalue()
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def test_cli_token_path_reaches_the_api_without_app_settings(
|
|
128
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
129
|
+
) -> None:
|
|
130
|
+
monkeypatch.setenv("PUBLISHER_TEST_TOKEN", "token")
|
|
131
|
+
standard_error = io.StringIO()
|
|
132
|
+
|
|
133
|
+
exit_code = main(
|
|
134
|
+
_token_arguments(tmp_path, "PUBLISHER_TEST_TOKEN"), stderr=standard_error
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
assert exit_code == 3
|
|
138
|
+
assert "Missing publisher setting" not in standard_error.getvalue()
|
|
139
|
+
assert "refused" in standard_error.getvalue().lower()
|
|
@@ -4,6 +4,8 @@ import sys
|
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
7
9
|
SCRIPTS_DIRECTORY = Path(__file__).resolve().parents[1]
|
|
8
10
|
if str(SCRIPTS_DIRECTORY) not in sys.path:
|
|
9
11
|
sys.path.insert(0, str(SCRIPTS_DIRECTORY))
|
|
@@ -315,11 +317,13 @@ def top_level_comment(
|
|
|
315
317
|
edited_minute: int | None = None,
|
|
316
318
|
url: str = BOT_SUMMARY_URL,
|
|
317
319
|
is_bot: bool = True,
|
|
320
|
+
is_notice: bool = False,
|
|
318
321
|
) -> model.TopLevelComment:
|
|
319
322
|
return model.TopLevelComment(
|
|
320
323
|
identifier=posted_minute,
|
|
321
324
|
author_login=author_login,
|
|
322
325
|
is_bot=is_bot,
|
|
326
|
+
is_notice=is_notice,
|
|
323
327
|
created_at=at_minute(posted_minute),
|
|
324
328
|
updated_at=at_minute(posted_minute if edited_minute is None else edited_minute),
|
|
325
329
|
url=url,
|
|
@@ -445,6 +449,72 @@ def should_open_a_bot_comment_posted_after_the_driver_time() -> None:
|
|
|
445
449
|
)
|
|
446
450
|
|
|
447
451
|
|
|
452
|
+
def should_close_a_bot_notice_posted_after_the_driver_time() -> None:
|
|
453
|
+
comment = top_level_comment(REVIEW_BOT, 5, is_notice=True)
|
|
454
|
+
|
|
455
|
+
assert model.top_level_finding(comment, DRIVING_AGENT, at_minute(4)) is None
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
@pytest.mark.parametrize(
|
|
459
|
+
"body",
|
|
460
|
+
[
|
|
461
|
+
"No code changes since the last review \u2014 review skipped",
|
|
462
|
+
"[Code review](https://example.test/pull/7#issuecomment-1) by qodo was "
|
|
463
|
+
"updated up to the latest commit https://example.test/commit/abc",
|
|
464
|
+
"<!-- graphite-review-comment -->\n\n### Graphite AI review",
|
|
465
|
+
"<h3>PR Summary by Qodo</h3>\n\nBuild the default map per instance",
|
|
466
|
+
"\n<h3>Qodo is busy working</h3>\n\nCheck back in a few minutes.",
|
|
467
|
+
"\n<h3>Code Review by Qodo</h3>\n<code>\U0001f41e Bugs (0)</code>\n\n"
|
|
468
|
+
"<h3>Great, no issues found!</h3>\nQodo reviewed your code",
|
|
469
|
+
],
|
|
470
|
+
)
|
|
471
|
+
def should_read_a_bot_notice_as_a_notice(body: str) -> None:
|
|
472
|
+
comment = model.parse_top_level_comment(notice_record(body, "Bot"))
|
|
473
|
+
|
|
474
|
+
assert comment.is_notice
|
|
475
|
+
assert model.top_level_findings((comment,), DRIVING_AGENT) == ()
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def should_read_a_person_pasting_notice_text_as_a_comment() -> None:
|
|
479
|
+
comment = model.parse_top_level_comment(
|
|
480
|
+
notice_record("No code changes since the last review", "User")
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
assert not comment.is_notice
|
|
484
|
+
assert len(model.top_level_findings((comment,), DRIVING_AGENT)) == 1
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def should_read_a_bot_finding_as_a_comment() -> None:
|
|
488
|
+
comment = model.parse_top_level_comment(
|
|
489
|
+
notice_record("A finding: the loop never ends.", "Bot")
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
assert not comment.is_notice
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def should_read_a_qodo_review_with_bugs_as_a_comment() -> None:
|
|
496
|
+
comment = model.parse_top_level_comment(
|
|
497
|
+
notice_record(
|
|
498
|
+
"\n<h3>Code Review by Qodo</h3>\n<code>\U0001f41e Bugs (2)</code>\n\n"
|
|
499
|
+
"1. The loop never ends.",
|
|
500
|
+
"Bot",
|
|
501
|
+
)
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
assert not comment.is_notice
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def notice_record(body: str, user_type: str) -> dict[str, object]:
|
|
508
|
+
return {
|
|
509
|
+
"id": 1,
|
|
510
|
+
"user": {"login": REVIEW_BOT, "type": user_type},
|
|
511
|
+
"body": body,
|
|
512
|
+
"created_at": "2026-09-26T12:05:00+00:00",
|
|
513
|
+
"updated_at": "2026-09-26T12:05:00+00:00",
|
|
514
|
+
"html_url": BOT_SUMMARY_URL,
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
|
|
448
518
|
def should_count_waiting_top_level_comments_among_the_findings() -> None:
|
|
449
519
|
all_findings = model.all_open_findings(
|
|
450
520
|
(), (top_level_comment(REVIEW_BOT, 1),), DRIVING_AGENT, None
|