claude-dev-env 1.79.0 → 1.81.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/_shared/pr-loop/scripts/CLAUDE.md +3 -1
- package/_shared/pr-loop/scripts/code_rules_gate.py +116 -30
- package/_shared/pr-loop/scripts/copilot_quota.py +360 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +2 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +13 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/copilot_quota_constants.py +24 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +113 -0
- package/_shared/pr-loop/scripts/terminology_sweep.py +467 -0
- package/_shared/pr-loop/scripts/tests/CLAUDE.md +8 -0
- package/_shared/pr-loop/scripts/tests/fixtures/copilot_internal_user_jonecho.json +76 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +339 -0
- package/_shared/pr-loop/scripts/tests/test_copilot_quota.py +242 -0
- package/_shared/pr-loop/scripts/tests/test_copilot_quota_constants.py +63 -0
- package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +297 -0
- package/audit-rubrics/CLAUDE.md +3 -2
- package/audit-rubrics/category_rubrics/CLAUDE.md +2 -1
- package/audit-rubrics/category_rubrics/category-a-api-contracts.md +2 -1
- package/audit-rubrics/category_rubrics/category-j-code-rules-compliance.md +13 -1
- package/audit-rubrics/category_rubrics/category-p-name-vs-behavior-contract.md +19 -0
- package/audit-rubrics/category_rubrics/category-q-cross-surface-claims.md +46 -0
- package/audit-rubrics/prompts/CLAUDE.md +2 -1
- package/audit-rubrics/prompts/category-a-api-contracts.md +1 -0
- package/audit-rubrics/prompts/category-j-code-rules-compliance.md +19 -7
- package/audit-rubrics/prompts/category-p-name-vs-behavior-contract.md +14 -0
- package/audit-rubrics/prompts/category-q-cross-surface-claims.md +51 -0
- package/docs/CODE_RULES.md +2 -2
- package/hooks/blocking/CLAUDE.md +2 -0
- package/hooks/blocking/code_rules_annotations_length.py +59 -11
- package/hooks/blocking/code_rules_banned_identifiers.py +48 -9
- package/hooks/blocking/code_rules_command_dispatch.py +140 -0
- package/hooks/blocking/code_rules_docstrings.py +93 -0
- package/hooks/blocking/code_rules_enforcer.py +58 -4
- package/hooks/blocking/code_rules_imports_logging.py +136 -1
- package/hooks/blocking/code_rules_js_conventions.py +246 -0
- package/hooks/blocking/code_rules_naming_collection.py +5 -0
- package/hooks/blocking/code_rules_test_assertions.py +3 -0
- package/hooks/blocking/code_rules_unused_imports.py +7 -66
- package/hooks/blocking/duplicate_rmtree_helper_blocker.py +7 -0
- package/hooks/blocking/test_code_rules_command_dispatch.py +95 -0
- package/hooks/blocking/test_code_rules_enforcer_annotations.py +20 -2
- package/hooks/blocking/test_code_rules_enforcer_banned_identifier.py +10 -2
- package/hooks/blocking/test_code_rules_enforcer_banned_import_alias.py +8 -4
- package/hooks/blocking/test_code_rules_enforcer_dispatch_wiring.py +9 -3
- package/hooks/blocking/test_code_rules_enforcer_docstring_type_checking_gate.py +164 -0
- package/hooks/blocking/test_code_rules_enforcer_js_returns_object.py +72 -0
- package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +112 -18
- package/hooks/blocking/test_code_rules_js_conventions.py +167 -0
- package/hooks/blocking/test_code_rules_js_returns_object_schemaless.py +167 -0
- package/hooks/hooks_constants/CLAUDE.md +2 -0
- package/hooks/hooks_constants/blocking_check_limits.py +10 -0
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +14 -0
- package/hooks/hooks_constants/command_dispatch_constants.py +28 -0
- package/hooks/hooks_constants/js_conventions_constants.py +54 -0
- package/hooks/hooks_constants/test_code_rules_enforcer_constants.py +93 -0
- package/hooks/hooks_constants/unused_module_import_constants.py +0 -1
- package/package.json +1 -1
- package/rules/docstring-prose-matches-implementation.md +3 -1
- package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +43 -1
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +2 -2
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +1 -4
- package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +100 -4
- package/skills/autoconverge/SKILL.md +28 -7
- package/skills/autoconverge/reference/convergence.md +3 -3
- package/skills/autoconverge/reference/stop-conditions.md +1 -1
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +4 -4
- package/skills/autoconverge/workflow/converge.contract.test.mjs +58 -122
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +289 -5
- package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +50 -46
- package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +6 -6
- package/skills/autoconverge/workflow/converge.mjs +235 -247
- package/skills/autoconverge/workflow/converge.run-input.test.mjs +11 -0
- package/skills/autoconverge/workflow/converge_multi.mjs +23 -7
- package/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +28 -2
- package/skills/pr-converge/SKILL.md +28 -2
- package/skills/pr-converge/reference/convergence-gates.md +13 -1
- package/skills/pr-converge/reference/state-schema.md +11 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""Behavioral tests for the commit-time terminology sweep."""
|
|
2
|
+
|
|
3
|
+
import importlib.util
|
|
4
|
+
import subprocess
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from types import ModuleType
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _load_sweep_module() -> ModuleType:
|
|
10
|
+
module_path = Path(__file__).parent.parent / "terminology_sweep.py"
|
|
11
|
+
spec = importlib.util.spec_from_file_location("terminology_sweep", module_path)
|
|
12
|
+
assert spec is not None
|
|
13
|
+
assert spec.loader is not None
|
|
14
|
+
module = importlib.util.module_from_spec(spec)
|
|
15
|
+
spec.loader.exec_module(module)
|
|
16
|
+
return module
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
sweep_module = _load_sweep_module()
|
|
20
|
+
sweep_diff = sweep_module.sweep_diff
|
|
21
|
+
staged_terminology_findings = sweep_module.staged_terminology_findings
|
|
22
|
+
main = sweep_module.main
|
|
23
|
+
parse_added_lines = sweep_module._parse_added_lines
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _init_git_repository(repository_path: Path) -> None:
|
|
27
|
+
for each_command in (
|
|
28
|
+
["git", "init"],
|
|
29
|
+
["git", "config", "user.email", "test@example.com"],
|
|
30
|
+
["git", "config", "user.name", "Test"],
|
|
31
|
+
):
|
|
32
|
+
subprocess.run(each_command, cwd=repository_path, check=True, capture_output=True)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
CODE_AND_PROSE_DIFF = (
|
|
36
|
+
"diff --git a/api/quota.py b/api/quota.py\n"
|
|
37
|
+
"--- a/api/quota.py\n"
|
|
38
|
+
"+++ b/api/quota.py\n"
|
|
39
|
+
"@@ -0,0 +1,2 @@\n"
|
|
40
|
+
"+def read_quota(account):\n"
|
|
41
|
+
'+ return account["premium_interactions"]\n'
|
|
42
|
+
"diff --git a/docs/README.md b/docs/README.md\n"
|
|
43
|
+
"--- a/docs/README.md\n"
|
|
44
|
+
"+++ b/docs/README.md\n"
|
|
45
|
+
"@@ -0,0 +1,1 @@\n"
|
|
46
|
+
"+The premium-request budget gates the run.\n"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_flags_prose_near_miss_of_code_identifier() -> None:
|
|
51
|
+
findings = sweep_diff(CODE_AND_PROSE_DIFF)
|
|
52
|
+
assert len(findings) == 1
|
|
53
|
+
assert "docs/README.md:1" in findings[0]
|
|
54
|
+
assert "premium-request" in findings[0]
|
|
55
|
+
assert "premium_interactions" in findings[0]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_does_not_flag_exact_hyphen_variant() -> None:
|
|
59
|
+
diff = (
|
|
60
|
+
"diff --git a/api/quota.py b/api/quota.py\n"
|
|
61
|
+
"--- a/api/quota.py\n"
|
|
62
|
+
"+++ b/api/quota.py\n"
|
|
63
|
+
"@@ -0,0 +1,1 @@\n"
|
|
64
|
+
"+premium_interactions = 5\n"
|
|
65
|
+
"diff --git a/docs/README.md b/docs/README.md\n"
|
|
66
|
+
"--- a/docs/README.md\n"
|
|
67
|
+
"+++ b/docs/README.md\n"
|
|
68
|
+
"@@ -0,0 +1,1 @@\n"
|
|
69
|
+
"+The premium-interactions budget gates the run.\n"
|
|
70
|
+
)
|
|
71
|
+
assert sweep_diff(diff) == []
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _code_and_prose_collision_diff(code_identifier: str, prose_compound: str) -> str:
|
|
75
|
+
return (
|
|
76
|
+
"diff --git a/api/quota.py b/api/quota.py\n"
|
|
77
|
+
"--- a/api/quota.py\n"
|
|
78
|
+
"+++ b/api/quota.py\n"
|
|
79
|
+
"@@ -0,0 +1,1 @@\n"
|
|
80
|
+
f"+{code_identifier} = 5\n"
|
|
81
|
+
"diff --git a/docs/README.md b/docs/README.md\n"
|
|
82
|
+
"--- a/docs/README.md\n"
|
|
83
|
+
"+++ b/docs/README.md\n"
|
|
84
|
+
"@@ -0,0 +1,1 @@\n"
|
|
85
|
+
f"+This is a {prose_compound} cache.\n"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_does_not_flag_english_compound_tail_read_only() -> None:
|
|
90
|
+
diff = _code_and_prose_collision_diff("read_config", "read-only")
|
|
91
|
+
assert sweep_diff(diff) == []
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_does_not_flag_english_compound_tail_data_driven() -> None:
|
|
95
|
+
diff = _code_and_prose_collision_diff("data_source", "data-driven")
|
|
96
|
+
assert sweep_diff(diff) == []
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_does_not_flag_english_compound_tail_type_safe() -> None:
|
|
100
|
+
diff = _code_and_prose_collision_diff("type_check", "type-safe")
|
|
101
|
+
assert sweep_diff(diff) == []
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_does_not_flag_english_compound_tail_test_driven() -> None:
|
|
105
|
+
diff = _code_and_prose_collision_diff("test_case", "test-driven")
|
|
106
|
+
assert sweep_diff(diff) == []
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_does_not_flag_english_compound_tail_high_quality() -> None:
|
|
110
|
+
diff = _code_and_prose_collision_diff("high_level", "high-quality")
|
|
111
|
+
assert sweep_diff(diff) == []
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def test_does_not_flag_unrelated_hyphenated_prose() -> None:
|
|
115
|
+
diff = (
|
|
116
|
+
"diff --git a/api/quota.py b/api/quota.py\n"
|
|
117
|
+
"--- a/api/quota.py\n"
|
|
118
|
+
"+++ b/api/quota.py\n"
|
|
119
|
+
"@@ -0,0 +1,1 @@\n"
|
|
120
|
+
"+premium_interactions = 5\n"
|
|
121
|
+
"diff --git a/docs/README.md b/docs/README.md\n"
|
|
122
|
+
"--- a/docs/README.md\n"
|
|
123
|
+
"+++ b/docs/README.md\n"
|
|
124
|
+
"@@ -0,0 +1,1 @@\n"
|
|
125
|
+
"+A well-known open-source project.\n"
|
|
126
|
+
)
|
|
127
|
+
assert sweep_diff(diff) == []
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_does_not_flag_plural_prose_of_singular_identifier() -> None:
|
|
131
|
+
diff = (
|
|
132
|
+
"diff --git a/api/quota.py b/api/quota.py\n"
|
|
133
|
+
"--- a/api/quota.py\n"
|
|
134
|
+
"+++ b/api/quota.py\n"
|
|
135
|
+
"@@ -0,0 +1,1 @@\n"
|
|
136
|
+
"+test_file = 5\n"
|
|
137
|
+
"diff --git a/docs/README.md b/docs/README.md\n"
|
|
138
|
+
"--- a/docs/README.md\n"
|
|
139
|
+
"+++ b/docs/README.md\n"
|
|
140
|
+
"@@ -0,0 +1,1 @@\n"
|
|
141
|
+
"+We scan the test files for coverage.\n"
|
|
142
|
+
)
|
|
143
|
+
assert sweep_diff(diff) == []
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def test_does_not_flag_plural_prose_of_singular_identifier_audit_prompt() -> None:
|
|
147
|
+
diff = (
|
|
148
|
+
"diff --git a/api/quota.py b/api/quota.py\n"
|
|
149
|
+
"--- a/api/quota.py\n"
|
|
150
|
+
"+++ b/api/quota.py\n"
|
|
151
|
+
"@@ -0,0 +1,1 @@\n"
|
|
152
|
+
"+audit_prompt = 5\n"
|
|
153
|
+
"diff --git a/docs/README.md b/docs/README.md\n"
|
|
154
|
+
"--- a/docs/README.md\n"
|
|
155
|
+
"+++ b/docs/README.md\n"
|
|
156
|
+
"@@ -0,0 +1,1 @@\n"
|
|
157
|
+
"+The audit prompts drive each round.\n"
|
|
158
|
+
)
|
|
159
|
+
assert sweep_diff(diff) == []
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def test_does_not_flag_singular_prose_of_plural_identifier() -> None:
|
|
163
|
+
diff = (
|
|
164
|
+
"diff --git a/api/quota.py b/api/quota.py\n"
|
|
165
|
+
"--- a/api/quota.py\n"
|
|
166
|
+
"+++ b/api/quota.py\n"
|
|
167
|
+
"@@ -0,0 +1,1 @@\n"
|
|
168
|
+
"+audit_prompts = 5\n"
|
|
169
|
+
"diff --git a/docs/README.md b/docs/README.md\n"
|
|
170
|
+
"--- a/docs/README.md\n"
|
|
171
|
+
"+++ b/docs/README.md\n"
|
|
172
|
+
"@@ -0,0 +1,1 @@\n"
|
|
173
|
+
"+The audit prompt drives each round.\n"
|
|
174
|
+
)
|
|
175
|
+
assert sweep_diff(diff) == []
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_does_not_flag_y_to_ies_plural_variant() -> None:
|
|
179
|
+
diff = (
|
|
180
|
+
"diff --git a/api/quota.py b/api/quota.py\n"
|
|
181
|
+
"--- a/api/quota.py\n"
|
|
182
|
+
"+++ b/api/quota.py\n"
|
|
183
|
+
"@@ -0,0 +1,1 @@\n"
|
|
184
|
+
"+retry_policy = 5\n"
|
|
185
|
+
"diff --git a/docs/README.md b/docs/README.md\n"
|
|
186
|
+
"--- a/docs/README.md\n"
|
|
187
|
+
"+++ b/docs/README.md\n"
|
|
188
|
+
"@@ -0,0 +1,1 @@\n"
|
|
189
|
+
"+The retry policies bound each attempt.\n"
|
|
190
|
+
)
|
|
191
|
+
assert sweep_diff(diff) == []
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def test_still_flags_non_plural_divergent_tail() -> None:
|
|
195
|
+
diff = (
|
|
196
|
+
"diff --git a/api/quota.py b/api/quota.py\n"
|
|
197
|
+
"--- a/api/quota.py\n"
|
|
198
|
+
"+++ b/api/quota.py\n"
|
|
199
|
+
"@@ -0,0 +1,1 @@\n"
|
|
200
|
+
"+premium_interactions = 5\n"
|
|
201
|
+
"diff --git a/docs/README.md b/docs/README.md\n"
|
|
202
|
+
"--- a/docs/README.md\n"
|
|
203
|
+
"+++ b/docs/README.md\n"
|
|
204
|
+
"@@ -0,0 +1,1 @@\n"
|
|
205
|
+
"+The premium request budget gates the run.\n"
|
|
206
|
+
)
|
|
207
|
+
findings = sweep_diff(diff)
|
|
208
|
+
assert len(findings) == 1
|
|
209
|
+
assert "premium request" in findings[0]
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def test_flags_near_miss_inside_code_comment() -> None:
|
|
213
|
+
diff = (
|
|
214
|
+
"diff --git a/api/quota.py b/api/quota.py\n"
|
|
215
|
+
"--- a/api/quota.py\n"
|
|
216
|
+
"+++ b/api/quota.py\n"
|
|
217
|
+
"@@ -0,0 +1,2 @@\n"
|
|
218
|
+
"+premium_interactions = 5\n"
|
|
219
|
+
"+# the premium-request path resets the counter\n"
|
|
220
|
+
)
|
|
221
|
+
findings = sweep_diff(diff)
|
|
222
|
+
assert len(findings) == 1
|
|
223
|
+
assert "premium-request" in findings[0]
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def test_no_findings_when_no_multiword_identifier_introduced() -> None:
|
|
227
|
+
diff = (
|
|
228
|
+
"diff --git a/docs/README.md b/docs/README.md\n"
|
|
229
|
+
"--- a/docs/README.md\n"
|
|
230
|
+
"+++ b/docs/README.md\n"
|
|
231
|
+
"@@ -0,0 +1,1 @@\n"
|
|
232
|
+
"+The premium-request budget gates the run.\n"
|
|
233
|
+
)
|
|
234
|
+
assert sweep_diff(diff) == []
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def test_main_exits_one_when_findings(tmp_path: Path) -> None:
|
|
238
|
+
diff_file = tmp_path / "change.diff"
|
|
239
|
+
diff_file.write_text(CODE_AND_PROSE_DIFF, encoding="utf-8")
|
|
240
|
+
exit_code = main(["--diff-file", str(diff_file)])
|
|
241
|
+
assert exit_code == 1
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def test_main_exits_zero_when_clean(tmp_path: Path) -> None:
|
|
245
|
+
diff_file = tmp_path / "clean.diff"
|
|
246
|
+
diff_file.write_text(
|
|
247
|
+
"diff --git a/docs/README.md b/docs/README.md\n"
|
|
248
|
+
"--- a/docs/README.md\n"
|
|
249
|
+
"+++ b/docs/README.md\n"
|
|
250
|
+
"@@ -0,0 +1,1 @@\n"
|
|
251
|
+
"+Nothing to see here.\n",
|
|
252
|
+
encoding="utf-8",
|
|
253
|
+
)
|
|
254
|
+
exit_code = main(["--diff-file", str(diff_file)])
|
|
255
|
+
assert exit_code == 0
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def test_staged_terminology_findings_flags_staged_prose(tmp_path: Path) -> None:
|
|
259
|
+
_init_git_repository(tmp_path)
|
|
260
|
+
(tmp_path / "quota.py").write_text(
|
|
261
|
+
"premium_interactions = 5\n", encoding="utf-8"
|
|
262
|
+
)
|
|
263
|
+
(tmp_path / "README.md").write_text(
|
|
264
|
+
"The premium-request budget gates the run.\n", encoding="utf-8"
|
|
265
|
+
)
|
|
266
|
+
subprocess.run(["git", "add", "-A"], cwd=tmp_path, check=True, capture_output=True)
|
|
267
|
+
findings = staged_terminology_findings(tmp_path)
|
|
268
|
+
assert any("premium-request" in each_finding for each_finding in findings)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def test_staged_terminology_findings_empty_when_clean(tmp_path: Path) -> None:
|
|
272
|
+
_init_git_repository(tmp_path)
|
|
273
|
+
(tmp_path / "README.md").write_text("Nothing notable here.\n", encoding="utf-8")
|
|
274
|
+
subprocess.run(["git", "add", "-A"], cwd=tmp_path, check=True, capture_output=True)
|
|
275
|
+
assert staged_terminology_findings(tmp_path) == []
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def test_parse_added_lines_counts_pre_increment_content_as_added_line() -> None:
|
|
279
|
+
diff_with_pre_increment = (
|
|
280
|
+
"diff --git a/app.mjs b/app.mjs\n"
|
|
281
|
+
"--- a/app.mjs\n"
|
|
282
|
+
"+++ b/app.mjs\n"
|
|
283
|
+
"@@ -0,0 +1,3 @@\n"
|
|
284
|
+
"+++counter;\n"
|
|
285
|
+
"+let first_total = read_first_value();\n"
|
|
286
|
+
"+let second_total = read_second_value();\n"
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
all_added_lines = parse_added_lines(diff_with_pre_increment)
|
|
290
|
+
|
|
291
|
+
assert [
|
|
292
|
+
(each_line_number, each_text) for _, each_line_number, each_text in all_added_lines
|
|
293
|
+
] == [
|
|
294
|
+
(1, "++counter;"),
|
|
295
|
+
(2, "let first_total = read_first_value();"),
|
|
296
|
+
(3, "let second_total = read_second_value();"),
|
|
297
|
+
]
|
package/audit-rubrics/CLAUDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# audit-rubrics
|
|
2
2
|
|
|
3
|
-
Audit rubrics for the PR-loop code-review suite. The rubrics define the
|
|
3
|
+
Audit rubrics for the PR-loop code-review suite. The rubrics define the 17 bug categories (A–Q), their sub-bucket decompositions, and the prompt templates agents use during an audit pass. Installed into `~/.claude/audit-rubrics/` by `bin/install.mjs`.
|
|
4
4
|
|
|
5
5
|
## Key file
|
|
6
6
|
|
|
@@ -12,7 +12,7 @@ Audit rubrics for the PR-loop code-review suite. The rubrics define the 16 bug c
|
|
|
12
12
|
|
|
13
13
|
| Entry | Description |
|
|
14
14
|
|---|---|
|
|
15
|
-
| `category_rubrics/` | One `.md` per category (A–
|
|
15
|
+
| `category_rubrics/` | One `.md` per category (A–Q): defines what the category audits, example findings, and a sub-bucket decomposition table |
|
|
16
16
|
| `prompts/` | One `.md` per category: the ready-to-use audit prompt template an agent inlines the artifact into |
|
|
17
17
|
|
|
18
18
|
## Categories
|
|
@@ -35,6 +35,7 @@ Audit rubrics for the PR-loop code-review suite. The rubrics define the 16 bug c
|
|
|
35
35
|
| N | Test name / scenario verifier |
|
|
36
36
|
| O | Docstring vs implementation drift |
|
|
37
37
|
| P | Name vs behavior contract |
|
|
38
|
+
| Q | Cross-surface claim consistency |
|
|
38
39
|
|
|
39
40
|
## Breaking-change rule
|
|
40
41
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# audit-rubrics/category_rubrics
|
|
2
2
|
|
|
3
|
-
One rubric file per audit category (A–
|
|
3
|
+
One rubric file per audit category (A–Q). Each file defines what the category covers, gives concrete examples of findings, and provides the sub-bucket decomposition an audit agent uses to structure its pass.
|
|
4
4
|
|
|
5
5
|
## Files
|
|
6
6
|
|
|
@@ -22,6 +22,7 @@ One rubric file per audit category (A–P). Each file defines what the category
|
|
|
22
22
|
| `category-n-test-name-scenario-verifier.md` | N — Test name / scenario verifier |
|
|
23
23
|
| `category-o-docstring-vs-impl-drift.md` | O — Docstring vs implementation drift |
|
|
24
24
|
| `category-p-name-vs-behavior-contract.md` | P — Name vs behavior contract |
|
|
25
|
+
| `category-q-cross-surface-claims.md` | Q — Cross-surface claim consistency |
|
|
25
26
|
|
|
26
27
|
## Rubric structure
|
|
27
28
|
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
- A PowerShell cmdlet is invoked with a parameter that belongs to a different parameter set.
|
|
11
11
|
- A new gate-time validator omits the `all_changed_lines` parameter that peer span-based validators accept, so the dispatcher cannot plumb diff scope through and the check silently over- or under-blocks.
|
|
12
12
|
- A new span-based check applies its result cap before honoring `defer_scope_to_caller=True`, while peer checks return all violations in that mode and let the caller cap; this leaves the new sibling stale against the established pattern.
|
|
13
|
+
- A call passes `PollingIntervals.resume_check` (a poll interval — how often to re-check) into a `progress_check_timeout` parameter (a timeout — how long to wait). The value's role does not match the parameter's role, so the call type-checks while the behavior is wrong.
|
|
13
14
|
|
|
14
15
|
**Companion reference:** see `../source-material-section-types.md` for guidance on how to chunk the artifact under audit.
|
|
15
16
|
|
|
@@ -23,7 +24,7 @@ The decomposition that worked best for PR #394 (a Python+PowerShell scheduled-ta
|
|
|
23
24
|
|
|
24
25
|
| ID | Axis name | Concrete checks |
|
|
25
26
|
|---|---|---|
|
|
26
|
-
| A1 | Python function signatures vs internal call sites | Parameter count, names, defaults, kw-only barriers; every internal call binds correctly. Is the symbol `async def`? Confirm the exact access path a caller uses: free function vs instance method reached through an object attribute vs import path. A keyword-only parameter with no default is required; omitting it raises `TypeError`. |
|
|
27
|
+
| A1 | Python function signatures vs internal call sites | Parameter count, names, defaults, kw-only barriers; every internal call binds correctly. Is the symbol `async def`? Confirm the exact access path a caller uses: free function vs instance method reached through an object attribute vs import path. A keyword-only parameter with no default is required; omitting it raises `TypeError`. When a call passes a config value as an argument, confirm the value's documented role matches the parameter's role — a poll interval is not a timeout, and a timeout is not a budget. |
|
|
27
28
|
| A2 | Python return-type annotation vs every code path | Each function's return annotation is satisfied by every path: explicit `return X`, fall-through, exception-handler exit. The full failure contract is the return value AND every exception raised — trace the body and the docstring `Raises:` for each `raise`, including custom errors. A `-> bool` function that also raises is not fully described by "returns bool". |
|
|
28
29
|
| A3 | argparse parser → Namespace contract | Every `add_argument(...)` produces the exact dest name accessed downstream; `type=` matches downstream usage; switches produce bools. |
|
|
29
30
|
| A4 | Stdlib callback contracts | `os.walk(onerror=...)` callback shape; `os.path.getctime` / `os.rmdir` argument and exception contracts; `time.sleep` argument types. Catch-site precision: for any claim that code "catches X", confirm the exact catch site and scope — an `except` around only a rollback inside `finally` does not catch the same error raised in the `with` body. |
|
|
@@ -30,7 +30,19 @@
|
|
|
30
30
|
| J11 | sys.path.insert dedup | `sys.path.insert(0, X)` must be guarded by `if X not in sys.path:` (test files exempt). |
|
|
31
31
|
| J12 | Hardcoded user paths | String literals naming a specific user's home directory (`C:/Users/jon/...`, `/Users/alice/...`, `/home/bob/...`). Use `pathlib.Path.home()`. |
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
The write-time hook (`code_rules_enforcer.py`) exempts test files (`test_*.py`, `*_test.py`, `*.test.*`, `*.spec.*`, `conftest.py`, paths under `/tests/`) from most Category J sub-buckets, and skips the naming, logging, annotation, and unused-import rules on `.mjs` / `.js` files. J11 (`sys.path.insert`) always applies. Read the next section for the sub-buckets this audit applies more widely than the hook.
|
|
34
|
+
|
|
35
|
+
## Write-time exemptions do not scope this audit
|
|
36
|
+
|
|
37
|
+
The write-time hook skips several rules on test files and on `.mjs` / `.js` files. This audit does not. Apply these rules to every changed line — production files, test files, and JavaScript files alike:
|
|
38
|
+
|
|
39
|
+
- **Naming (J5, J6)** — banned identifiers (`ctx`, `cfg`, `msg`, `result`, `data`, `output`, `value`, `item`, `temp`, `elem`, `val`, …) and banned function prefixes (`handle_`, `process_`, `manage_`, `do_`); boolean names prefixed `is_` / `has_` / `should_` / `can_` / `was_` / `did_`.
|
|
40
|
+
- **Logging format (J9)** — the format string and its arguments pass as separate parameters (`log_*("...", arg)`), never an f-string.
|
|
41
|
+
- **Type annotations (J7)** — every changed function parameter and return is typed; no `Any`, no bare `# type: ignore`.
|
|
42
|
+
- **Unused imports** — a module-level import a changed file does not read is a finding.
|
|
43
|
+
- **Function length** — a changed function that runs past the length threshold splits into named helpers.
|
|
44
|
+
|
|
45
|
+
A `test_*.py` name or a `.mjs` extension takes the line out of the write-time gate, not out of this audit. When the diff touches a test or a JavaScript file, walk these five rules against the changed lines there too. J1 (magic values) and J3 (constants location) keep their test-file exemption.
|
|
34
46
|
|
|
35
47
|
---
|
|
36
48
|
|
|
@@ -15,6 +15,25 @@ The label-vs-body gap is its own failure mode independent of behavior-equivalenc
|
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
+
## Binding counterexample protocol
|
|
19
|
+
|
|
20
|
+
For every regex, brace or token scanner, or word-list the diff adds or edits, build at least three concrete inputs and trace each one through the pattern by hand before you call the pattern clean:
|
|
21
|
+
|
|
22
|
+
- **One intended match** — an input the pattern must catch.
|
|
23
|
+
- **One near-miss** — an input a quick reading would expect to catch, but the contract says the pattern must reject.
|
|
24
|
+
- **One structural edge** — an input whose shape stresses the scanner: a destructured parameter, a quoted key, a nested brace, an escaped delimiter.
|
|
25
|
+
|
|
26
|
+
Walk each input character by character (or token by token) and write down what the pattern returns. A pattern that returns the wrong answer on any of the three inputs is a Category P finding; cite the input that breaks it. The pattern is clean only after all three inputs return the answer the contract promises.
|
|
27
|
+
|
|
28
|
+
`hooks/blocking/` is the priority surface. Its patterns gate every write, so a pattern that matches too much or too little there fires on real edits across the whole codebase.
|
|
29
|
+
|
|
30
|
+
Two canonical failures show why the hand trace is binding:
|
|
31
|
+
|
|
32
|
+
- **Brace scan that reads a parameter as body.** A scanner counts braces from the signature's opening `(` and treats the first balanced close as the end of the function body. A destructured parameter closes a brace inside the signature — `renderRow({ id, label })` in JavaScript, or a `{a, b}` binding in another language — so the scanner marks the body one token too early and reads the parameter list as body code.
|
|
33
|
+
- **`\bschema\b` that matches a value reference.** A `\bschema\b` pattern targets a `schema` declaration, yet it also matches `schema` as the value in `{ label: schema }`, where the word is a reference, not the declaration the pattern means to find. A word boundary alone does not tell a declaration from a key or a value.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
18
37
|
## Sub-bucket decomposition (Category P)
|
|
19
38
|
|
|
20
39
|
Decomposition is by the **kind of identifier / reference data** whose label is being audited against its body.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Category Q — Cross-surface claim consistency (terminology, PR-description claims, message-vs-guard)
|
|
2
|
+
|
|
3
|
+
**What this category audits:** claims a change makes on one surface that a second surface contradicts. A code field carries one spelling while the prose that names it uses a near-miss variant. A PR description states a fact that the diff or the repo does not bear out. A log or error string asserts a condition that the branch gating it does not guarantee. Each defect lives in the gap between two surfaces that must agree: the code and the prose about it, the PR body and the diff, the message text and the guard that fires it.
|
|
4
|
+
|
|
5
|
+
**Why this category is its own bucket:** each surface reads as correct on its own. The code field is a valid identifier. The PR sentence is a well-formed claim. The log line is grammatical. The defect surfaces only when a reader holds two surfaces side by side and finds that one says a thing the other denies. Categories that read a single surface miss this; Q forces the reader to pair every claim with the surface that must back it.
|
|
6
|
+
|
|
7
|
+
## Canonical examples
|
|
8
|
+
|
|
9
|
+
- **PR #810 (Q1).** Eleven prose surfaces name the term one way while the code field reads `premium_interactions`. One divergent term spreads across README text, doc tables, and inline prose, so a reader who trusts any of the eleven surfaces reaches for a field name the code does not define.
|
|
10
|
+
- **PR #808 (Q2).** The PR description claims zero remaining gate subjects while `converge.mjs` still holds them. The body asserts a cleanup the diff does not finish, so a reviewer who trusts the description approves a state the code contradicts.
|
|
11
|
+
- **PR #823 (Q3).** A skip-branch log asserts a hardening PR opened, yet `spawnStandardsFollowUp` can return `hardeningPrOpened: false`. The message states an outcome the guard that reaches it does not guarantee, so the log reads as a success the run did not achieve.
|
|
12
|
+
|
|
13
|
+
## Other typical patterns
|
|
14
|
+
|
|
15
|
+
- A config key the diff adds reads `retry_budget`; a doc table row names it `retry-budget`, and a runbook names it `retry budget`.
|
|
16
|
+
- An API field the diff introduces is `itemCount` (singular tail); a client doc calls it `itemCounts`.
|
|
17
|
+
- A PR body says "this removes the last caller of `legacy_resolve`"; a reference search still finds a caller in a sibling module.
|
|
18
|
+
- A PR body claims a 30-second ceiling; the config value the diff sets reads 60 seconds.
|
|
19
|
+
- An error string says "config file is missing" while the branch that raises it fires on a parse failure, not an absent file.
|
|
20
|
+
- A log line says "retry limit reached this run" while the counter it reads resets each round, so the wording overstates the lifetime of the state it names.
|
|
21
|
+
|
|
22
|
+
**Companion reference:** see `../source-material-section-types.md`.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Sub-bucket decomposition (Category Q)
|
|
27
|
+
|
|
28
|
+
Decomposition is by the **pair of surfaces** that must agree.
|
|
29
|
+
|
|
30
|
+
| ID | Axis name | Concrete checks |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| Q1 | Term sweep | Pull out every identifier, API field, and config key the diff introduces or reads. Sweep all changed prose surfaces for near-miss variants — hyphen or space in place of an underscore, singular where the code is plural (or the reverse), a shared prefix with a divergent tail. Run `packages/claude-dev-env/_shared/pr-loop/scripts/terminology_sweep.py` for mechanical recall — it reads a unified diff on stdin or via `--diff-file`, prints one `file:line` finding per near-miss, exits 1 when findings exist and 0 when clean — then adjudicate each finding, since a near-miss can be an intentional distinct term. |
|
|
33
|
+
| Q2 | PR-description claim verification | Fetch the PR body. For each falsifiable claim, verify it two ways against the diff and the repo. A dead-code claim ("removes the last caller of X") gets a reference search across the repo. A performance or ceiling claim ("caps at 30 seconds", "cuts calls in half") gets checked against the actual config values and call sites the diff sets. |
|
|
34
|
+
| Q3 | Message-vs-guard consistency | For every log, error, and evidence string the diff adds or edits, read the condition that gates it. The message text matches what the guard guarantees — a string that asserts an outcome fires only on a branch that reaches that outcome. Temporal wording matches the lifetime of the state variable it names: "this run" versus "this round", "is open" versus "was opened", "reached" versus "attempted". |
|
|
35
|
+
|
|
36
|
+
Customize per-artifact: a change that adds no new identifier and no PR-body claim reduces Q to "verify every message the diff touches still matches its guard." A rename that spreads a term across many docs may need Q1 alone to list every prose surface.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Sample prompt
|
|
41
|
+
|
|
42
|
+
The reusable template for Category Q is in [`../prompts/category-q-cross-surface-claims.md`](../prompts/category-q-cross-surface-claims.md). The Category Q source-material block needs the diff, the PR body text, and the changed prose surfaces the agent must cross-reference against the code.
|
|
43
|
+
|
|
44
|
+
## Why Category Q matters as its own bucket
|
|
45
|
+
|
|
46
|
+
A reviewer walking a single surface at a time — the code alone, the docs alone, the PR body alone — judges each on its own merits and misses Q. The code field is a valid name; the PR sentence is a plausible claim; the log line is grammatical. Only pairing each claim with the surface that must back it surfaces the gap. Q forces that pairing: every identifier the diff touches is swept against the prose, every PR-body claim is checked against the diff and the repo, and every message is read against the guard that fires it.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# audit-rubrics/prompts
|
|
2
2
|
|
|
3
|
-
Agent-ready audit prompt templates, one per category (A–
|
|
3
|
+
Agent-ready audit prompt templates, one per category (A–Q). An agent inlines the artifact under review into the `[INLINE THE FULL ARTIFACT HERE]` placeholder and runs the prompt as-is.
|
|
4
4
|
|
|
5
5
|
## Files
|
|
6
6
|
|
|
@@ -22,6 +22,7 @@ Agent-ready audit prompt templates, one per category (A–P). An agent inlines t
|
|
|
22
22
|
| `category-n-test-name-scenario-verifier.md` | N — Test name / scenario verifier |
|
|
23
23
|
| `category-o-docstring-vs-impl-drift.md` | O — Docstring vs implementation drift |
|
|
24
24
|
| `category-p-name-vs-behavior-contract.md` | P — Name vs behavior contract |
|
|
25
|
+
| `category-q-cross-surface-claims.md` | Q — Cross-surface claim consistency |
|
|
25
26
|
|
|
26
27
|
## Prompt structure
|
|
27
28
|
|
|
@@ -16,6 +16,7 @@ ID prefix: `find`.
|
|
|
16
16
|
- Flag calls that omit a required parameter relying on a default that does not exist on the current branch.
|
|
17
17
|
- Verify decorators (`@staticmethod`, `@classmethod`, `@property`) do not silently shift the parameter binding (e.g., `self` / `cls` insertion).
|
|
18
18
|
- Confirm sync-vs-async (is the symbol `async def`?), the exact access path a caller uses (free function vs instance method via an object attribute vs import path), and that a keyword-only parameter with no default is required — omitting it raises `TypeError`.
|
|
19
|
+
- When a call passes a config value as an argument, confirm the value's documented role matches the parameter's role (poll interval vs timeout vs budget). A poll interval such as `PollingIntervals.resume_check` handed to a `progress_check_timeout` parameter type-checks but carries the wrong meaning.
|
|
19
20
|
|
|
20
21
|
**A2. Return-type annotation vs every code path**
|
|
21
22
|
- For each annotated function, walk every code path: explicit `return X`, fall-through to implicit `None`, exception-handler exit, generator `yield` paths, async coroutine return value.
|
|
@@ -11,10 +11,22 @@ ID prefix: `find`.
|
|
|
11
11
|
|
|
12
12
|
## Source material
|
|
13
13
|
|
|
14
|
-
Inline the artifact (full diff or full file contents) under a clearly delimited block below this section. Use the chunking guide in [`../source-material-section-types.md`](../source-material-section-types.md) to choose the right Source-material section type (full-diff, file-set, patch-series, or excerpt-with-context). Mark every line range that is in scope; mark explicitly which files are test files
|
|
14
|
+
Inline the artifact (full diff or full file contents) under a clearly delimited block below this section. Use the chunking guide in [`../source-material-section-types.md`](../source-material-section-types.md) to choose the right Source-material section type (full-diff, file-set, patch-series, or excerpt-with-context). Mark every line range that is in scope; mark explicitly which files are test files and which are production.
|
|
15
15
|
|
|
16
16
|
Replace this paragraph with the chunked source material before issuing the prompt.
|
|
17
17
|
|
|
18
|
+
## Write-time exemptions do not scope this audit
|
|
19
|
+
|
|
20
|
+
The write-time hook skips several rules on test files and on `.mjs` / `.js` files; this audit does not. Apply these rules to every changed line — production, test, and JavaScript files alike:
|
|
21
|
+
|
|
22
|
+
- Naming (J5, J6) — banned identifiers and banned function prefixes; boolean names prefixed `is_` / `has_` / `should_` / `can_` / `was_` / `did_`.
|
|
23
|
+
- Logging format (J9) — the format string and its arguments pass as separate parameters, never an f-string.
|
|
24
|
+
- Type annotations (J7) — every changed parameter and return is typed; no `Any`, no bare `# type: ignore`.
|
|
25
|
+
- Unused imports — a module-level import a changed file does not read.
|
|
26
|
+
- Function length — a changed function past the length threshold splits into named helpers.
|
|
27
|
+
|
|
28
|
+
A `test_*.py` name or a `.mjs` extension takes the line out of the write-time gate, not out of this audit. J1 (magic values) and J3 (constants location) keep their test-file exemption; the five rules above do not.
|
|
29
|
+
|
|
18
30
|
## Sub-buckets (each requires Shape A finding OR Shape B with ≥3 adversarial probes)
|
|
19
31
|
|
|
20
32
|
**J1. Magic values in production function bodies**
|
|
@@ -40,18 +52,18 @@ Replace this paragraph with the chunked source material before issuing the promp
|
|
|
40
52
|
- Adversarial probes: (a) is any imported constant referenced only once in the importing file (suggesting the import itself is gratuitous)? (b) is any helper function defined in a production file but never called from inside the same file (separate dead-code concern, surfaced here for completeness)? (c) does any constant in `config/` get imported from zero call sites across the repo?
|
|
41
53
|
|
|
42
54
|
**J5. Abbreviations**
|
|
43
|
-
- Walk every parameter, local, and attribute name
|
|
44
|
-
-
|
|
55
|
+
- Walk every parameter, local, and attribute name across production, test, and JavaScript changed lines. Flag: `ctx`, `cfg`, `msg`, `btn`, `idx`, `cnt`, `elem`, `val`, `tmp`, `str`, `num`, `arr`, `obj`, `fn`, `cb`, `req`, `res`. Loop counters `i`/`j`/`k` and `e` for exceptions are exempt.
|
|
56
|
+
- This audit walks changed test-file and `.mjs` / `.js` lines for this rule, even though the write-time hook skips them.
|
|
45
57
|
- Adversarial probes: (a) is there a borderline name (e.g., `removed`, `arguments`) that someone might mis-classify as an abbreviation but is actually a full English word? Confirm. (b) does any callback / parameter / attribute use a short variant of a domain term that is technically a full word but conventionally abbreviates a longer one? (c) does any variable in a comprehension or lambda use a single letter outside the `i`/`j`/`k`/`e` exemption?
|
|
46
58
|
|
|
47
59
|
**J6. Vague names**
|
|
48
60
|
- Flag any name from the vague list: `result`, `data`, `output`, `response`, `value`, `item`, `temp`, `info`, `stuff`, `thing`. Vague verb prefixes for function names: `handle`, `process`, `manage`, `do`.
|
|
49
|
-
-
|
|
61
|
+
- This audit walks changed test-file and `.mjs` / `.js` lines for this rule, even though the write-time hook skips them.
|
|
50
62
|
- Adversarial probes: (a) does any local variable use a domain-adjacent name that is actually on the vague list (e.g., `result` from a parser, `data` from a fetch)? (b) does any newly-introduced function name start with a vague prefix? (c) does any public attribute / dict key use a vague label that the call site has to disambiguate by surrounding context?
|
|
51
63
|
|
|
52
64
|
**J7. Type hints**
|
|
53
|
-
- Walk every function
|
|
54
|
-
-
|
|
65
|
+
- Walk every function across production, test, and JavaScript changed lines. Verify parameter and return types are present, no `Any`, no `# type: ignore`.
|
|
66
|
+
- This audit walks changed test-file and `.mjs` / `.js` lines for this rule, even though the write-time hook skips them.
|
|
55
67
|
- Adversarial probes: (a) does any production function rely on inferred return type from a single `return` path? (b) does any parameter use a string-quoted forward reference that masks `Any`? (c) is there a `# type: ignore` anywhere? Grep the diff explicitly.
|
|
56
68
|
|
|
57
69
|
**J8. New inline comments**
|
|
@@ -64,7 +76,7 @@ Replace this paragraph with the chunked source material before issuing the promp
|
|
|
64
76
|
**J9. Logging format**
|
|
65
77
|
- Walk every `log_*(...)` call. Must be `log_*("template with {}", arg)`, not `log_*(f"...")`.
|
|
66
78
|
- The rule applies to the project's structured `log_*` family, not stdlib `print`. `print` f-strings are J2-scope (string-template magic), not J9-scope.
|
|
67
|
-
-
|
|
79
|
+
- This audit walks changed test-file and `.mjs` / `.js` lines for this rule, even though the write-time hook skips them.
|
|
68
80
|
- Adversarial probes: (a) is there any imported `log_*` function in production code that uses an f-string? (b) is there a logger-equivalent call (e.g., `logger.info(f"...")` from `logging` stdlib) that should be subject to the same rule? (c) does any non-Python logger family (e.g., `console.log`, `Write-Host`, structured-log helpers) appear with a template-string pattern that mirrors the J9 anti-pattern?
|
|
69
81
|
|
|
70
82
|
**J10. Imports inside functions**
|
|
@@ -19,6 +19,20 @@ ID prefix: `find`.
|
|
|
19
19
|
|
|
20
20
|
[INLINE each fresh identifier in context — the constant declaration with the value, the function signature with the body, the flag with every assignment / reset site, the regex source with surrounding usage.]
|
|
21
21
|
|
|
22
|
+
## Binding counterexample protocol (run before every Shape B "clean" verdict)
|
|
23
|
+
|
|
24
|
+
For every regex, brace or token scanner, or word-list the diff adds or edits, build at least three concrete inputs and trace each one through the pattern by hand:
|
|
25
|
+
|
|
26
|
+
- One intended match the pattern must catch.
|
|
27
|
+
- One near-miss the pattern must reject (hyphen vs underscore, a value reference vs a declaration, a substring of a longer word).
|
|
28
|
+
- One structural edge that stresses the scanner: a destructured parameter, a quoted key, a nested brace, an escaped delimiter.
|
|
29
|
+
|
|
30
|
+
Record what the pattern returns for each input. A wrong answer on any input is a Shape A finding, cited with the breaking input; a clean verdict is earned only after all three inputs return the contract's answer. `hooks/blocking/` is the priority surface — its patterns gate every write.
|
|
31
|
+
|
|
32
|
+
Two canonical failures to probe for:
|
|
33
|
+
- A brace scan that counts from the signature `(` and reads a destructured parameter (`renderRow({ id, label })`) as the start of the function body.
|
|
34
|
+
- A `\bschema\b` pattern that matches the value in `{ label: schema }`, not just the `schema` declaration it targets.
|
|
35
|
+
|
|
22
36
|
## Sub-buckets (each requires Shape A finding OR Shape B with ≥3 adversarial probes)
|
|
23
37
|
|
|
24
38
|
**P1. Boolean / flag names assert state the body keeps**
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
Audit [REPO/ARTIFACT] [TARGET_ID] for **Category Q only** (cross-surface claim consistency — terminology, PR-description claims, message-vs-guard). Skip A–P. Sub-bucket forced-exhaustion mode: Category Q is decomposed into 3 sub-buckets below. Each sub-bucket needs at least one Shape A finding OR exactly one Shape B proof-of-absence with **at least 3 adversarial probes** specific to that sub-bucket. A sub-bucket returning neither is a protocol gap.
|
|
2
|
+
|
|
3
|
+
[ARTIFACT METADATA]
|
|
4
|
+
|
|
5
|
+
- Title / one-line summary: [TITLE]
|
|
6
|
+
- Base ref / SHA (state before the change): [BASE_SHA]
|
|
7
|
+
- Head ref / SHA at audit time (state after the change): [HEAD_SHA]
|
|
8
|
+
- Changed surfaces (file + line range + symbol/region name): [CHANGED_SURFACES]
|
|
9
|
+
- New or read identifiers, API fields, and config keys the diff touches: [DIFF_TERMS]
|
|
10
|
+
- Changed prose surfaces (README, doc tables, inline prose, log and error strings): [CHANGED_PROSE_SURFACES]
|
|
11
|
+
- PR description body text: [PR_BODY]
|
|
12
|
+
- Stated intent of the change: [INTENT]
|
|
13
|
+
|
|
14
|
+
ID prefix: `find`.
|
|
15
|
+
|
|
16
|
+
[ONE-PARAGRAPH FRAME: describe what the diff changed in plain English, naming the identifiers and config keys it introduces, the PR-body claims it makes, and the messages it adds or edits. State the audit goal: find any surface whose wording contradicts another surface that must agree with it — a prose term that misspells a code field, a PR-body claim the diff or repo does not bear out, or a message that asserts more than the guard gating it guarantees.]
|
|
17
|
+
|
|
18
|
+
## Source material ([N] files/sections, all lines in scope)
|
|
19
|
+
|
|
20
|
+
[INLINE THE FULL DIFF — the changed lines and enough surrounding context to show each identifier's definition and each message's guard.]
|
|
21
|
+
|
|
22
|
+
[ALSO INLINE the PR description body text and every changed prose surface — README sections, doc tables, runbooks, log and error strings — so the agent can pair each claim with the code it names.]
|
|
23
|
+
|
|
24
|
+
## Sub-buckets (each needs a Shape A finding OR Shape B with ≥3 adversarial probes)
|
|
25
|
+
|
|
26
|
+
**Q1. Term sweep**
|
|
27
|
+
- Pull out every identifier, API field, and config key the diff introduces or reads. For each, sweep all changed prose surfaces for a near-miss variant — a hyphen or a space in place of an underscore, a singular where the code is plural (or the reverse), a shared prefix with a divergent tail.
|
|
28
|
+
- Run `packages/claude-dev-env/_shared/pr-loop/scripts/terminology_sweep.py` for mechanical recall. It reads a unified diff on stdin or via `--diff-file`, prints one `file:line` finding per near-miss, exits 1 when findings exist and 0 when clean. Feed it the PR diff, then adjudicate each finding: a near-miss can be an intentional distinct term, so cite the code definition and the prose line and judge whether they name the same thing.
|
|
29
|
+
- Adversarial probes when the sweep is clean: (a) check camelCase versus snake_case spellings of the same field across code and docs; (b) check singular/plural drift on collection field names; (c) check a shared-prefix family (`retry_budget`, `retry_count`, `retry_limit`) for a doc that names the wrong member.
|
|
30
|
+
|
|
31
|
+
**Q2. PR-description claim verification**
|
|
32
|
+
- Read the PR body. List each falsifiable claim — a dead-code removal, a performance or ceiling number, a "last caller" assertion, a "no remaining X" cleanup claim.
|
|
33
|
+
- Verify each claim two ways against the diff and the repo. A dead-code claim gets a reference search across the repo for the named symbol. A ceiling or performance claim gets checked against the actual config value the diff sets and the call sites it touches. Cite the PR-body line and the diff or repo line that confirms or contradicts it.
|
|
34
|
+
- Adversarial probes when the body looks correct: (a) search the repo for a surviving caller of any symbol the body calls dead; (b) compare each stated number against the config constant the diff sets; (c) check whether a "this finishes the cleanup" claim leaves a matching pattern in an unchanged sibling file.
|
|
35
|
+
|
|
36
|
+
**Q3. Message-vs-guard consistency**
|
|
37
|
+
- For every log, error, and evidence string the diff adds or edits, read the branch condition that gates it. The message text matches what that guard guarantees: a string asserting an outcome fires only on a branch that reaches the outcome.
|
|
38
|
+
- Check temporal wording against the lifetime of the state variable the message names: "this run" versus "this round", "is open" versus "was opened", "reached" versus "attempted". Cite the message line and the guard or state-variable line.
|
|
39
|
+
- Adversarial probes when messages look aligned: (a) trace each success message back to the return value or flag it claims and confirm the branch cannot reach it with the opposite value; (b) check every "missing" / "not found" error against the exception class the branch actually catches; (c) check every counter-based message against where the counter resets.
|
|
40
|
+
|
|
41
|
+
## Cross-bucket questions to answer at the end
|
|
42
|
+
|
|
43
|
+
Q-a: Which single claim in this change is most likely to mislead a reader at runtime — a prose term that names a field the code does not define, a PR-body fact the diff contradicts, or a message that overstates its guard? Cite both surfaces by `path:line`.
|
|
44
|
+
|
|
45
|
+
Q-b: Which surface is the strongest witness to the contradiction — the doc, PR body, or message a reader would trust while the code says otherwise?
|
|
46
|
+
|
|
47
|
+
Q-c: Is there a term the diff introduces that appears correct in the code yet drifts across two or more prose surfaces at once? Cite each prose line and the code definition.
|
|
48
|
+
|
|
49
|
+
## Output
|
|
50
|
+
|
|
51
|
+
Lead: `Total: N (P0=N, P1=N, P2=N)`. For each sub-bucket Q1-Q3, produce Shape A or Shape B (with ≥3 probes). Each Shape A finding cites BOTH surfaces — the claim and the surface that contradicts it — by `path:line`. The `failure_mode` describes the contradiction between the two surfaces. Cross-bucket Q-a through Q-c answers after the per-sub-bucket walk. Adversarial second pass: "assume your first pass missed at least 3 cross-surface contradictions — find them." Open Questions section for ambiguities. Read-only. No edits, no commits.
|