claude-dev-env 1.42.0 → 1.43.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 (207) hide show
  1. package/_shared/pr-loop/scripts/_claude_permissions_common.py +1 -5
  2. package/_shared/pr-loop/scripts/code_rules_gate.py +293 -8
  3. package/_shared/pr-loop/scripts/fix_hookspath.py +96 -5
  4. package/_shared/pr-loop/scripts/grant_project_claude_permissions.py +3 -16
  5. package/_shared/pr-loop/scripts/post_audit_thread.py +4 -4
  6. package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/claude_permissions_constants.py +1 -1
  7. package/_shared/pr-loop/scripts/preflight.py +13 -31
  8. package/_shared/pr-loop/scripts/reviews_disabled.py +2 -16
  9. package/_shared/pr-loop/scripts/revoke_project_claude_permissions.py +3 -16
  10. package/_shared/pr-loop/scripts/tests/conftest.py +1 -51
  11. package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +4 -4
  12. package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +4 -2
  13. package/_shared/pr-loop/scripts/tests/test_claude_permissions_constants.py +4 -2
  14. package/_shared/pr-loop/scripts/tests/test_claude_settings_keys_constants.py +4 -2
  15. package/_shared/pr-loop/scripts/tests/test_code_rules_gate_constants.py +4 -2
  16. package/_shared/pr-loop/scripts/tests/test_fix_hookspath_constants.py +6 -2
  17. package/_shared/pr-loop/scripts/tests/test_grant_project_claude_permissions.py +2 -2
  18. package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +1 -2
  19. package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +4 -2
  20. package/_shared/pr-loop/scripts/tests/test_preflight.py +17 -52
  21. package/_shared/pr-loop/scripts/tests/test_preflight_constants.py +6 -2
  22. package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +2 -2
  23. package/agents/pr-description-writer.md +50 -140
  24. package/docs/PR_DESCRIPTION_GUIDE.md +101 -102
  25. package/hooks/_gh_pr_author_swap_utils.py +1 -1
  26. package/hooks/blocking/bot_mention_comment_blocker.py +4 -10
  27. package/hooks/blocking/code_rules_enforcer.py +217 -99
  28. package/hooks/blocking/code_rules_path_utils.py +8 -1
  29. package/hooks/blocking/destructive_command_blocker.py +1 -1
  30. package/hooks/blocking/es_exe_path_rewriter.py +7 -13
  31. package/hooks/blocking/gh_body_arg_blocker.py +6 -1
  32. package/hooks/blocking/gh_pr_author_enforcer.py +5 -5
  33. package/hooks/blocking/gh_pr_author_restore.py +5 -5
  34. package/hooks/blocking/hedging_language_blocker.py +4 -10
  35. package/hooks/blocking/md_path_exemptions.py +205 -0
  36. package/hooks/blocking/md_to_html_blocker.py +48 -20
  37. package/hooks/blocking/pr_converge_bugteam_enforcer.py +5 -11
  38. package/hooks/blocking/pr_description_enforcer.py +626 -41
  39. package/hooks/blocking/question_to_user_enforcer.py +4 -10
  40. package/hooks/blocking/state_description_blocker.py +6 -12
  41. package/hooks/blocking/tdd_enforcer.py +1 -1
  42. package/hooks/blocking/test_bot_mention_comment_blocker.py +1 -1
  43. package/hooks/blocking/test_code_rules_enforcer.py +3 -3
  44. package/hooks/blocking/test_code_rules_enforcer_any_exempt_files.py +1 -1
  45. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -2
  46. package/hooks/blocking/test_code_rules_enforcer_comment_string_awareness.py +184 -0
  47. package/hooks/blocking/test_code_rules_enforcer_type_checking_scope.py +82 -0
  48. package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +29 -29
  49. package/hooks/blocking/test_gh_body_arg_blocker.py +7 -8
  50. package/hooks/blocking/test_gh_pr_author_enforcer.py +1 -1
  51. package/hooks/blocking/test_gh_pr_author_restore.py +1 -1
  52. package/hooks/blocking/test_hedging_language_blocker.py +2 -2
  53. package/hooks/blocking/test_md_to_html_blocker.py +463 -8
  54. package/hooks/blocking/test_pr_converge_bugteam_enforcer.py +1 -1
  55. package/hooks/blocking/test_pr_description_enforcer.py +1210 -13
  56. package/hooks/blocking/test_question_to_user_enforcer.py +1 -1
  57. package/hooks/blocking/windows_rmtree_blocker.py +5 -11
  58. package/hooks/diagnostic/hook_log_extractor.py +1 -1
  59. package/hooks/diagnostic/hook_log_init.py +1 -1
  60. package/hooks/diagnostic/hook_log_stop_wrapper.py +1 -1
  61. package/hooks/diagnostic/test_hook_log_extractor.py +1 -1
  62. package/hooks/diagnostic/test_hook_log_init.py +2 -2
  63. package/hooks/diagnostic/test_hook_log_stop_wrapper.py +1 -1
  64. package/hooks/git-hooks/gate_utils.py +1 -1
  65. package/hooks/git-hooks/pre_commit.py +1 -1
  66. package/hooks/git-hooks/pre_push.py +1 -1
  67. package/hooks/git-hooks/test_config.py +5 -5
  68. package/hooks/git-hooks/test_pre_push.py +6 -6
  69. package/hooks/{config → hooks_constants}/code_rules_enforcer_constants.py +37 -0
  70. package/hooks/hooks_constants/code_rules_path_utils_constants.py +28 -0
  71. package/hooks/hooks_constants/md_to_html_blocker_constants.py +82 -0
  72. package/hooks/{config → hooks_constants}/pr_converge_bugteam_enforcer_state.py +1 -1
  73. package/hooks/hooks_constants/pr_description_enforcer_constants.py +154 -0
  74. package/hooks/{config → hooks_constants}/pre_tool_use_stdin.py +1 -1
  75. package/hooks/{config → hooks_constants}/project_paths_reader.py +2 -2
  76. package/hooks/{config → hooks_constants}/test_banned_identifiers_constants.py +1 -1
  77. package/hooks/{config → hooks_constants}/test_dynamic_stderr_handler.py +1 -1
  78. package/hooks/{config → hooks_constants}/test_hardcoded_user_path_constants.py +1 -1
  79. package/hooks/{config → hooks_constants}/test_hook_log_extractor_constants.py +2 -2
  80. package/hooks/hooks_constants/test_md_to_html_blocker_constants.py +110 -0
  81. package/hooks/{config → hooks_constants}/test_messages.py +2 -6
  82. package/hooks/{config → hooks_constants}/test_path_rewriter_constants.py +1 -1
  83. package/hooks/hooks_constants/test_pr_description_enforcer_constants.py +292 -0
  84. package/hooks/{config → hooks_constants}/test_pre_tool_use_stdin.py +2 -2
  85. package/hooks/{config → hooks_constants}/test_project_paths_reader.py +3 -3
  86. package/hooks/{config → hooks_constants}/test_session_env_cleanup_constants.py +1 -1
  87. package/hooks/{config → hooks_constants}/test_setup_project_paths_constants.py +2 -2
  88. package/hooks/{config → hooks_constants}/test_unused_module_import_constants.py +1 -1
  89. package/hooks/lifecycle/pr_converge_bugteam_skill_tracker.py +5 -11
  90. package/hooks/lifecycle/test_pr_converge_bugteam_skill_tracker.py +1 -1
  91. package/hooks/session/gh_pr_author_session_cleanup.py +5 -6
  92. package/hooks/session/session_env_cleanup.py +4 -10
  93. package/hooks/session/test_gh_pr_author_session_cleanup.py +1 -1
  94. package/hooks/session/test_untracked_repo_detector.py +2 -2
  95. package/hooks/session/untracked_repo_detector.py +6 -12
  96. package/hooks/test__gh_pr_author_swap_utils.py +1 -1
  97. package/hooks/validators/run_all_validators.py +16 -5
  98. package/hooks/validators/test_output_formatter.py +46 -0
  99. package/hooks/workflow/doc_gist_auto_publish.py +1 -1
  100. package/hooks/workflow/md_to_html_companion.py +8 -15
  101. package/hooks/workflow/test_md_to_html_companion.py +184 -23
  102. package/package.json +1 -1
  103. package/rules/ask-user-question-required.md +1 -1
  104. package/rules/vault-context.md +1 -1
  105. package/scripts/{config → dev_env_scripts_constants}/timing.py +1 -1
  106. package/scripts/setup_project_paths.py +49 -11
  107. package/scripts/sweep_empty_dirs.py +10 -1
  108. package/scripts/test_setup_project_paths.py +2 -2
  109. package/scripts/test_sweep_empty_dirs.py +2 -6
  110. package/skills/_shared/pr-loop/scripts/_path_resolver.py +1 -1
  111. package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +1 -1
  112. package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +1 -1
  113. package/skills/_shared/pr-loop/scripts/init_loop_state.py +1 -1
  114. package/skills/_shared/pr-loop/scripts/teardown_worktrees.py +1 -1
  115. package/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +2 -2
  116. package/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +2 -2
  117. package/skills/bugteam/PROMPTS.md +1 -1
  118. package/skills/bugteam/SKILL.md +1 -1
  119. package/skills/bugteam/reference/github-pr-reviews.md +1 -1
  120. package/skills/bugteam/scripts/{_claude_permissions_common.py → _bugteam_permissions_common.py} +1 -13
  121. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +1 -13
  122. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +1 -16
  123. package/skills/bugteam/scripts/bugteam_preflight.py +1 -13
  124. package/skills/bugteam/scripts/grant_project_claude_permissions.py +2 -8
  125. package/skills/bugteam/scripts/probe_code_rules_enforcer_check.py +1 -1
  126. package/skills/bugteam/scripts/reflow_skill_md.py +1 -1
  127. package/skills/bugteam/scripts/revoke_project_claude_permissions.py +2 -8
  128. package/skills/bugteam/scripts/{test__claude_permissions_common.py → test__bugteam_permissions_common.py} +4 -4
  129. package/skills/bugteam/scripts/test_agent_config_carveout.py +2 -2
  130. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +0 -26
  131. package/skills/bugteam/scripts/{test_claude_permissions_common.py → test_bugteam_permissions_common.py} +3 -66
  132. package/skills/bugteam/scripts/test_bugteam_preflight.py +2 -27
  133. package/skills/bugteam/scripts/windows_safe_rmtree.py +1 -1
  134. package/skills/doc-gist/SKILL.md +1 -1
  135. package/skills/doc-gist/scripts/gist_upload.py +1 -1
  136. package/skills/implement/SKILL.md +2 -2
  137. package/skills/implement/scripts/append_note.py +1 -1
  138. package/skills/pr-converge/pr_converge_skill_constants/__init__.py +0 -0
  139. package/skills/pr-converge/{config → pr_converge_skill_constants}/constants.py +1 -1
  140. package/skills/pr-converge/scripts/check_bugbot_ci.py +1 -1
  141. package/skills/pr-converge/scripts/check_convergence.py +11 -4
  142. package/skills/pr-converge/scripts/check_pending_reviews.py +1 -1
  143. package/skills/pr-converge/scripts/fetch_copilot_reviews.py +1 -1
  144. package/skills/pr-converge/scripts/post_fix_reply.py +1 -1
  145. package/skills/pr-converge/scripts/pr_converge_scripts_constants/__init__.py +0 -0
  146. package/skills/pr-converge/scripts/{config → pr_converge_scripts_constants}/pr_converge_constants.py +1 -1
  147. package/skills/pr-converge/scripts/reflow_skill_md.py +90 -16
  148. package/skills/pr-converge/scripts/test_check_convergence.py +18 -0
  149. package/skills/pr-converge/scripts/test_reflow_skill_md.py +0 -31
  150. package/skills/session-log/SKILL.md +98 -233
  151. package/hooks/config/pr_description_enforcer_constants.py +0 -19
  152. package/hooks/config/test_pr_description_enforcer_constants.py +0 -82
  153. package/skills/bugteam/scripts/test_grant_project_claude_permissions.py +0 -55
  154. package/skills/bugteam/scripts/test_revoke_project_claude_permissions.py +0 -55
  155. package/skills/pr-converge/scripts/conftest.py +0 -60
  156. package/skills/pr-converge/scripts/evict_cached_config_modules.py +0 -20
  157. package/skills/pr-converge/scripts/test_evict_cached_config_modules.py +0 -22
  158. /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/__init__.py +0 -0
  159. /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/claude_settings_keys_constants.py +0 -0
  160. /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/code_rules_gate_constants.py +0 -0
  161. /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/fix_hookspath_constants.py +0 -0
  162. /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/post_audit_thread_constants.py +0 -0
  163. /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/preflight_constants.py +0 -0
  164. /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/reviews_disabled_constants.py +0 -0
  165. /package/hooks/git-hooks/{config.py → git_hooks_constants/__init__.py} +0 -0
  166. /package/hooks/{config → hooks_constants}/__init__.py +0 -0
  167. /package/hooks/{config → hooks_constants}/any_type_config.py +0 -0
  168. /package/hooks/{config → hooks_constants}/banned_identifiers_constants.py +0 -0
  169. /package/hooks/{config → hooks_constants}/blocking_check_limits.py +0 -0
  170. /package/hooks/{config → hooks_constants}/bot_mention_comment_blocker_constants.py +0 -0
  171. /package/hooks/{config → hooks_constants}/convergence_branch_constants.py +0 -0
  172. /package/hooks/{config → hooks_constants}/doc_gist_auto_publish_constants.py +0 -0
  173. /package/hooks/{config → hooks_constants}/dynamic_stderr_handler.py +0 -0
  174. /package/hooks/{config → hooks_constants}/gh_pr_author_swap_constants.py +0 -0
  175. /package/hooks/{config → hooks_constants}/hardcoded_user_path_constants.py +0 -0
  176. /package/hooks/{config → hooks_constants}/hook_log_extractor_constants.py +0 -0
  177. /package/hooks/{config → hooks_constants}/html_companion_constants.py +0 -0
  178. /package/hooks/{config → hooks_constants}/inline_tuple_string_magic_constants.py +0 -0
  179. /package/hooks/{config → hooks_constants}/messages.py +0 -0
  180. /package/hooks/{config → hooks_constants}/path_rewriter_constants.py +0 -0
  181. /package/hooks/{config → hooks_constants}/pr_converge_bugteam_enforcer_constants.py +0 -0
  182. /package/hooks/{config → hooks_constants}/session_env_cleanup_constants.py +0 -0
  183. /package/hooks/{config → hooks_constants}/setup_project_paths_constants.py +0 -0
  184. /package/hooks/{config → hooks_constants}/state_description_blocker_constants.py +0 -0
  185. /package/hooks/{config → hooks_constants}/stuttering_check_config.py +0 -0
  186. /package/hooks/{config → hooks_constants}/stuttering_import_binding_constants.py +0 -0
  187. /package/hooks/{config → hooks_constants}/sys_path_insert_constants.py +0 -0
  188. /package/hooks/{config → hooks_constants}/unused_module_import_constants.py +0 -0
  189. /package/hooks/{config → hooks_constants}/windows_rmtree_blocker_constants.py +0 -0
  190. /package/{skills/_shared/pr-loop/scripts/config → hooks/lifecycle}/__init__.py +0 -0
  191. /package/{skills/bugteam/scripts/config → hooks/session}/__init__.py +0 -0
  192. /package/scripts/{config → dev_env_scripts_constants}/__init__.py +0 -0
  193. /package/skills/{doc-gist/scripts/config → _shared/pr-loop/scripts/skills_pr_loop_constants}/__init__.py +0 -0
  194. /package/skills/_shared/pr-loop/scripts/{config → skills_pr_loop_constants}/path_resolver_constants.py +0 -0
  195. /package/skills/{implement/scripts/config → bugteam/scripts/bugteam_scripts_constants}/__init__.py +0 -0
  196. /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/bugteam_code_rules_gate_constants.py +0 -0
  197. /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/bugteam_fix_hookspath_constants.py +0 -0
  198. /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/bugteam_preflight_constants.py +0 -0
  199. /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/claude_permissions_common_constants.py +0 -0
  200. /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/probe_code_rules_enforcer_check_constants.py +0 -0
  201. /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/reflow_skill_md_constants.py +0 -0
  202. /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/windows_safe_rmtree_constants.py +0 -0
  203. /package/skills/{pr-converge/config → doc-gist/scripts/doc_gist_scripts_constants}/__init__.py +0 -0
  204. /package/skills/doc-gist/scripts/{config → doc_gist_scripts_constants}/gist_upload_constants.py +0 -0
  205. /package/skills/{pr-converge/scripts/config → implement/scripts/implement_scripts_constants}/__init__.py +0 -0
  206. /package/skills/implement/scripts/{config → implement_scripts_constants}/notes_constants.py +0 -0
  207. /package/skills/pr-converge/scripts/{config → pr_converge_scripts_constants}/reflow_skill_md_constants.py +0 -0
@@ -33,7 +33,7 @@ _pr_converge_dir = Path(__file__).absolute().parent.parent
33
33
  if str(_pr_converge_dir) not in sys.path:
34
34
  sys.path.insert(0, str(_pr_converge_dir))
35
35
 
36
- from config.constants import (
36
+ from pr_converge_skill_constants.constants import (
37
37
  ALL_BUGBOT_CHECK_RUN_ACTIVE_STATUSES,
38
38
  ALL_BUGBOT_CHECK_RUN_COMPLETE_CONCLUSIONS,
39
39
  BUGBOT_CHECK_RUN_COMPLETED_STATUS,
@@ -23,7 +23,7 @@ _pr_converge_dir = Path(__file__).absolute().parent.parent
23
23
  if str(_pr_converge_dir) not in sys.path:
24
24
  sys.path.insert(0, str(_pr_converge_dir))
25
25
 
26
- from config.constants import (
26
+ from pr_converge_skill_constants.constants import (
27
27
  ALL_COPILOT_DIRTY_REVIEW_STATES,
28
28
  ALL_BUGBOT_CHECK_RUN_COMPLETE_CONCLUSIONS,
29
29
  BUGBOT_CHECK_RUN_NAME_SUBSTRING,
@@ -495,9 +495,16 @@ def check_all(*, owner: str, repo: str, number: int, bugbot_down: bool) -> int:
495
495
 
496
496
  Returns:
497
497
  ``0`` when every gate reports PASS, ``1`` when at least one gate
498
- reports FAIL. The function never raises for gate-level failures;
499
- gh-api transport failures surface as gate FAILs in the printed
500
- output and contribute to the ``1`` exit code.
498
+ reports FAIL. Per-gate ``gh api`` transport failures surface as
499
+ gate FAIL lines in the printed output and contribute to the ``1``
500
+ exit code.
501
+
502
+ Raises:
503
+ SystemExit: Propagated by the initial ``_get_pr_head_sha`` call
504
+ with ``EXIT_CODE_GH_ERROR`` when the PR-head-SHA fetch fails
505
+ before any gate runs. The function does not catch this
506
+ exception; the caller is responsible for converting it into
507
+ an exit code.
501
508
  """
502
509
  head_sha = _get_pr_head_sha(owner=owner, repo=repo, number=number)
503
510
  print(f"HEAD: {head_sha[:7]}\n")
@@ -21,7 +21,7 @@ _pr_converge_dir = Path(__file__).absolute().parent.parent
21
21
  if str(_pr_converge_dir) not in sys.path:
22
22
  sys.path.insert(0, str(_pr_converge_dir))
23
23
 
24
- from config.constants import (
24
+ from pr_converge_skill_constants.constants import (
25
25
  EXIT_CODE_GH_ERROR,
26
26
  GH_REVIEWS_PATH_TEMPLATE,
27
27
  REVIEWS_PER_PAGE,
@@ -19,7 +19,7 @@ _pr_converge_dir = Path(__file__).absolute().parent.parent
19
19
  if str(_pr_converge_dir) not in sys.path:
20
20
  sys.path.insert(0, str(_pr_converge_dir))
21
21
 
22
- from config.constants import (
22
+ from pr_converge_skill_constants.constants import (
23
23
  COPILOT_LOGIN_FILTER_SUBSTRING,
24
24
  GH_REVIEWS_PATH_TEMPLATE,
25
25
  REVIEWS_PER_PAGE,
@@ -24,7 +24,7 @@ _pr_converge_dir = Path(__file__).absolute().parent.parent
24
24
  if str(_pr_converge_dir) not in sys.path:
25
25
  sys.path.insert(0, str(_pr_converge_dir))
26
26
 
27
- from config.constants import (
27
+ from pr_converge_skill_constants.constants import (
28
28
  EXIT_CODE_GH_ERROR,
29
29
  GH_INLINE_COMMENT_REPLY_PATH_TEMPLATE,
30
30
  GH_ISSUE_COMMENT_CREATE_PATH_TEMPLATE,
@@ -11,7 +11,7 @@ settings) live here.
11
11
  import re
12
12
  from pathlib import Path
13
13
 
14
- from config.constants import ( # noqa: F401
14
+ from pr_converge_skill_constants.constants import ( # noqa: F401
15
15
  ALL_BUGBOT_CHECK_RUN_ACTIVE_STATUSES,
16
16
  ALL_CLAUDE_DIRTY_REVIEW_STATES,
17
17
  ALL_COPILOT_DIRTY_REVIEW_STATES,
@@ -9,22 +9,9 @@ Run: python3 packages/claude-dev-env/skills/pr-converge/scripts/reflow_skill_md.
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
- import sys
13
12
  import textwrap
14
- from pathlib import Path
15
13
 
16
- script_directory = str(Path(__file__).resolve().parent)
17
-
18
- while script_directory in sys.path:
19
- sys.path.remove(script_directory)
20
- if script_directory not in sys.path:
21
- sys.path.insert(0, script_directory)
22
-
23
- from evict_cached_config_modules import evict_cached_config_modules
24
-
25
- evict_cached_config_modules()
26
-
27
- from config.reflow_skill_md_constants import (
14
+ from pr_converge_scripts_constants.reflow_skill_md_constants import (
28
15
  BASH_CONTINUATION_MARKER_WIDTH,
29
16
  BULLET_LIST_ITEM_PATTERN as BULLET_RE,
30
17
  MARKDOWN_REFERENCE_DEFINITION_PATTERN as REF_DEF_RE,
@@ -36,6 +23,14 @@ from config.reflow_skill_md_constants import (
36
23
 
37
24
 
38
25
  def wrap_paragraph_plain(text: str) -> list[str]:
26
+ """Wrap a plain paragraph to MAX_WIDTH after collapsing whitespace.
27
+
28
+ Args:
29
+ text: Paragraph text with internal whitespace runs to collapse.
30
+
31
+ Returns:
32
+ Wrapped lines; empty list when the input collapses to nothing.
33
+ """
39
34
  collapsed = " ".join(text.split())
40
35
  if not collapsed:
41
36
  return []
@@ -48,6 +43,17 @@ def wrap_paragraph_plain(text: str) -> list[str]:
48
43
 
49
44
 
50
45
  def wrap_list_item(lead_ws: str, marker: str, body: str) -> list[str]:
46
+ """Wrap a list item, preserving the leading marker and indentation.
47
+
48
+ Args:
49
+ lead_ws: Leading whitespace before the list marker.
50
+ marker: List marker such as a hyphen or numeric prefix.
51
+ body: Item body text to wrap.
52
+
53
+ Returns:
54
+ Wrapped lines with the marker on the first line and matching
55
+ indentation on subsequent lines.
56
+ """
51
57
  collapsed = " ".join(body.split())
52
58
  if not collapsed:
53
59
  return [lead_ws + marker.rstrip()]
@@ -67,6 +73,16 @@ def reflow_yaml_description_block(
67
73
  all_lines: list[str],
68
74
  body_start: int,
69
75
  ) -> tuple[list[str], int]:
76
+ """Reflow the YAML description block until the closing fence.
77
+
78
+ Args:
79
+ all_lines: Full SKILL.md lines.
80
+ body_start: Index of the first description body line.
81
+
82
+ Returns:
83
+ Tuple of wrapped description lines and the index just past the
84
+ closing fence.
85
+ """
70
86
  body_parts: list[str] = []
71
87
  index = body_start
72
88
  while index < len(all_lines):
@@ -95,6 +111,16 @@ def is_table_line(line: str) -> bool:
95
111
 
96
112
 
97
113
  def is_new_logical_line(stripped: str) -> bool:
114
+ """Decide whether ``stripped`` starts a new logical line.
115
+
116
+ Args:
117
+ stripped: Candidate line with leading whitespace already removed.
118
+
119
+ Returns:
120
+ True when the line begins a new markdown construct (fence, heading,
121
+ table row, list item, reference definition, or example tag) and
122
+ therefore must not be merged into the prior buffer.
123
+ """
98
124
  if not stripped:
99
125
  return False
100
126
  if stripped.startswith("```"):
@@ -115,7 +141,16 @@ def is_new_logical_line(stripped: str) -> bool:
115
141
 
116
142
 
117
143
  def merge_without_space(buffer: str, continuation: str) -> bool:
118
- """Join without space only for split markdown link URL paths."""
144
+ """Join without space only for split markdown link URL paths.
145
+
146
+ Args:
147
+ buffer: Accumulated line preceding the candidate continuation.
148
+ continuation: Next line to evaluate for joining.
149
+
150
+ Returns:
151
+ True when continuation is the tail of a split markdown link target
152
+ and buffer ends inside an unfinished link target.
153
+ """
119
154
  base = buffer.rstrip()
120
155
  stripped = continuation.lstrip()
121
156
  if not base or not stripped:
@@ -126,6 +161,15 @@ def merge_without_space(buffer: str, continuation: str) -> bool:
126
161
 
127
162
 
128
163
  def merge_soft_breaks(all_lines: list[str]) -> list[str]:
164
+ """Merge soft line breaks across non-fence markdown paragraphs.
165
+
166
+ Args:
167
+ all_lines: Raw SKILL.md lines.
168
+
169
+ Returns:
170
+ Lines with each paragraph collapsed to a single buffer line; fences
171
+ and blank lines are preserved verbatim.
172
+ """
129
173
  reflowed_lines: list[str] = []
130
174
  index = 0
131
175
  is_inside_fence = False
@@ -166,6 +210,15 @@ def merge_soft_breaks(all_lines: list[str]) -> list[str]:
166
210
 
167
211
 
168
212
  def reflow_merged_line(line: str) -> list[str]:
213
+ """Reflow a single merged buffer line into MAX_WIDTH-bounded lines.
214
+
215
+ Args:
216
+ line: Buffer line produced by merge_soft_breaks.
217
+
218
+ Returns:
219
+ Wrapped lines; structural constructs (fences, tables, separators)
220
+ are returned unchanged.
221
+ """
169
222
  stripped = line.strip()
170
223
  if stripped == "":
171
224
  return [""]
@@ -221,6 +274,14 @@ def reflow_merged_line(line: str) -> list[str]:
221
274
 
222
275
 
223
276
  def reflow_markdown_body(all_lines: list[str]) -> list[str]:
277
+ """Merge soft breaks then reflow every line of the SKILL.md body.
278
+
279
+ Args:
280
+ all_lines: Raw SKILL.md body lines following the YAML front matter.
281
+
282
+ Returns:
283
+ Reflowed body lines bounded by MAX_WIDTH.
284
+ """
224
285
  merged = merge_soft_breaks(all_lines)
225
286
  reflowed_lines: list[str] = []
226
287
  for each_line in merged:
@@ -232,7 +293,15 @@ def reflow_markdown_body(all_lines: list[str]) -> list[str]:
232
293
 
233
294
 
234
295
  def wrap_long_bash_fence_lines(all_lines: list[str]) -> list[str]:
235
- """Hard-wrap only ```bash fence bodies that still exceed MAX_WIDTH."""
296
+ """Hard-wrap bash fence bodies that still exceed MAX_WIDTH.
297
+
298
+ Args:
299
+ all_lines: SKILL.md body lines after paragraph reflow.
300
+
301
+ Returns:
302
+ Lines with overlong bash-fence bodies split on whitespace with a
303
+ trailing backslash continuation marker.
304
+ """
236
305
  wrapped_lines: list[str] = []
237
306
  is_inside_bash_fence = False
238
307
  for each_line in all_lines:
@@ -272,6 +341,11 @@ def wrap_long_bash_fence_lines(all_lines: list[str]) -> list[str]:
272
341
 
273
342
 
274
343
  def main() -> None:
344
+ """Read SKILL.md, reflow it to MAX_WIDTH, and write the result back.
345
+
346
+ Raises:
347
+ SystemExit: When the file does not start with YAML front matter.
348
+ """
275
349
  raw = SKILL_PATH.read_text(encoding="utf-8")
276
350
  lines = raw.splitlines()
277
351
  if not lines or lines[0].strip() != "---":
@@ -304,3 +304,21 @@ def should_bypass_bugbot_gates_when_bugbot_down_is_true(
304
304
  assert "_check_bugbot_not_dirty" not in all_invocation_names
305
305
  assert "bypassed (bugbot_down)" in captured_stdout
306
306
  assert exit_code == 0
307
+
308
+
309
+ def should_propagate_systemexit_from_get_pr_head_sha(
310
+ monkeypatch: pytest.MonkeyPatch,
311
+ ) -> None:
312
+ def stub_get_pr_head_sha_raising_systemexit(
313
+ *, owner: str, repo: str, number: int
314
+ ) -> str:
315
+ raise SystemExit(check_convergence.EXIT_CODE_GH_ERROR)
316
+
317
+ monkeypatch.setattr(
318
+ check_convergence, "_get_pr_head_sha", stub_get_pr_head_sha_raising_systemexit
319
+ )
320
+
321
+ with pytest.raises(SystemExit) as exc_info:
322
+ check_convergence.check_all(owner="o", repo="r", number=1, bugbot_down=False)
323
+
324
+ assert exc_info.value.code == check_convergence.EXIT_CODE_GH_ERROR
@@ -108,27 +108,6 @@ def test_reflow_merged_line_preserves_long_markdown_reference_definition() -> No
108
108
  assert reflow_module.reflow_merged_line(line) == [stripped_line]
109
109
 
110
110
 
111
- def test_reflow_bootstrap_moves_script_directory_ahead_of_shadow_config(
112
- tmp_path: Path,
113
- ) -> None:
114
- """sys.path bootstrap must move the script directory ahead of shadow config packages."""
115
- shadow_config_directory = tmp_path / "shadow" / "config"
116
- shadow_config_directory.mkdir(parents=True)
117
- (shadow_config_directory / "__init__.py").write_text("", encoding="utf-8")
118
- (shadow_config_directory / "pr_converge_constants.py").write_text(
119
- "BROKEN = True\n", encoding="utf-8"
120
- )
121
- original_sys_path = list(sys.path)
122
- try:
123
- sys.path.insert(0, str(tmp_path / "shadow"))
124
- loaded_module = _load_module()
125
- assert loaded_module.MAX_WIDTH == 80
126
- assert sys.path[0] == str(_SCRIPTS_DIRECTORY)
127
- assert sys.path.count(str(_SCRIPTS_DIRECTORY)) == 1
128
- finally:
129
- sys.path[:] = original_sys_path
130
-
131
-
132
111
  def test_wrap_long_bash_fence_lines_uses_continuation_marker_for_long_lines() -> None:
133
112
  """Wrapped continuation lines use the bash continuation marker."""
134
113
  long_line = "echo " + "word " * 20
@@ -150,13 +129,3 @@ def test_reflow_uses_config_constant_for_continuation_marker_width() -> None:
150
129
  "reflow_skill_md.py must import BASH_CONTINUATION_MARKER_WIDTH from config"
151
130
  )
152
131
 
153
- def test_reflow_bootstrap_matches_code_rules_sys_path_pattern() -> None:
154
- """Bootstrap must guard insert with a membership check."""
155
- module_path = _SCRIPTS_DIRECTORY / "reflow_skill_md.py"
156
- source = module_path.read_text(encoding="utf-8")
157
- assert "while script_directory in sys.path:" in source, (
158
- "Bootstrap must dedup script_directory entries before insert"
159
- )
160
- assert "sys.path.insert(0, script_directory)" in source, (
161
- "Bootstrap must insert script_directory at index 0"
162
- )