claude-dev-env 1.93.1 → 1.95.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 (262) hide show
  1. package/_shared/advisor/CLAUDE.md +2 -2
  2. package/_shared/advisor/advisor-protocol.md +35 -27
  3. package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +3 -2
  4. package/_shared/advisor/scripts/model_tier_run_validator.py +23 -15
  5. package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +81 -17
  6. package/_shared/pr-loop/CLAUDE.md +1 -0
  7. package/_shared/pr-loop/audit-contract.md +1 -1
  8. package/_shared/pr-loop/gh-payloads.md +3 -3
  9. package/_shared/pr-loop/post-audit-thread-contract.md +51 -0
  10. package/_shared/pr-loop/scripts/README.md +2 -2
  11. package/_shared/pr-loop/scripts/fix_hookspath.py +47 -12
  12. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +0 -69
  13. package/_shared/pr-loop/scripts/tests/test_fix_hookspath.py +497 -374
  14. package/_shared/pr-loop/state-schema.md +3 -22
  15. package/bin/CLAUDE.md +11 -2
  16. package/bin/ever-shipped-skills.mjs +70 -0
  17. package/bin/install.mjs +138 -13
  18. package/bin/install.prune.test.mjs +457 -0
  19. package/bin/install.test.mjs +10 -4
  20. package/docs/CODE_RULES.md +1 -1
  21. package/hooks/blocking/CLAUDE.md +2 -0
  22. package/hooks/blocking/code_rules_enforcer.py +4 -0
  23. package/hooks/blocking/code_rules_shared.py +82 -0
  24. package/hooks/blocking/code_rules_test_layout.py +9 -3
  25. package/hooks/blocking/pii_commit_command.py +398 -0
  26. package/hooks/blocking/pii_payload_scan.py +133 -0
  27. package/hooks/blocking/pii_prevention_blocker.py +151 -449
  28. package/hooks/blocking/plain_language_blocker.py +138 -4
  29. package/hooks/blocking/sensitive_file_protector.py +114 -48
  30. package/hooks/blocking/tdd_enforcer.py +9 -2
  31. package/hooks/blocking/test_code_rules_enforcer_scratchpad.py +105 -0
  32. package/hooks/blocking/test_code_rules_shared.py +181 -0
  33. package/hooks/blocking/test_pii_prevention_blocker.py +299 -0
  34. package/hooks/blocking/test_plain_language_blocker_allowlist.py +184 -0
  35. package/hooks/blocking/test_sensitive_file_protector.py +185 -0
  36. package/hooks/blocking/test_tdd_enforcer_scratchpad.py +105 -0
  37. package/hooks/blocking/test_verdict_directory_write_blocker.py +42 -7
  38. package/hooks/hooks_constants/CLAUDE.md +4 -2
  39. package/hooks/hooks_constants/code_rules_path_utils_constants.py +0 -2
  40. package/hooks/hooks_constants/harness_scratchpad_constants.py +17 -0
  41. package/hooks/hooks_constants/local_identity.py +65 -15
  42. package/hooks/hooks_constants/pii_prevention_constants.py +16 -0
  43. package/hooks/hooks_constants/plain_language_blocker_constants.py +5 -0
  44. package/hooks/hooks_constants/pr_description_enforcer_constants.py +1 -1
  45. package/hooks/hooks_constants/sensitive_file_protector_constants.py +42 -0
  46. package/hooks/hooks_constants/test_local_identity.py +68 -0
  47. package/hooks/pyproject.toml +75 -4
  48. package/hooks/validators/CLAUDE.md +1 -1
  49. package/hooks/validators/README.md +2 -0
  50. package/hooks/validators/python_style_checks.py +114 -136
  51. package/hooks/validators/python_style_helpers.py +95 -0
  52. package/hooks/validators/test_python_style_checks.py +0 -164
  53. package/hooks/validators/test_python_style_checks_decorator_gap.py +119 -0
  54. package/hooks/validators/test_python_style_fixes.py +251 -0
  55. package/hooks/validators/test_python_style_helpers.py +125 -0
  56. package/package.json +1 -1
  57. package/rules/CLAUDE.md +1 -0
  58. package/rules/anti-corollary-tests.md +69 -0
  59. package/rules/bdd.md +1 -3
  60. package/rules/code-reviews.md +1 -1
  61. package/rules/gh-paginate.md +1 -1
  62. package/rules/plain-language.md +2 -0
  63. package/scripts/CLAUDE.md +10 -1
  64. package/scripts/Get-SessionAccount.ps1 +210 -0
  65. package/scripts/tests/CLAUDE.md +15 -1
  66. package/scripts/tests/Get-SessionAccount.Tests.ps1 +350 -0
  67. package/skills/CLAUDE.md +6 -27
  68. package/skills/anthropic-plan/SKILL.md +1 -13
  69. package/skills/autoconverge/CLAUDE.md +5 -2
  70. package/skills/autoconverge/SKILL.md +112 -387
  71. package/skills/autoconverge/reference/CLAUDE.md +4 -1
  72. package/skills/autoconverge/reference/closing-report.md +9 -2
  73. package/skills/autoconverge/reference/convergence.md +26 -22
  74. package/skills/autoconverge/reference/headless-safety.md +44 -0
  75. package/skills/autoconverge/reference/multi-pr.md +88 -0
  76. package/skills/autoconverge/reference/self-closing-loop.md +84 -0
  77. package/skills/autoconverge/reference/stop-conditions.md +21 -13
  78. package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +87 -27
  79. package/skills/autoconverge/workflow/converge.contract.test.mjs +40 -10
  80. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +179 -47
  81. package/skills/autoconverge/workflow/converge.fix-progress.test.mjs +1 -1
  82. package/skills/autoconverge/workflow/converge.mjs +286 -96
  83. package/skills/bugteam/CLAUDE.md +1 -1
  84. package/skills/bugteam/CONSTRAINTS.md +3 -3
  85. package/skills/bugteam/PROMPTS.md +8 -19
  86. package/skills/bugteam/SKILL.md +13 -14
  87. package/skills/bugteam/reference/CLAUDE.md +1 -1
  88. package/skills/bugteam/reference/README.md +2 -3
  89. package/skills/bugteam/reference/audit-and-teammates.md +2 -2
  90. package/skills/bugteam/reference/copilot-gap-analysis.md +2 -0
  91. package/skills/bugteam/reference/github-pr-reviews.md +56 -38
  92. package/skills/bugteam/reference/team-setup.md +11 -11
  93. package/skills/bugteam/scripts/CLAUDE.md +6 -6
  94. package/skills/bugteam/scripts/README.md +20 -8
  95. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +13 -1463
  96. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +23 -358
  97. package/skills/bugteam/scripts/bugteam_preflight.py +13 -315
  98. package/skills/bugteam/scripts/bugteam_scripts_constants/CLAUDE.md +2 -3
  99. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +34 -1053
  100. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +30 -347
  101. package/skills/bugteam/scripts/test_bugteam_preflight.py +30 -305
  102. package/skills/closeout/SKILL.md +153 -0
  103. package/skills/closeout/reference/handoff-prompt-template.md +72 -0
  104. package/skills/closeout/reference/issue-body-templates.md +108 -0
  105. package/skills/closeout/reference/pii-redaction-checklist.md +36 -0
  106. package/skills/copilot-finding-triage/SKILL.md +80 -28
  107. package/skills/copilot-finding-triage/reference/tier-rubric.md +70 -11
  108. package/skills/copilot-finding-triage/templates/notification.md +8 -3
  109. package/skills/everything-search/SKILL.md +1 -2
  110. package/skills/grokify/SKILL.md +58 -0
  111. package/skills/grokify/templates/handoff-template.md +92 -0
  112. package/skills/orchestrator/SKILL.md +35 -25
  113. package/skills/orchestrator-refresh/SKILL.md +15 -11
  114. package/skills/pr-converge/CLAUDE.md +1 -1
  115. package/skills/pr-converge/SKILL.md +132 -233
  116. package/skills/pr-converge/reference/convergence-gates.md +102 -66
  117. package/skills/pr-converge/reference/examples.md +37 -25
  118. package/skills/pr-converge/reference/fix-protocol.md +7 -7
  119. package/skills/pr-converge/reference/multi-pr-orchestration.md +10 -4
  120. package/skills/pr-converge/reference/per-tick.md +64 -41
  121. package/skills/pr-converge/reference/state-schema.md +27 -2
  122. package/skills/pr-converge/scripts/CLAUDE.md +8 -0
  123. package/skills/pr-converge/scripts/README.md +4 -3
  124. package/skills/pr-converge/scripts/_pr_converge_path_setup.py +23 -0
  125. package/skills/pr-converge/scripts/check_convergence.py +189 -645
  126. package/skills/pr-converge/scripts/check_convergence_gates.py +235 -0
  127. package/skills/pr-converge/scripts/check_convergence_thread_gates.py +219 -0
  128. package/skills/pr-converge/scripts/conftest.py +8 -0
  129. package/skills/pr-converge/scripts/pr_converge_scripts_constants/CLAUDE.md +1 -0
  130. package/skills/pr-converge/scripts/pr_converge_scripts_constants/convergence_gate_constants.py +36 -0
  131. package/skills/pr-converge/scripts/test__pr_converge_path_setup.py +26 -0
  132. package/skills/pr-converge/scripts/test_check_convergence.py +73 -228
  133. package/skills/pr-converge/scripts/test_check_convergence_contract.py +321 -0
  134. package/skills/pr-converge/scripts/test_check_convergence_gates.py +68 -0
  135. package/skills/pr-converge/scripts/test_check_convergence_thread_gates.py +75 -0
  136. package/skills/pr-fix-protocol/SKILL.md +20 -20
  137. package/skills/privacy-hygiene/SKILL.md +4 -3
  138. package/skills/reviewer-gates/SKILL.md +26 -8
  139. package/skills/session-log/SKILL.md +11 -12
  140. package/skills/session-tidy/SKILL.md +40 -30
  141. package/skills/skill-builder/CLAUDE.md +13 -10
  142. package/skills/skill-builder/SKILL.md +31 -31
  143. package/skills/skill-builder/references/CLAUDE.md +10 -6
  144. package/skills/skill-builder/references/delegation-map.md +13 -4
  145. package/skills/skill-builder/references/description-field.md +113 -0
  146. package/skills/skill-builder/references/self-audit-checklist.md +21 -4
  147. package/skills/skill-builder/references/skill-modularity.md +113 -0
  148. package/skills/skill-builder/references/skill-types.md +4 -0
  149. package/skills/skill-builder/templates/CLAUDE.md +4 -4
  150. package/skills/skill-builder/templates/gap-analysis.md +25 -1
  151. package/skills/skill-builder/workflows/CLAUDE.md +6 -4
  152. package/skills/skill-builder/workflows/improve-skill.md +17 -16
  153. package/skills/skill-builder/workflows/new-skill.md +34 -10
  154. package/skills/skill-builder/workflows/polish-skill.md +59 -43
  155. package/skills/team-advisor/SKILL.md +15 -11
  156. package/skills/usage-pause/SKILL.md +10 -14
  157. package/system-prompts/software-engineer.xml +7 -6
  158. package/hooks/validators/test_verify_paths.py +0 -32
  159. package/hooks/validators/verify_paths.py +0 -57
  160. package/skills/bdd-protocol/CLAUDE.md +0 -26
  161. package/skills/bdd-protocol/SKILL.md +0 -30
  162. package/skills/bdd-protocol/references/CLAUDE.md +0 -10
  163. package/skills/bdd-protocol/references/anti-patterns.md +0 -26
  164. package/skills/bdd-protocol/references/example-mapping.md +0 -23
  165. package/skills/bg-agent/CLAUDE.md +0 -17
  166. package/skills/bg-agent/SKILL.md +0 -69
  167. package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_code_rules_gate_constants.py +0 -55
  168. package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_fix_hookspath_constants.py +0 -26
  169. package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_preflight_constants.py +0 -35
  170. package/skills/caveman/CLAUDE.md +0 -15
  171. package/skills/caveman/SKILL.md +0 -38
  172. package/skills/code/CLAUDE.md +0 -17
  173. package/skills/code/SKILL.md +0 -178
  174. package/skills/deep-research/CLAUDE.md +0 -17
  175. package/skills/deep-research/SKILL.md +0 -80
  176. package/skills/findbugs/CLAUDE.md +0 -20
  177. package/skills/findbugs/SKILL.md +0 -241
  178. package/skills/fixbugs/CLAUDE.md +0 -19
  179. package/skills/fixbugs/SKILL.md +0 -142
  180. package/skills/gh-paginate/CLAUDE.md +0 -18
  181. package/skills/gh-paginate/SKILL.md +0 -84
  182. package/skills/gotcha/CLAUDE.md +0 -33
  183. package/skills/gotcha/SKILL.md +0 -73
  184. package/skills/implement/CLAUDE.md +0 -27
  185. package/skills/implement/SKILL.md +0 -66
  186. package/skills/implement/scripts/CLAUDE.md +0 -22
  187. package/skills/implement/scripts/append_note.py +0 -133
  188. package/skills/implement/scripts/implement_scripts_constants/CLAUDE.md +0 -22
  189. package/skills/implement/scripts/implement_scripts_constants/__init__.py +0 -0
  190. package/skills/implement/scripts/implement_scripts_constants/notes_constants.py +0 -12
  191. package/skills/implement/scripts/test_append_note.py +0 -191
  192. package/skills/log-audit/CLAUDE.md +0 -20
  193. package/skills/log-audit/SKILL.md +0 -66
  194. package/skills/log-audit/reference/CLAUDE.md +0 -9
  195. package/skills/log-audit/reference/charter.md +0 -52
  196. package/skills/log-audit/scripts/CLAUDE.md +0 -27
  197. package/skills/log-audit/scripts/cluster_recurrences.py +0 -261
  198. package/skills/log-audit/scripts/collect_log_window.py +0 -199
  199. package/skills/log-audit/scripts/log_audit_constants/CLAUDE.md +0 -12
  200. package/skills/log-audit/scripts/log_audit_constants/__init__.py +0 -0
  201. package/skills/log-audit/scripts/log_audit_constants/cluster_recurrences_constants.py +0 -23
  202. package/skills/log-audit/scripts/log_audit_constants/collect_log_window_constants.py +0 -24
  203. package/skills/log-audit/scripts/log_audit_constants/mine_copilot_findings_constants.py +0 -49
  204. package/skills/log-audit/scripts/mine_copilot_findings.py +0 -302
  205. package/skills/log-audit/scripts/test_cluster_recurrences.py +0 -160
  206. package/skills/log-audit/scripts/test_collect_log_window.py +0 -111
  207. package/skills/log-audit/scripts/test_mine_copilot_findings.py +0 -126
  208. package/skills/logifix/CLAUDE.md +0 -36
  209. package/skills/logifix/SKILL.md +0 -69
  210. package/skills/logifix/scripts/CLAUDE.md +0 -16
  211. package/skills/logifix/scripts/logifix.ps1 +0 -205
  212. package/skills/monitor-open-prs/CLAUDE.md +0 -34
  213. package/skills/monitor-open-prs/SKILL.md +0 -88
  214. package/skills/monitor-open-prs/scripts/CLAUDE.md +0 -17
  215. package/skills/monitor-open-prs/scripts/discover_open_prs.py +0 -69
  216. package/skills/monitor-open-prs/scripts/test_discover_open_prs.py +0 -149
  217. package/skills/monitor-open-prs/test_skill_contract.py +0 -27
  218. package/skills/post-audit-findings/SKILL.md +0 -80
  219. package/skills/pr-consistency-audit/CLAUDE.md +0 -34
  220. package/skills/pr-consistency-audit/SKILL.md +0 -116
  221. package/skills/pr-consistency-audit/reference/CLAUDE.md +0 -16
  222. package/skills/pr-consistency-audit/reference/detection-rules.md +0 -96
  223. package/skills/pr-consistency-audit/reference/illustrations.md +0 -78
  224. package/skills/pr-review-responder/CLAUDE.md +0 -35
  225. package/skills/pr-review-responder/EXAMPLES.md +0 -590
  226. package/skills/pr-review-responder/PRINCIPLES.md +0 -533
  227. package/skills/pr-review-responder/README.md +0 -168
  228. package/skills/pr-review-responder/SKILL.md +0 -203
  229. package/skills/pr-review-responder/TESTING.md +0 -350
  230. package/skills/pr-scope-resolve/SKILL.md +0 -45
  231. package/skills/pre-compact/CLAUDE.md +0 -24
  232. package/skills/pre-compact/SKILL.md +0 -134
  233. package/skills/qbug/CLAUDE.md +0 -40
  234. package/skills/qbug/SKILL.md +0 -387
  235. package/skills/qbug/test_qbug_skill_audit_schema.py +0 -152
  236. package/skills/qbug/test_qbug_skill_post_fix_audit.py +0 -103
  237. package/skills/refine/CLAUDE.md +0 -44
  238. package/skills/refine/SKILL.md +0 -262
  239. package/skills/refine/templates/CLAUDE.md +0 -17
  240. package/skills/refine/templates/implementation-notes-template.html +0 -56
  241. package/skills/refine/templates/plan-template.md +0 -60
  242. package/skills/research-mode/CLAUDE.md +0 -35
  243. package/skills/research-mode/SKILL.md +0 -53
  244. package/skills/structure-prompt/CLAUDE.md +0 -42
  245. package/skills/structure-prompt/SKILL.md +0 -44
  246. package/skills/structure-prompt/reference/CLAUDE.md +0 -28
  247. package/skills/structure-prompt/reference/adversarial-tuning.md +0 -62
  248. package/skills/structure-prompt/reference/block-classification.md +0 -27
  249. package/skills/structure-prompt/reference/canonical-case.md +0 -48
  250. package/skills/structure-prompt/reference/citation-depth.md +0 -70
  251. package/skills/structure-prompt/reference/cleanup.md +0 -33
  252. package/skills/structure-prompt/reference/constraints.md +0 -33
  253. package/skills/structure-prompt/reference/directives.md +0 -37
  254. package/skills/structure-prompt/reference/examples.md +0 -72
  255. package/skills/structure-prompt/reference/instantiation.md +0 -51
  256. package/skills/structure-prompt/reference/output-contract.md +0 -72
  257. package/skills/structure-prompt/reference/per-category.md +0 -23
  258. package/skills/structure-prompt/reference/persona.md +0 -38
  259. package/skills/structure-prompt/reference/research.md +0 -33
  260. package/skills/structure-prompt/reference/structure.md +0 -28
  261. package/skills/verified-build/CLAUDE.md +0 -33
  262. package/skills/verified-build/SKILL.md +0 -33
@@ -1,316 +1,41 @@
1
- """Tests for bugteam_preflight git hooks path verification.
1
+ """Smoke tests for the skill-path preflight wrap.
2
2
 
3
- Covers:
4
- - core.hooksPath unset: exits non-zero with correction message
5
- - core.hooksPath pointing to the correct claude hooks dir: exits zero
6
- - core.hooksPath pointing elsewhere (husky override): exits non-zero
7
- - core.hooksPath with trailing slash: must still pass after normalization
3
+ Behavioral coverage lives at
4
+ ``_shared/pr-loop/scripts/tests/test_preflight.py``.
8
5
  """
9
6
 
10
7
  from __future__ import annotations
11
8
 
12
9
  import importlib.util
13
- import subprocess
14
- import sys
15
10
  from pathlib import Path
16
11
  from types import ModuleType
17
- from unittest.mock import MagicMock, patch
18
12
 
19
- import pytest
20
13
 
21
-
22
- def _load_preflight_module() -> ModuleType:
14
+ def _load_wrap_module() -> ModuleType:
23
15
  module_path = Path(__file__).parent / "bugteam_preflight.py"
24
- spec = importlib.util.spec_from_file_location("bugteam_preflight", module_path)
25
- assert spec is not None
26
- assert spec.loader is not None
27
- module = importlib.util.module_from_spec(spec)
28
- spec.loader.exec_module(module)
29
- return module
30
-
31
-
32
- bugteam_preflight = _load_preflight_module()
33
-
34
-
35
- def _make_completed_process(
36
- stdout: str, returncode: int
37
- ) -> subprocess.CompletedProcess:
38
- process = MagicMock(spec=subprocess.CompletedProcess)
39
- process.stdout = stdout
40
- process.returncode = returncode
41
- return process
42
-
43
-
44
- def test_should_exit_nonzero_when_core_hooks_path_unset(capsys: pytest.CaptureFixture[str]) -> None:
45
- with patch("subprocess.run") as mock_run:
46
- mock_run.return_value = _make_completed_process("", returncode=1)
47
- exit_code = bugteam_preflight.verify_git_hooks_path(Path("."))
48
- assert exit_code != 0
49
- captured = capsys.readouterr()
50
- assert "core.hooksPath" in captured.err
51
- assert "npx claude-dev-env" in captured.err or "git config" in captured.err
52
-
53
-
54
- def test_should_exit_zero_when_core_hooks_path_points_to_claude_hooks(tmp_path: Path) -> None:
55
- claude_hooks_path = tmp_path / ".claude" / "hooks" / "git-hooks"
56
- claude_hooks_path.mkdir(parents=True)
57
- with patch("subprocess.run") as mock_run:
58
- mock_run.return_value = _make_completed_process(
59
- str(claude_hooks_path) + "\n", returncode=0
60
- )
61
- exit_code = bugteam_preflight.verify_git_hooks_path(Path("."))
62
- assert exit_code == 0
63
-
64
-
65
- def test_should_exit_nonzero_when_core_hooks_path_points_elsewhere(capsys: pytest.CaptureFixture[str]) -> None:
66
- with patch("subprocess.run") as mock_run:
67
- mock_run.return_value = _make_completed_process(
68
- "/some/other/path/.husky\n", returncode=0
69
- )
70
- exit_code = bugteam_preflight.verify_git_hooks_path(Path("."))
71
- assert exit_code != 0
72
- captured = capsys.readouterr()
73
- assert "core.hooksPath" in captured.err
74
-
75
-
76
- def test_should_include_correction_commands_in_error_message(capsys: pytest.CaptureFixture[str]) -> None:
77
- with patch("subprocess.run") as mock_run:
78
- mock_run.return_value = _make_completed_process("", returncode=1)
79
- bugteam_preflight.verify_git_hooks_path(Path("."))
80
- captured = capsys.readouterr()
81
- assert (
82
- "npx claude-dev-env" in captured.err
83
- or "git config --global core.hooksPath" in captured.err
84
- )
85
-
86
-
87
- def test_main_should_exit_nonzero_when_hooks_path_unset() -> None:
88
- with patch("subprocess.run") as mock_run:
89
- mock_run.return_value = _make_completed_process("", returncode=1)
90
- exit_code = bugteam_preflight.main(["--no-pytest"])
91
- assert exit_code != 0
92
-
93
-
94
- def test_main_should_continue_when_hooks_path_valid(tmp_path: Path) -> None:
95
- claude_hooks_path = tmp_path / ".claude" / "hooks" / "git-hooks"
96
- claude_hooks_path.mkdir(parents=True)
97
- with patch("subprocess.run") as mock_run:
98
- mock_run.return_value = _make_completed_process(
99
- str(claude_hooks_path) + "\n", returncode=0
100
- )
101
- exit_code = bugteam_preflight.main(["--no-pytest"])
102
- assert exit_code == 0
103
-
104
-
105
- def test_should_accept_hooks_path_with_trailing_slash() -> None:
106
- with patch("subprocess.run") as mock_run:
107
- mock_run.return_value = _make_completed_process(
108
- "/home/user/.claude/hooks/git-hooks/\n", returncode=0
109
- )
110
- exit_code = bugteam_preflight.verify_git_hooks_path(Path("."))
111
- assert exit_code == 0, (
112
- "hooksPath with trailing slash must pass verification after normalization"
113
- )
114
-
115
-
116
- def test_should_exit_zero_when_hooks_path_set_at_repo_scope(tmp_path: Path) -> None:
117
- claude_hooks_path = tmp_path / ".claude" / "hooks" / "git-hooks"
118
- claude_hooks_path.mkdir(parents=True)
119
- repo_root = tmp_path / "my-repo"
120
- repo_root.mkdir()
121
- with patch("subprocess.run") as mock_run:
122
- mock_run.return_value = _make_completed_process(
123
- str(claude_hooks_path) + "\n", returncode=0
124
- )
125
- exit_code = bugteam_preflight.verify_git_hooks_path(repo_root)
126
- assert exit_code == 0, (
127
- "verify_git_hooks_path must accept a valid path returned by effective "
128
- "config query (not restricted to --global scope)"
129
- )
130
- called_command = mock_run.call_args[0][0]
131
- assert "--global" not in called_command, (
132
- "verify_git_hooks_path must query effective config, not --global only"
133
- )
134
- assert "-C" in called_command, (
135
- "verify_git_hooks_path must use git -C <repo_root> for repo-effective config"
136
- )
137
- dash_c_index = called_command.index("-C")
138
- assert called_command[dash_c_index + 1] == str(repo_root), (
139
- "git -C must receive the resolved repository root path"
140
- )
141
-
142
-
143
- def test_verify_git_hooks_path_accepts_none_repository_root(tmp_path: Path) -> None:
144
- """When repository_root is None, the call must use git's cwd-effective config.
145
-
146
- Binds the documented optional contract: passing None must not raise and must
147
- omit the `-C <root>` arguments so git falls back to the working directory.
148
- """
149
- claude_hooks_path = tmp_path / ".claude" / "hooks" / "git-hooks"
150
- claude_hooks_path.mkdir(parents=True)
151
- with patch("subprocess.run") as mock_run:
152
- mock_run.return_value = _make_completed_process(
153
- str(claude_hooks_path) + "\n", returncode=0
154
- )
155
- exit_code = bugteam_preflight.verify_git_hooks_path(None)
156
- assert exit_code == 0
157
- called_command = mock_run.call_args[0][0]
158
- assert "-C" not in called_command, (
159
- "verify_git_hooks_path(None) must omit -C so git uses cwd-effective config"
160
- )
161
-
162
-
163
- def test_should_accept_hooks_path_with_backslash_and_trailing_slash() -> None:
164
- with patch("subprocess.run") as mock_run:
165
- mock_run.return_value = _make_completed_process(
166
- "C:\\Users\\user\\.claude\\hooks\\git-hooks\\\n", returncode=0
167
- )
168
- exit_code = bugteam_preflight.verify_git_hooks_path(Path("."))
169
- assert exit_code == 0, (
170
- "Windows hooksPath with trailing backslash must pass after normalization"
171
- )
172
-
173
-
174
- def test_should_exit_nonzero_when_git_executable_not_found(
175
- capsys: pytest.CaptureFixture[str],
176
- ) -> None:
177
- """Preflight must not crash with a traceback when git is missing from PATH."""
178
- with patch("subprocess.run", side_effect=FileNotFoundError()):
179
- exit_code = bugteam_preflight.verify_git_hooks_path(Path("."))
180
- assert exit_code != 0, (
181
- "FileNotFoundError from subprocess.run must produce a non-zero exit, "
182
- "not a propagated traceback"
183
- )
184
- captured = capsys.readouterr()
185
- assert "git" in captured.err.lower(), (
186
- "Error message must mention git so the user knows what is missing"
187
- )
188
- assert (
189
- "npx claude-dev-env" in captured.err
190
- or "git config --global core.hooksPath" in captured.err
191
- ), "Error message must include the enforcement-absent remediation hints"
192
-
193
-
194
- def test_should_exit_nonzero_when_subprocess_run_raises_os_error(
195
- capsys: pytest.CaptureFixture[str],
196
- ) -> None:
197
- """Preflight must surface a clean error for other OS-level git launch failures."""
198
- with patch("subprocess.run", side_effect=OSError("permission denied")):
199
- exit_code = bugteam_preflight.verify_git_hooks_path(Path("."))
200
- assert exit_code != 0, (
201
- "OSError from subprocess.run must produce a non-zero exit, "
202
- "not a propagated traceback"
203
- )
204
- captured = capsys.readouterr()
205
- assert "bugteam_preflight" in captured.err, (
206
- "Error message must be prefixed with the preflight tool name for context"
207
- )
208
- assert "permission denied" in captured.err, (
209
- "Error message must include the underlying OSError detail for diagnosis"
210
- )
211
-
212
-
213
- def test_has_pytest_configuration_finds_pytest_ini(tmp_path: Path) -> None:
214
- """has_pytest_configuration must detect pytest.ini at the repo root.
215
-
216
- Regression for loop1-17/loop1-18: the literals "pytest.ini",
217
- "pyproject.toml", and "[tool.pytest" were inlined in production function
218
- bodies; centralizing them in bugteam_scripts_constants and importing here
219
- pins the contract.
220
- """
221
- repository_root = tmp_path / "repo"
222
- repository_root.mkdir()
223
- (repository_root / "pytest.ini").write_text("[pytest]\n", encoding="utf-8")
224
- assert bugteam_preflight.has_pytest_configuration(repository_root) is True
225
-
226
-
227
- def test_has_pytest_configuration_finds_pyproject_pytest_section(
228
- tmp_path: Path,
229
- ) -> None:
230
- repository_root = tmp_path / "repo"
231
- repository_root.mkdir()
232
- (repository_root / "pyproject.toml").write_text(
233
- "[tool.pytest.ini_options]\nminversion = '6.0'\n", encoding="utf-8"
234
- )
235
- assert bugteam_preflight.has_pytest_configuration(repository_root) is True
236
-
237
-
238
- def test_has_pytest_configuration_returns_false_without_either_file(
239
- tmp_path: Path,
240
- ) -> None:
241
- repository_root = tmp_path / "repo"
242
- repository_root.mkdir()
243
- assert bugteam_preflight.has_pytest_configuration(repository_root) is False
244
-
245
-
246
- def test_main_should_halt_when_env_var_lists_bugteam(
247
- monkeypatch: pytest.MonkeyPatch,
248
- capsys: pytest.CaptureFixture[str],
249
- ) -> None:
250
- """CLAUDE_REVIEWS_DISABLED=bugteam must halt preflight with the dedicated exit code."""
251
- monkeypatch.setenv("CLAUDE_REVIEWS_DISABLED", "bugteam")
252
- monkeypatch.delenv("BUGTEAM_PREFLIGHT_SKIP", raising=False)
253
- exit_code = bugteam_preflight.main(["--no-pytest"])
254
- assert exit_code == bugteam_preflight.EXIT_CODE_BUGTEAM_DISABLED_VIA_ENV
255
- captured = capsys.readouterr()
256
- assert "CLAUDE_REVIEWS_DISABLED" in captured.err
257
- assert "bugteam" in captured.err
258
-
259
-
260
- def test_main_should_continue_when_env_var_omits_bugteam(
261
- monkeypatch: pytest.MonkeyPatch,
262
- tmp_path: Path,
263
- ) -> None:
264
- """CLAUDE_REVIEWS_DISABLED without the bugteam token must not halt preflight."""
265
- monkeypatch.setenv("CLAUDE_REVIEWS_DISABLED", "copilot,bugbot")
266
- monkeypatch.delenv("BUGTEAM_PREFLIGHT_SKIP", raising=False)
267
- claude_hooks_path = tmp_path / ".claude" / "hooks" / "git-hooks"
268
- claude_hooks_path.mkdir(parents=True)
269
- with patch("subprocess.run") as mock_run:
270
- mock_run.return_value = _make_completed_process(
271
- str(claude_hooks_path) + "\n", returncode=0
272
- )
273
- exit_code = bugteam_preflight.main(["--no-pytest"])
274
- assert exit_code != bugteam_preflight.EXIT_CODE_BUGTEAM_DISABLED_VIA_ENV
275
-
276
-
277
- def test_main_should_halt_when_env_var_contains_uppercase_or_whitespace_bugteam_token(
278
- monkeypatch: pytest.MonkeyPatch,
279
- ) -> None:
280
- """Token matching must be case-insensitive and whitespace-tolerant."""
281
- monkeypatch.setenv("CLAUDE_REVIEWS_DISABLED", " BugTeam , copilot ")
282
- monkeypatch.delenv("BUGTEAM_PREFLIGHT_SKIP", raising=False)
283
- exit_code = bugteam_preflight.main(["--no-pytest"])
284
- assert exit_code == bugteam_preflight.EXIT_CODE_BUGTEAM_DISABLED_VIA_ENV
285
-
286
-
287
- def _was_called_with_argument(
288
- mock_subprocess_run: MagicMock, argument_token: str
289
- ) -> bool:
290
- return any(
291
- argument_token in each_call_args[0][0]
292
- for each_call_args in mock_subprocess_run.call_args_list
293
- )
294
-
295
-
296
- def test_verify_git_hooks_path_invokes_self_heal_before_effective_query(
297
- tmp_path: Path,
298
- ) -> None:
299
- """verify_git_hooks_path must delegate to the shared self-heal helper before --get."""
300
- canonical_hooks_path = tmp_path / ".claude" / "hooks" / "git-hooks"
301
- canonical_hooks_path.mkdir(parents=True)
302
- with patch("subprocess.run") as mock_run:
303
- mock_run.return_value = _make_completed_process(
304
- str(canonical_hooks_path) + "\n", returncode=0
305
- )
306
- bugteam_preflight.verify_git_hooks_path(tmp_path)
307
- assert _was_called_with_argument(mock_run, "--get-all"), (
308
- "verify_git_hooks_path must run the --local --get-all read for self-heal"
309
- )
310
- assert _was_called_with_argument(mock_run, "--get"), (
311
- "verify_git_hooks_path must still run the effective --get verification"
312
- )
313
- first_called_command = mock_run.call_args_list[0][0][0]
314
- assert "--get-all" in first_called_command, (
315
- "Self-heal must run BEFORE the effective config query, not after"
316
- )
16
+ specification = importlib.util.spec_from_file_location(
17
+ "bugteam_preflight",
18
+ module_path,
19
+ )
20
+ assert specification is not None
21
+ assert specification.loader is not None
22
+ wrap_module = importlib.util.module_from_spec(specification)
23
+ specification.loader.exec_module(wrap_module)
24
+ return wrap_module
25
+
26
+
27
+ def test_wrap_main_delegates_to_shared_module() -> None:
28
+ wrap_module = _load_wrap_module()
29
+ assert wrap_module.main.__module__ == "preflight"
30
+ shared_filename = Path(wrap_module.main.__code__.co_filename).name
31
+ assert shared_filename == "preflight.py"
32
+
33
+
34
+ def test_wrap_main_help_exits_cleanly() -> None:
35
+ wrap_module = _load_wrap_module()
36
+ try:
37
+ wrap_module.main(["--help"])
38
+ except SystemExit as exit_signal:
39
+ assert exit_signal.code in (0, None)
40
+ return
41
+ raise AssertionError("main(['--help']) must raise SystemExit")
@@ -0,0 +1,153 @@
1
+ ---
2
+ name: closeout
3
+ description: >-
4
+ Harvests session obstacles into GitHub issues backed by quoted evidence at
5
+ session end, validates each draft with the user, dedupes against open and
6
+ closed issues, routes each to its repo, files children then a parent
7
+ checklist, and prints a computed cloud handoff prompt. Triggers: /closeout,
8
+ close out this session, close out the session, file the session obstacles,
9
+ session closeout, harvest session obstacles, end-of-session issue filing.
10
+ Near-miss: not session-log, which journals the session to the vault; closeout
11
+ files GitHub issues and prints a handoff prompt, and writes no session
12
+ journal.
13
+ ---
14
+
15
+ # Closeout
16
+
17
+ **Core principle:** At the end of a working session, turn the obstacles the session hit into user-approved GitHub issues — each backed by a quoted line — plus a computed cloud handoff prompt, never guessing, never filing without approval, never touching the host repo's live pipeline.
18
+
19
+ ## Gotchas
20
+
21
+ Highest-signal content. Append a bullet each time a run fails in a new way.
22
+
23
+ - An obstacle stated from memory drifts. File only what the session can quote word for word — the actual error text, the exact command, the exact log line. A candidate that cannot be quoted goes under "Unverified candidates" for the user to judge, never into a filed issue as fact.
24
+ - Skipping the confirmation gate files noise to a shared server that other people read. Every parent and child draft passes the AskUserQuestion gate before any write.
25
+ - A body that leans on chat context reads as a puzzle to anyone who opens the issue cold. Write each body so a reader with zero session context acts on it: name the failure, the count, and the quoted line.
26
+ - A `--body` string mangles backticks on GitHub — they land as literal `\``. Every `gh` create and comment uses `--body-file <path>`.
27
+ - A dedupe search that skips closed issues re-files a twin the team already resolved. The search covers `--state all`.
28
+ - A volatile path in an issue body breaks the moment the job scratch is cleaned. Keep temp dirs, worktrees, and `$CLAUDE_JOB_DIR` out of every body.
29
+
30
+ ## When this skill applies
31
+
32
+ Run this skill **at the end** of a working session, from inside that same session, when the session hit obstacles worth filing — hook blocks, gates that fired wrongly, tools that failed, forced workarounds, dead ends.
33
+
34
+ Triggers: `/closeout`, "close out this session", "file the session obstacles", "session closeout", "harvest session obstacles", "end-of-session issue filing".
35
+
36
+ **Refusal cases — first match wins:**
37
+
38
+ - **Mid-session, work still open.** Respond: `Closeout runs at session end. Keep working, and run /closeout once the session's work is done.`
39
+ - **Asked to journal the session.** Respond: `Closeout files GitHub issues; it does not write a session journal. For a session report to the vault, use /session-log.`
40
+ - **No obstacles this session.** Respond: `No obstacles to file — the session hit no hook blocks, tool failures, or dead ends worth an issue. Nothing to close out.`
41
+
42
+ ## The process
43
+
44
+ ```
45
+ - [ ] Phase 1 — Harvest obstacles from the three sources; quote verbatim evidence
46
+ - [ ] Phase 1 — Run the PII pass over every candidate
47
+ - [ ] Phase 2 — Draft the parent + children set
48
+ - [ ] Phase 2 — Confirmation gate: AskUserQuestion, file only on approval
49
+ - [ ] Phase 3 — Dedupe each candidate against open and closed issues
50
+ - [ ] Phase 4 — Route each issue to its repo
51
+ - [ ] Phase 5 — File children, then the parent checklist
52
+ - [ ] Phase 6 — Print the computed cloud handoff prompt in chat
53
+ ```
54
+
55
+ ### Track the phases on the task list
56
+
57
+ At invocation, copy the six phases onto the session task list — one task each via TaskCreate: harvest, draft + user validation, dedupe, repo routing, filing, handoff prompt. Mark a task `in_progress` with TaskUpdate when its phase starts and `completed` when the phase finishes.
58
+
59
+ Hold one line on the filing task: never mark it `completed` while any planned issue is still uncreated. A filing phase that lands fewer issues than the approved set keeps the filing task open, with the missing issues named on it.
60
+
61
+ ### Phase 1 — Harvest obstacles
62
+
63
+ Read three sources, in order:
64
+
65
+ 1. **This session's conversation** — the chat log visible in context.
66
+ 2. **The session task list** — TaskCreate/TaskUpdate records read through TaskList and TaskGet.
67
+ 3. **Tool results still in the context window** — hook denials, command output, log tails.
68
+
69
+ An obstacle is a hook block, a gate that fired wrongly, a tool that failed, a forced workaround, or a dead end.
70
+
71
+ **Non-negotiable evidence rule:** every filed issue quotes verbatim evidence captured this session — the actual error text, the exact command, the exact log line. An obstacle you cannot quote is dropped, or listed under a "Unverified candidates" section of the drafts for the user to decide. It is never filed as fact.
72
+
73
+ **PII pass (runs on every run):** strip personal data from every issue body and from the handoff prompt — emails, real names, home paths, private hosts and IPs, account ids, tokens. The pass runs whether the target repo is public or private; repository visibility changes only how aggressive the redaction is (public repos get the strictest pass), never whether the pass runs. Checklist and swaps: [reference/pii-redaction-checklist.md](reference/pii-redaction-checklist.md).
74
+
75
+ ### Phase 2 — Draft and validate with the user
76
+
77
+ Build the parent → children issue set as drafts. Body shapes and worked examples: [reference/issue-body-templates.md](reference/issue-body-templates.md).
78
+
79
+ Then the **mandatory confirmation gate**. Present through AskUserQuestion:
80
+
81
+ - Each drafted parent and child — title, target repo, one line of scope each.
82
+ - Any PII concern the pass found.
83
+ - Any closed twin a dedupe search surfaced (see Phase 3), as a reopen/comment/file-new choice.
84
+
85
+ Filing to GitHub is an irreversible write to a shared server that other people read. File only on explicit user approval. The user validates every finding before anything is posted.
86
+
87
+ ### Phase 3 — Dedupe
88
+
89
+ Before filing each candidate, search open and closed issues on the target repo:
90
+
91
+ ```
92
+ gh issue list --search "<terms>" --state all
93
+ ```
94
+
95
+ - **Open twin exists** → comment on it, rather than filing a new issue.
96
+ - **Closed twin exists** → do not silently file or comment. Surface it in the Phase 2 gate as "previously closed twin — reopen, comment, or file new" for the user to decide.
97
+
98
+ `gh issue list` needs no pagination flags. If you show a `gh api` read of a paginated list endpoint anywhere, show `--paginate --slurp` piped to external `jq` — `gh`'s built-in `--jq` runs per page and gives wrong cross-page results.
99
+
100
+ ### Phase 4 — Repo routing
101
+
102
+ Route each issue by a deterministic rule:
103
+
104
+ - The evidence names a file under the dev-env tree — `packages/claude-dev-env/hooks/`, `rules/`, `skills/`, `commands/`, `agents/`, `bin/`, or `docs/` — or an installed copy of those under `~/.claude/` (hooks, rules, skills, commands, agents) → file against **claude-dev-env**.
105
+ - Otherwise → the **working repo**, read live:
106
+
107
+ ```
108
+ gh repo view --json nameWithOwner
109
+ ```
110
+
111
+ - **Cross-repo case** — a hook shipped by repo B blocked work in repo A → file against **B** and reference A in the body.
112
+
113
+ ### Phase 5 — File
114
+
115
+ File **children first**, then the parent. The parent body is a checklist of `- [ ] owner/repo#N` lines, one per child created.
116
+
117
+ - Every `gh issue create` and `gh issue comment` uses `--body-file <path>`, never `--body`.
118
+ - No volatile paths in any body: no temp dirs, no worktrees, no `$CLAUDE_JOB_DIR`, no `.claude-editor/jobs` or `.claude/worktrees` paths.
119
+ - Bodies are self-contained and specific: the failure mode, the count, and the quoted line — not "improve error handling".
120
+
121
+ ### Phase 6 — Computed handoff prompt
122
+
123
+ Print **in chat** (not a file) a prompt the user pastes into a cloud session that opens PRs for the filed issues and drives them to convergence. Template and worked example: [reference/handoff-prompt-template.md](reference/handoff-prompt-template.md).
124
+
125
+ The prompt is computed, not a bare list. It carries:
126
+
127
+ 1. **Safety boundaries** — what must never be run, merged, deployed, or synced. The working repo's pipeline is live in production.
128
+ 2. **Base branch and verification commands** — read the base branch and the per-package verification commands from the target repo's CLAUDE.md and docs at runtime.
129
+ 3. **Dependency order** — which issue must land before which, and which issues touch the same files and so must stack on one branch rather than run in parallel.
130
+
131
+ If any issue cannot be done from a cloud session — it needs a local environment, physical devices, or a private network — the prompt says so per issue and scopes the cloud work to what a cloud session can reach.
132
+
133
+ ## Skill boundaries
134
+
135
+ This skill runs inside a repo whose pipeline is live in production. Hold these lines:
136
+
137
+ - It never runs that pipeline, never merges, never deploys, never syncs.
138
+ - It never runs the host repo's automations.
139
+ - It creates issues and comments and prints text. Nothing else.
140
+
141
+ ## File index
142
+
143
+ | File | Purpose |
144
+ |------|---------|
145
+ | `SKILL.md` | This hub — core principle, gotchas, refusal cases, six-phase process, boundaries |
146
+ | `reference/issue-body-templates.md` | Parent and child issue body shapes with worked examples |
147
+ | `reference/pii-redaction-checklist.md` | The PII pass: categories, swaps, public-versus-private aggression |
148
+ | `reference/handoff-prompt-template.md` | The computed cloud handoff prompt shape with a worked example |
149
+
150
+ ## Folder map
151
+
152
+ - `SKILL.md` — hub: principle, gotchas, refusal, six-phase process, boundaries.
153
+ - `reference/` — issue body templates, PII redaction checklist, handoff prompt template.
@@ -0,0 +1,72 @@
1
+ # Handoff prompt template
2
+
3
+ The closing session prints this prompt in chat. The user pastes it into a cloud session that opens PRs for the filed issues and drives them to convergence. The prompt is computed from the filed set — the safety lines, base branch, verification commands, and dependency order are read at runtime, not guessed.
4
+
5
+ ## Contents
6
+
7
+ - [Shape](#shape)
8
+ - [How to compute each block](#how-to-compute-each-block)
9
+ - [Worked example](#worked-example)
10
+
11
+ ## Shape
12
+
13
+ ```markdown
14
+ # Cloud handoff — <parent issue owner/repo#N>
15
+
16
+ ## Task
17
+ Open a PR per filed issue below and drive each to convergence.
18
+
19
+ ## Safety — never do these
20
+ - Never run, merge, deploy, or sync the <working repo> pipeline. It is live in production.
21
+ - Never run the host repo's automations.
22
+ - <any repo-specific never line read from the target CLAUDE.md>
23
+
24
+ ## Base branch and verification
25
+ - Base branch: <base>
26
+ - Per-package verification commands:
27
+ - <package A>: <command>
28
+ - <package B>: <command>
29
+
30
+ ## Issues, in dependency order
31
+ 1. owner/repo#<N> — <title>. <cloud-doable? yes / no + why>.
32
+ 2. owner/repo#<N> — <title>. Depends on #<N> landing first.
33
+ 3. owner/repo#<N> — <title>. Touches the same files as #<N>; stack on one branch, do not run in parallel.
34
+
35
+ ## Cannot be done from a cloud session
36
+ - owner/repo#<N> — needs <local environment / physical device / private network>. Scope the cloud work to <what a cloud session can reach>.
37
+ ```
38
+
39
+ ## How to compute each block
40
+
41
+ - **Safety lines** — read the working repo's CLAUDE.md and docs for the pipeline, deploy, and sync commands the cloud session must never run. Name each one.
42
+ - **Base branch** — read it live from the target repo (its default branch), not from memory.
43
+ - **Verification commands** — read the per-package test and check commands from the target repo's CLAUDE.md or docs. List one line per package a filed issue touches.
44
+ - **Dependency order** — for each pair of issues, decide: does one need the other's change to land first? Do they touch the same files? Same-file issues stack on one branch; independent issues run in parallel.
45
+ - **Cloud reachability** — for each issue, decide whether a cloud session can do the work. An issue needing a local environment, physical devices, or a private network is marked, and the cloud work is scoped to the reachable part.
46
+
47
+ ## Worked example
48
+
49
+ ```markdown
50
+ # Cloud handoff — jl-cmd/claude-dev-env#100
51
+
52
+ ## Task
53
+ Open a PR per filed issue below and drive each to convergence.
54
+
55
+ ## Safety — never do these
56
+ - Never run, merge, deploy, or sync the claude-dev-env publish pipeline. It is live in production (publishes to npm).
57
+ - Never run the host repo's automations.
58
+ - Never hand-edit .cursor/BUGBOT.md; it is generated from AGENTS.md.
59
+
60
+ ## Base branch and verification
61
+ - Base branch: main
62
+ - Per-package verification commands:
63
+ - claude-dev-env (JS): cd packages/claude-dev-env && npm test
64
+ - claude-dev-env (Python): python -m pytest packages/claude-dev-env
65
+
66
+ ## Issues, in dependency order
67
+ 1. jl-cmd/claude-dev-env#101 — inline-collection gate fires in exempt test files. Cloud-doable: yes.
68
+ 2. jl-cmd/claude-dev-env#102 — boolean-naming gate flags a fixture variable. Touches the same file as #101 (code_rules_enforcer.py); stack on one branch with #101, do not run in parallel.
69
+
70
+ ## Cannot be done from a cloud session
71
+ - None. Both issues are pure hook-logic changes with local pytest coverage a cloud session can run.
72
+ ```