claude-dev-env 1.69.2 → 1.71.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/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +1 -0
- package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +8 -4
- package/hooks/blocking/CLAUDE.md +1 -0
- package/hooks/blocking/claude_md_orphan_file_blocker.py +632 -0
- package/hooks/blocking/test_claude_md_orphan_file_blocker.py +623 -0
- package/hooks/git-hooks/CLAUDE.md +1 -1
- package/hooks/git-hooks/git_hooks_constants/__init__.py +13 -0
- package/hooks/git-hooks/pre_push.py +74 -15
- package/hooks/git-hooks/test_pre_push.py +118 -0
- package/hooks/hooks.json +5 -0
- package/hooks/hooks_constants/CLAUDE.md +1 -0
- package/hooks/hooks_constants/claude_md_orphan_file_blocker_constants.py +107 -0
- package/package.json +1 -1
- package/rules/CLAUDE.md +1 -0
- package/rules/claude-md-orphan-file.md +24 -0
- package/rules/docstring-prose-matches-implementation.md +4 -1
- package/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +36 -5
- package/skills/autoconverge/workflow/render_report.py +43 -5
- package/skills/autoconverge/workflow/test_render_report.py +43 -0
|
@@ -16,6 +16,7 @@ from autoconverge_report_constants.render_report_constants import (
|
|
|
16
16
|
CAUSE_MUTED_STYLE,
|
|
17
17
|
DEFAULT_FINDING_CATEGORY,
|
|
18
18
|
DEFAULT_FINDING_SEVERITY,
|
|
19
|
+
DEFAULT_ISSUE_ICON,
|
|
19
20
|
GITHUB_PR_URL_TEMPLATE,
|
|
20
21
|
HTML_DOCTYPE,
|
|
21
22
|
HTML_HEAD_TEMPLATE,
|
|
@@ -30,6 +31,7 @@ from autoconverge_report_constants.render_report_constants import (
|
|
|
30
31
|
ISSUE_CLASS_FIELD_PLAINNAME,
|
|
31
32
|
ISSUE_CLASS_FIELD_SEVERITY,
|
|
32
33
|
ISSUE_CLASS_FIELD_STATUS,
|
|
34
|
+
ISSUE_ICON_BY_CATEGORY,
|
|
33
35
|
JOURNAL_SIBLING_SUBAGENTS,
|
|
34
36
|
JOURNAL_SIBLING_WORKFLOWS,
|
|
35
37
|
LABEL_CONVERGENCE_SUMMARY,
|
|
@@ -43,6 +45,9 @@ from autoconverge_report_constants.render_report_constants import (
|
|
|
43
45
|
SCENE_FIELD_CONDITION,
|
|
44
46
|
SCENE_FIELD_RESULT,
|
|
45
47
|
SCENE_FIELD_TRIGGER,
|
|
48
|
+
SCORECARD_LABEL_CAUGHT,
|
|
49
|
+
SCORECARD_LABEL_REMAINING,
|
|
50
|
+
SCORECARD_LABEL_ROUNDS,
|
|
46
51
|
SEVERITY_SORT_RANK,
|
|
47
52
|
SHORT_SHA_LENGTH,
|
|
48
53
|
STATUS_LABEL_BY_VALUE,
|
|
@@ -456,6 +461,30 @@ def _render_verdict_banner(
|
|
|
456
461
|
)
|
|
457
462
|
|
|
458
463
|
|
|
464
|
+
def _render_scorecard(run_data: RunData, round_count: int) -> str:
|
|
465
|
+
"""Return the at-a-glance scorecard: findings caught, rounds, and zero remaining.
|
|
466
|
+
|
|
467
|
+
Args:
|
|
468
|
+
run_data: Aggregated metrics from the journal.
|
|
469
|
+
round_count: Total number of convergence rounds.
|
|
470
|
+
|
|
471
|
+
Returns:
|
|
472
|
+
An HTML .scorecard grid of three .stat tiles; the remaining tile is marked
|
|
473
|
+
.good and reads zero, since the report renders only on a converged run.
|
|
474
|
+
"""
|
|
475
|
+
remaining_count = 0
|
|
476
|
+
return (
|
|
477
|
+
'<div class="scorecard">'
|
|
478
|
+
f'<div class="stat"><div class="stat-num">{run_data.total_finding_count}</div>'
|
|
479
|
+
f'<div class="stat-label">{SCORECARD_LABEL_CAUGHT}</div></div>'
|
|
480
|
+
f'<div class="stat"><div class="stat-num">{round_count}</div>'
|
|
481
|
+
f'<div class="stat-label">{SCORECARD_LABEL_ROUNDS}</div></div>'
|
|
482
|
+
f'<div class="stat good"><div class="stat-num">{remaining_count}</div>'
|
|
483
|
+
f'<div class="stat-label">{SCORECARD_LABEL_REMAINING}</div></div>'
|
|
484
|
+
"</div>"
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
|
|
459
488
|
def _render_scene_row(scene: dict, is_problem: bool) -> str:
|
|
460
489
|
"""Return one .scene row plus its caption for a problem-or-fix scene.
|
|
461
490
|
|
|
@@ -642,20 +671,26 @@ def _issue_class_sort_key(issue_class: dict) -> tuple[int, int]:
|
|
|
642
671
|
|
|
643
672
|
|
|
644
673
|
def _render_issue_class_heading(issue_class: dict) -> str:
|
|
645
|
-
"""Return the per-class heading with
|
|
674
|
+
"""Return the per-class heading with a category icon, plain name, and count.
|
|
646
675
|
|
|
647
676
|
Args:
|
|
648
677
|
issue_class: One issue-class dict from the summary.
|
|
649
678
|
|
|
650
679
|
Returns:
|
|
651
|
-
An HTML .bug-head block
|
|
680
|
+
An HTML .bug-head block: a category icon and the plain bug name grouped in
|
|
681
|
+
a .bug-title span, plus a finding-count chip.
|
|
652
682
|
"""
|
|
653
683
|
plain_name = html.escape(str(issue_class.get(ISSUE_CLASS_FIELD_PLAINNAME, "")))
|
|
654
684
|
count = _coerce_count(issue_class.get(ISSUE_CLASS_FIELD_COUNT, 0))
|
|
655
685
|
count_phrase = html.escape(_pluralize(count, "finding", "findings"))
|
|
686
|
+
category = str(issue_class.get(ISSUE_CLASS_FIELD_CATEGORY, CATEGORY_BUG))
|
|
687
|
+
icon = ISSUE_ICON_BY_CATEGORY.get(category, DEFAULT_ISSUE_ICON)
|
|
656
688
|
return (
|
|
657
689
|
'<div class="bug-head">'
|
|
690
|
+
'<span class="bug-title">'
|
|
691
|
+
f'<span class="bug-icon">{icon}</span>'
|
|
658
692
|
f'<span class="bug-name">{plain_name}</span>'
|
|
693
|
+
"</span>"
|
|
659
694
|
f'<span class="bug-count">{count_phrase}</span>'
|
|
660
695
|
"</div>"
|
|
661
696
|
)
|
|
@@ -792,9 +827,10 @@ def _render_summary_body(
|
|
|
792
827
|
final_sha_short: First eight characters of the final commit sha.
|
|
793
828
|
|
|
794
829
|
Returns:
|
|
795
|
-
An HTML body fragment: verdict banner,
|
|
796
|
-
caught section that opens with a
|
|
797
|
-
issue-class before/after panels,
|
|
830
|
+
An HTML body fragment: verdict banner, an at-a-glance scorecard,
|
|
831
|
+
problem/fix cards, then a single caught section that opens with a
|
|
832
|
+
run-stats lead line and holds the issue-class before/after panels,
|
|
833
|
+
followed by the collapsed appendix.
|
|
798
834
|
"""
|
|
799
835
|
convergence_summary = run_data.convergence_summary
|
|
800
836
|
if convergence_summary is None:
|
|
@@ -803,12 +839,14 @@ def _render_summary_body(
|
|
|
803
839
|
verdict_banner = _render_verdict_banner(
|
|
804
840
|
convergence_summary, run_data, final_sha_short
|
|
805
841
|
)
|
|
842
|
+
scorecard = _render_scorecard(run_data, round_count)
|
|
806
843
|
pf_grid = _render_pf_grid(convergence_summary)
|
|
807
844
|
caught_lead = _render_caught_lead(convergence_summary, run_data, round_count)
|
|
808
845
|
issue_panels = _render_issue_class_panels(convergence_summary)
|
|
809
846
|
appendix = _render_appendix(run_data.all_distinct_findings)
|
|
810
847
|
return (
|
|
811
848
|
f"{verdict_banner}"
|
|
849
|
+
f"{scorecard}"
|
|
812
850
|
f"<h2>What this PR does</h2>{pf_grid}"
|
|
813
851
|
f"<h2>What was caught — and how it looked</h2>{caught_lead}{issue_panels}"
|
|
814
852
|
f"{appendix}"
|
|
@@ -138,6 +138,49 @@ def test_cli_renders_verdict_banner_with_python_computed_vsub(tmp_path: Path) ->
|
|
|
138
138
|
assert "final commit 7c2f420c" in html_content
|
|
139
139
|
|
|
140
140
|
|
|
141
|
+
def test_cli_renders_scorecard_with_caught_rounds_and_zero_remaining(
|
|
142
|
+
tmp_path: Path,
|
|
143
|
+
) -> None:
|
|
144
|
+
"""Should render an at-a-glance scorecard: findings caught, rounds, and zero left."""
|
|
145
|
+
out_path = tmp_path / "report-scorecard.html"
|
|
146
|
+
|
|
147
|
+
completed = _render_cli(FIXTURE_JOURNAL, out_path)
|
|
148
|
+
assert completed.returncode == 0, f"CLI failed:\n{completed.stderr}"
|
|
149
|
+
|
|
150
|
+
html_content = out_path.read_text(encoding="utf-8")
|
|
151
|
+
assert 'class="scorecard"' in html_content
|
|
152
|
+
assert 'class="stat good"' in html_content
|
|
153
|
+
assert f'<div class="stat-num">{EXPECTED_TOTAL_FINDINGS}</div>' in html_content
|
|
154
|
+
assert f'<div class="stat-num">{EXPECTED_ROUND_COUNT}</div>' in html_content
|
|
155
|
+
assert '<div class="stat-num">0</div>' in html_content
|
|
156
|
+
assert ">caught</div>" in html_content
|
|
157
|
+
assert ">rounds</div>" in html_content
|
|
158
|
+
assert ">left</div>" in html_content
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def test_render_issue_class_heading_carries_a_category_icon() -> None:
|
|
162
|
+
"""Should prefix the plain name with the icon matching the issue category."""
|
|
163
|
+
bug_heading = render_report._render_issue_class_heading(
|
|
164
|
+
{"plainName": "A plain symptom", "count": 2, "category": "bug"}
|
|
165
|
+
)
|
|
166
|
+
assert 'class="bug-title"' in bug_heading
|
|
167
|
+
assert 'class="bug-icon"' in bug_heading
|
|
168
|
+
assert render_report.ISSUE_ICON_BY_CATEGORY["bug"] in bug_heading
|
|
169
|
+
|
|
170
|
+
standard_heading = render_report._render_issue_class_heading(
|
|
171
|
+
{"plainName": "A standard symptom", "count": 1, "category": "code-standard"}
|
|
172
|
+
)
|
|
173
|
+
assert render_report.ISSUE_ICON_BY_CATEGORY["code-standard"] in standard_heading
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def test_render_issue_class_heading_uses_default_icon_for_unknown_category() -> None:
|
|
177
|
+
"""Should fall back to the default icon when the category has no mapped icon."""
|
|
178
|
+
heading = render_report._render_issue_class_heading(
|
|
179
|
+
{"plainName": "An unmapped symptom", "count": 1, "category": "mystery"}
|
|
180
|
+
)
|
|
181
|
+
assert render_report.DEFAULT_ISSUE_ICON in heading
|
|
182
|
+
|
|
183
|
+
|
|
141
184
|
def test_cli_renders_problem_and_fix_scene_cards(tmp_path: Path) -> None:
|
|
142
185
|
"""Should draw problem and fix scene cards with trigger, result, and caption."""
|
|
143
186
|
out_path = tmp_path / "report-scenes.html"
|