claude-dev-env 1.59.0 → 1.61.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.
Files changed (81) hide show
  1. package/CLAUDE.md +4 -0
  2. package/audit-rubrics/category_rubrics/category-b-selector-engine-compat.md +1 -1
  3. package/audit-rubrics/category_rubrics/category-e-dead-code.md +1 -0
  4. package/audit-rubrics/category_rubrics/category-f-silent-failures.md +1 -1
  5. package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +1 -1
  6. package/audit-rubrics/prompts/category-b-selector-engine-compat.md +2 -2
  7. package/audit-rubrics/prompts/category-e-dead-code.md +17 -4
  8. package/audit-rubrics/prompts/category-f-silent-failures.md +1 -0
  9. package/docs/CODE_RULES.md +2 -2
  10. package/hooks/blocking/code_rules_annotations_length.py +189 -10
  11. package/hooks/blocking/code_rules_dead_module_constant.py +321 -0
  12. package/hooks/blocking/code_rules_duplicate_body.py +152 -0
  13. package/hooks/blocking/code_rules_enforcer.py +38 -15
  14. package/hooks/blocking/code_rules_orphan_css_class.py +196 -0
  15. package/hooks/blocking/code_rules_typeddict_stub.py +172 -0
  16. package/hooks/blocking/config/__init__.py +5 -0
  17. package/hooks/blocking/config/verified_commit_constants.py +118 -0
  18. package/hooks/blocking/destructive_command_blocker.py +483 -61
  19. package/hooks/blocking/test_code_rules_enforcer_annotations.py +240 -0
  20. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +1 -0
  21. package/hooks/blocking/test_code_rules_enforcer_cross_skill_duplicate.py +146 -0
  22. package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +188 -0
  23. package/hooks/blocking/test_code_rules_enforcer_dispatch_wiring.py +82 -0
  24. package/hooks/blocking/test_code_rules_enforcer_orphan_css_class.py +196 -0
  25. package/hooks/blocking/test_code_rules_enforcer_zero_payload_alias.py +415 -0
  26. package/hooks/blocking/test_code_rules_enforcer_zero_payload_alias_hook_routing.py +156 -0
  27. package/hooks/blocking/test_destructive_command_blocker.py +213 -0
  28. package/hooks/blocking/test_verdict_directory_write_blocker.py +720 -0
  29. package/hooks/blocking/test_verification_verdict_store.py +490 -0
  30. package/hooks/blocking/test_verified_commit_gate.py +495 -0
  31. package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +131 -0
  32. package/hooks/blocking/test_verifier_verdict_minter.py +193 -0
  33. package/hooks/blocking/verdict_directory_write_blocker.py +667 -0
  34. package/hooks/blocking/verification_verdict_store.py +686 -0
  35. package/hooks/blocking/verified_commit_gate.py +535 -0
  36. package/hooks/blocking/verified_commit_message_accuracy_blocker.py +152 -0
  37. package/hooks/blocking/verifier_verdict_minter.py +221 -0
  38. package/hooks/diagnostic/test_hook_log_extractor.py +3 -3
  39. package/hooks/hooks.json +43 -1
  40. package/hooks/hooks_constants/blocking_check_limits.py +1 -0
  41. package/hooks/hooks_constants/code_rules_enforcer_constants.py +6 -0
  42. package/hooks/hooks_constants/dead_module_constant_constants.py +20 -0
  43. package/hooks/hooks_constants/destructive_command_segment_constants.py +15 -0
  44. package/hooks/hooks_constants/duplicate_function_body_constants.py +22 -5
  45. package/hooks/hooks_constants/orphan_css_class_constants.py +40 -0
  46. package/hooks/hooks_constants/precommit_code_rules_gate_constants.py +1 -1
  47. package/hooks/validation/mypy_validator.py +59 -7
  48. package/hooks/validation/test_mypy_validator.py +94 -0
  49. package/package.json +1 -1
  50. package/rules/file-global-constants.md +7 -1
  51. package/rules/no-cross-skill-duplicate-helpers.md +29 -0
  52. package/rules/orphan-css-class.md +23 -0
  53. package/skills/_shared/pr-loop/scripts/preflight_worktree.py +392 -0
  54. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/preflight_constants.py +70 -0
  55. package/skills/_shared/pr-loop/scripts/test_preflight_worktree.py +263 -0
  56. package/skills/autoconverge/SKILL.md +54 -17
  57. package/skills/autoconverge/reference/closing-report.md +59 -17
  58. package/skills/autoconverge/workflow/aggregate_runs.py +371 -0
  59. package/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +192 -76
  60. package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +76 -0
  61. package/skills/autoconverge/workflow/converge.contract.test.mjs +395 -206
  62. package/skills/autoconverge/workflow/converge.mjs +520 -57
  63. package/skills/autoconverge/workflow/convergence_summary.py +110 -0
  64. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ab1c2d3e4f5a6b7c8.jsonl +2 -0
  65. package/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +7 -0
  66. package/skills/autoconverge/workflow/render_report.py +488 -397
  67. package/skills/autoconverge/workflow/test_aggregate_runs.py +134 -0
  68. package/skills/autoconverge/workflow/test_convergence_summary.py +132 -0
  69. package/skills/autoconverge/workflow/test_render_report.py +518 -259
  70. package/skills/pr-converge/reference/per-tick.md +28 -8
  71. package/skills/rebase/SKILL.md +2 -4
  72. package/system-prompts/software-engineer.xml +2 -6
  73. package/hooks/blocking/content_search_to_zoekt_redirector.py +0 -59
  74. package/hooks/blocking/content_search_zoekt_bash_block_reason.py +0 -25
  75. package/hooks/blocking/content_search_zoekt_block_payload.py +0 -21
  76. package/hooks/blocking/content_search_zoekt_indexed_paths.py +0 -24
  77. package/hooks/blocking/content_search_zoekt_indexed_roots_config.py +0 -131
  78. package/hooks/blocking/content_search_zoekt_redirect_guidance.py +0 -52
  79. package/hooks/blocking/test_content_search_to_zoekt_redirector_integration.py +0 -61
  80. package/hooks/blocking/test_content_search_to_zoekt_redirector_unit.py +0 -92
  81. package/hooks/blocking/test_content_search_zoekt_indexed_roots_config.py +0 -102
@@ -0,0 +1,134 @@
1
+ """Tests for aggregate_runs against the real wf_881252e6-700 fixture run tree."""
2
+
3
+ import json
4
+ import shutil
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
9
+
10
+ import aggregate_runs
11
+ import render_report
12
+
13
+ FIXTURE_DIR = Path(__file__).resolve().parent / "fixtures" / "wf_run"
14
+ FIXTURE_RUN_ID = "wf_881252e6-700"
15
+ FIXTURE_PR_NUMBER = 211
16
+ FIXTURE_OWNER = "example-owner"
17
+ FIXTURE_REPO = "example-repo"
18
+
19
+
20
+ def _copy_fixture_run(projects_root: Path, session_name: str) -> Path:
21
+ """Copy the fixture run tree into projects_root/proj/<session> and return its journal."""
22
+ session_dir = projects_root / "proj" / session_name
23
+ shutil.copytree(FIXTURE_DIR, session_dir)
24
+ return session_dir / "workflows" / f"{FIXTURE_RUN_ID}.json"
25
+
26
+
27
+ def _write_minimal_run(
28
+ projects_root: Path,
29
+ session_name: str,
30
+ run_id: str,
31
+ pr_number: int,
32
+ resolve_heads: int,
33
+ ) -> Path:
34
+ """Write a transcript-free autoconverge journal with N resolve-head entries."""
35
+ workflows_dir = projects_root / "proj" / session_name / "workflows"
36
+ workflows_dir.mkdir(parents=True)
37
+ progress = [
38
+ {
39
+ "index": each_index + 1,
40
+ "label": render_report.LABEL_RESOLVE_HEAD,
41
+ "agentId": None,
42
+ }
43
+ for each_index in range(resolve_heads)
44
+ ]
45
+ journal = {
46
+ "runId": run_id,
47
+ "timestamp": "2026-06-14T00:00:00.000Z",
48
+ "workflowName": "autoconverge",
49
+ "args": json.dumps(
50
+ {"owner": FIXTURE_OWNER, "repo": FIXTURE_REPO, "prNumber": pr_number}
51
+ ),
52
+ "result": {
53
+ "converged": False,
54
+ "rounds": resolve_heads,
55
+ "finalSha": "",
56
+ "blocker": None,
57
+ },
58
+ "workflowProgress": progress,
59
+ }
60
+ journal_path = workflows_dir / f"{run_id}.json"
61
+ journal_path.write_text(json.dumps(journal, indent=2), encoding="utf-8")
62
+ return journal_path
63
+
64
+
65
+ def test_aggregate_single_run_reproduces_loader_counts(tmp_path: Path) -> None:
66
+ """Should reproduce the single-run finding, round, and fix counts when only one journal exists."""
67
+ projects_root = tmp_path / "projects"
68
+ seed_journal = _copy_fixture_run(projects_root, "session-a")
69
+ work_dir = tmp_path / "work"
70
+ work_dir.mkdir()
71
+
72
+ result = aggregate_runs.aggregate_pr_journals(
73
+ seed_journal, FIXTURE_OWNER, FIXTURE_REPO, FIXTURE_PR_NUMBER, work_dir
74
+ )
75
+
76
+ assert result.round_count == 4
77
+ assert len(result.findings) == 15
78
+ assert result.final_sha.startswith("7c2f420c")
79
+ run_data = render_report.load_run_data(result.combined_journal_path)
80
+ assert run_data.total_finding_count == 15
81
+ assert run_data.fix_commit_count == 2
82
+
83
+
84
+ def test_aggregate_two_runs_sums_rounds(tmp_path: Path) -> None:
85
+ """Should sum resolve-head rounds across every journal for the same PR."""
86
+ projects_root = tmp_path / "projects"
87
+ seed_journal = _copy_fixture_run(projects_root, "session-a")
88
+ _write_minimal_run(projects_root, "session-b", "wf_extra-001", FIXTURE_PR_NUMBER, 2)
89
+ work_dir = tmp_path / "work"
90
+ work_dir.mkdir()
91
+
92
+ result = aggregate_runs.aggregate_pr_journals(
93
+ seed_journal, FIXTURE_OWNER, FIXTURE_REPO, FIXTURE_PR_NUMBER, work_dir
94
+ )
95
+
96
+ assert result.round_count == 6
97
+ assert len(result.findings) == 15
98
+ assert result.final_sha.startswith("7c2f420c")
99
+
100
+
101
+ def test_force_remove_clears_existing_directory(tmp_path: Path) -> None:
102
+ """Should remove an existing directory tree without raising on any supported Python."""
103
+ target = tmp_path / "to-remove"
104
+ target.mkdir()
105
+ (target / "nested").mkdir()
106
+ (target / "nested" / "file.txt").write_text("content", encoding="utf-8")
107
+
108
+ aggregate_runs._force_remove(target)
109
+
110
+ assert not target.exists()
111
+
112
+
113
+ def test_force_remove_handler_keyword_is_version_guarded() -> None:
114
+ """Should pick onexc on Python 3.12+ and onerror otherwise, never bare onexc."""
115
+ handler_keyword = aggregate_runs._select_rmtree_handler_keyword()
116
+
117
+ if sys.version_info >= (3, 12):
118
+ assert set(handler_keyword) == {"onexc"}
119
+ else:
120
+ assert set(handler_keyword) == {"onerror"}
121
+
122
+
123
+ def test_discover_filters_by_owner_repo_and_pr_number(tmp_path: Path) -> None:
124
+ """Should return only journals whose args match the requested owner, repo, and PR."""
125
+ projects_root = tmp_path / "projects"
126
+ _copy_fixture_run(projects_root, "session-a")
127
+ _write_minimal_run(projects_root, "session-other", "wf_other-001", 999, 1)
128
+
129
+ discovered = aggregate_runs.discover_pr_journals(
130
+ projects_root, FIXTURE_OWNER, FIXTURE_REPO, FIXTURE_PR_NUMBER
131
+ )
132
+
133
+ assert len(discovered) == 1
134
+ assert discovered[0].stem == FIXTURE_RUN_ID
@@ -0,0 +1,132 @@
1
+ """Tests for convergence_summary.build_summary_prompt."""
2
+
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
7
+
8
+ import convergence_summary
9
+
10
+
11
+ SAMPLE_FINDINGS = [
12
+ {
13
+ "severity": "P0",
14
+ "category": "bug",
15
+ "file": "hooks/blocking/destructive_command_blocker.py",
16
+ "line": 42,
17
+ "title": "launcher-wrapped delete runs with no prompt",
18
+ "detail": "timeout 5 bash -c 'rm -rf /etc' bypasses the confirmation gate.",
19
+ },
20
+ {
21
+ "severity": "P2",
22
+ "category": "code-standard",
23
+ "file": "hooks/blocking/destructive_command_blocker.py",
24
+ "line": 9,
25
+ "title": "comment claims a refusal the code does not perform",
26
+ "detail": "",
27
+ },
28
+ ]
29
+
30
+
31
+ def test_prompt_carries_pr_coordinates_and_round_count() -> None:
32
+ """Should name the PR coordinates and the aggregated round count."""
33
+ prompt = convergence_summary.build_summary_prompt(
34
+ owner="jl-cmd",
35
+ repo="claude-code-config",
36
+ pr_number=581,
37
+ round_count=39,
38
+ findings=SAMPLE_FINDINGS,
39
+ fix_summaries=["renamed and annotated", "guarded the launcher"],
40
+ standards_note=None,
41
+ copilot_note=None,
42
+ )
43
+
44
+ assert "owner=jl-cmd repo=claude-code-config PR #581" in prompt
45
+ assert "https://github.com/jl-cmd/claude-code-config/pull/581" in prompt
46
+ assert "convergence in 39 round(s)" in prompt
47
+ assert "gh api repos/jl-cmd/claude-code-config/pulls/581" in prompt
48
+
49
+
50
+ def test_prompt_numbers_every_finding_with_severity_and_location() -> None:
51
+ """Should list each aggregated finding numbered with severity, file, and line."""
52
+ prompt = convergence_summary.build_summary_prompt(
53
+ owner="jl-cmd",
54
+ repo="claude-code-config",
55
+ pr_number=581,
56
+ round_count=39,
57
+ findings=SAMPLE_FINDINGS,
58
+ fix_summaries=[],
59
+ standards_note=None,
60
+ copilot_note=None,
61
+ )
62
+
63
+ assert (
64
+ "1. [P0/bug] hooks/blocking/destructive_command_blocker.py:42 - "
65
+ "launcher-wrapped delete runs with no prompt" in prompt
66
+ )
67
+ assert "2. [P2/code-standard]" in prompt
68
+ assert "Per-round fix summaries:\nnone" in prompt
69
+
70
+
71
+ def test_prompt_carries_notes_when_present() -> None:
72
+ """Should include the standards and copilot notes only when they are given."""
73
+ with_notes = convergence_summary.build_summary_prompt(
74
+ owner="o",
75
+ repo="r",
76
+ pr_number=1,
77
+ round_count=2,
78
+ findings=[],
79
+ fix_summaries=[],
80
+ standards_note="round 2 deferred a magic-value class",
81
+ copilot_note="Copilot out of quota",
82
+ )
83
+ without_notes = convergence_summary.build_summary_prompt(
84
+ owner="o",
85
+ repo="r",
86
+ pr_number=1,
87
+ round_count=2,
88
+ findings=[],
89
+ fix_summaries=[],
90
+ standards_note=None,
91
+ copilot_note=None,
92
+ )
93
+
94
+ assert "Deferred code-standard note: round 2 deferred a magic-value class" in (
95
+ with_notes
96
+ )
97
+ assert "Copilot gate note: Copilot out of quota" in with_notes
98
+ assert "Deferred code-standard note:" not in without_notes
99
+ assert "Copilot gate note:" not in without_notes
100
+
101
+
102
+ def test_prompt_imposes_no_issue_class_cap() -> None:
103
+ """Should instruct one class per distinct kind with no fixed class ceiling."""
104
+ prompt = convergence_summary.build_summary_prompt(
105
+ owner="o",
106
+ repo="r",
107
+ pr_number=1,
108
+ round_count=1,
109
+ findings=[],
110
+ fix_summaries=[],
111
+ standards_note=None,
112
+ copilot_note=None,
113
+ )
114
+
115
+ assert "Cap to about 5 classes" not in prompt
116
+ assert "one class per distinct KIND of problem" in prompt
117
+
118
+
119
+ def test_empty_findings_state_clean_run() -> None:
120
+ """Should render the clean-run sentence when no findings were caught."""
121
+ prompt = convergence_summary.build_summary_prompt(
122
+ owner="o",
123
+ repo="r",
124
+ pr_number=1,
125
+ round_count=1,
126
+ findings=[],
127
+ fix_summaries=[],
128
+ standards_note=None,
129
+ copilot_note=None,
130
+ )
131
+
132
+ assert "none - every lens was clean on a stable HEAD" in prompt