claude-dev-env 1.36.0 → 1.36.2

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 (42) hide show
  1. package/_shared/pr-loop/audit-contract.md +159 -0
  2. package/_shared/pr-loop/code-rules-gate.md +64 -0
  3. package/_shared/pr-loop/fix-protocol.md +37 -0
  4. package/_shared/pr-loop/gh-payloads.md +85 -0
  5. package/_shared/pr-loop/scripts/README.md +20 -0
  6. package/_shared/pr-loop/scripts/_claude_permissions_common.py +234 -0
  7. package/_shared/pr-loop/scripts/code_rules_gate.py +975 -0
  8. package/_shared/pr-loop/scripts/config/__init__.py +0 -0
  9. package/_shared/pr-loop/scripts/config/claude_permissions_constants.py +36 -0
  10. package/_shared/pr-loop/scripts/config/claude_settings_keys_constants.py +11 -0
  11. package/_shared/pr-loop/scripts/config/code_rules_gate_constants.py +56 -0
  12. package/_shared/pr-loop/scripts/config/fix_hookspath_constants.py +25 -0
  13. package/_shared/pr-loop/scripts/config/gh_util_constants.py +31 -0
  14. package/_shared/pr-loop/scripts/config/preflight_constants.py +47 -0
  15. package/_shared/pr-loop/scripts/fix_hookspath.py +260 -0
  16. package/_shared/pr-loop/scripts/gh_util.py +193 -0
  17. package/_shared/pr-loop/scripts/grant_project_claude_permissions.py +130 -0
  18. package/_shared/pr-loop/scripts/preflight.py +227 -0
  19. package/_shared/pr-loop/scripts/revoke_project_claude_permissions.py +156 -0
  20. package/_shared/pr-loop/scripts/tests/conftest.py +51 -0
  21. package/_shared/pr-loop/scripts/tests/test__claude_permissions_common.py +135 -0
  22. package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +169 -0
  23. package/_shared/pr-loop/scripts/tests/test_claude_permissions_constants.py +58 -0
  24. package/_shared/pr-loop/scripts/tests/test_claude_settings_keys_constants.py +50 -0
  25. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +917 -0
  26. package/_shared/pr-loop/scripts/tests/test_code_rules_gate_constants.py +102 -0
  27. package/_shared/pr-loop/scripts/tests/test_fix_hookspath.py +374 -0
  28. package/_shared/pr-loop/scripts/tests/test_fix_hookspath_constants.py +47 -0
  29. package/_shared/pr-loop/scripts/tests/test_gh_util.py +257 -0
  30. package/_shared/pr-loop/scripts/tests/test_gh_util_constants.py +61 -0
  31. package/_shared/pr-loop/scripts/tests/test_grant_project_claude_permissions.py +49 -0
  32. package/_shared/pr-loop/scripts/tests/test_preflight.py +333 -0
  33. package/_shared/pr-loop/scripts/tests/test_preflight_constants.py +82 -0
  34. package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +49 -0
  35. package/_shared/pr-loop/state-schema.md +81 -0
  36. package/package.json +2 -1
  37. package/skills/bugteam/SKILL.md +332 -108
  38. package/skills/bugteam/scripts/reflow_skill_md.py +298 -0
  39. package/skills/bugteam/test_team_lifecycle.py +9 -0
  40. package/skills/pr-converge/SKILL.md +1005 -395
  41. package/skills/pr-converge/scripts/reflow_skill_md.py +288 -0
  42. package/skills/pr-converge/test_team_lifecycle.py +9 -0
@@ -0,0 +1,298 @@
1
+ """Reflow packages/claude-dev-env/skills/bugteam/SKILL.md to 80 columns.
2
+
3
+ Merge soft line breaks outside fenced blocks (space join; URL path fragments
4
+ joined without a space only inside unfinished markdown link targets), then
5
+ wrap with textwrap. Preserves fenced blocks verbatim.
6
+
7
+ Same algorithm as ``packages/claude-dev-env/skills/pr-converge/scripts/reflow_skill_md.py``
8
+ from https://github.com/jl-cmd/claude-code-config/pull/349 (branch
9
+ ``cursor/pr-converge-skill-line-wrap-ecd1``); ``SKILL_PATH`` points at bugteam
10
+ ``SKILL.md``. Link reference definitions (``[id]: url``) are treated as logical
11
+ line starts so they are not merged with prior paragraphs.
12
+
13
+ Run: python3 packages/claude-dev-env/skills/bugteam/scripts/reflow_skill_md.py
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import re
19
+ import textwrap
20
+ from pathlib import Path
21
+
22
+ MAX_WIDTH = 80
23
+ SKILL_PATH = Path(__file__).resolve().parent.parent / "SKILL.md"
24
+
25
+ ORDERED_RE = re.compile(r"^(\s*)(\d+\.\s)(.*)$")
26
+ BULLET_RE = re.compile(r"^(\s*)([-*]\s)(.*)$")
27
+ UNFINISHED_MD_LINK_TARGET = re.compile(r"\]\([^)]*$")
28
+
29
+
30
+ def wrap_paragraph_plain(text: str) -> list[str]:
31
+ collapsed = " ".join(text.split())
32
+ if not collapsed:
33
+ return []
34
+ return textwrap.fill(
35
+ collapsed,
36
+ width=MAX_WIDTH,
37
+ break_long_words=False,
38
+ break_on_hyphens=False,
39
+ ).splitlines()
40
+
41
+
42
+ def wrap_list_item(lead_ws: str, marker: str, body: str) -> list[str]:
43
+ collapsed = " ".join(body.split())
44
+ if not collapsed:
45
+ return [lead_ws + marker.rstrip()]
46
+ prefix = lead_ws + marker
47
+ subsequent = lead_ws + (" " * len(marker))
48
+ return textwrap.fill(
49
+ collapsed,
50
+ width=MAX_WIDTH,
51
+ initial_indent=prefix,
52
+ subsequent_indent=subsequent,
53
+ break_long_words=False,
54
+ break_on_hyphens=False,
55
+ ).splitlines()
56
+
57
+
58
+ def reflow_yaml_description_block(lines: list[str], body_start: int) -> tuple[list[str], int]:
59
+ body_parts: list[str] = []
60
+ index = body_start
61
+ while index < len(lines):
62
+ line = lines[index]
63
+ if line.strip() == "---":
64
+ index += 1
65
+ break
66
+ stripped = line.lstrip()
67
+ if stripped:
68
+ body_parts.append(stripped)
69
+ index += 1
70
+ merged = " ".join(body_parts)
71
+ wrapped = textwrap.fill(
72
+ merged,
73
+ width=MAX_WIDTH,
74
+ initial_indent=" ",
75
+ subsequent_indent=" ",
76
+ break_long_words=False,
77
+ break_on_hyphens=False,
78
+ )
79
+ return wrapped.splitlines(), index
80
+
81
+
82
+ def is_table_line(line: str) -> bool:
83
+ return line.lstrip().startswith("|")
84
+
85
+
86
+ def is_link_reference_definition(stripped: str) -> bool:
87
+ return bool(re.match(r"^\[[^\]]+\]:\s+\S", stripped))
88
+
89
+
90
+ def is_new_logical_line(stripped: str) -> bool:
91
+ if not stripped:
92
+ return False
93
+ if stripped.startswith("```"):
94
+ return True
95
+ if stripped.startswith("#"):
96
+ return True
97
+ if stripped == "---":
98
+ return True
99
+ if is_table_line(stripped):
100
+ return True
101
+ if stripped.startswith("<example>") or stripped.startswith("</example>"):
102
+ return True
103
+ if is_link_reference_definition(stripped):
104
+ return True
105
+ if ORDERED_RE.match(stripped) or BULLET_RE.match(stripped):
106
+ return True
107
+ return False
108
+
109
+
110
+ def merge_without_space(buffer: str, continuation: str) -> bool:
111
+ """Join without space only for split markdown link URL paths."""
112
+ base = buffer.rstrip()
113
+ stripped = continuation.lstrip()
114
+ if not base or not stripped:
115
+ return False
116
+ if stripped.startswith("/") and UNFINISHED_MD_LINK_TARGET.search(base):
117
+ return True
118
+ return False
119
+
120
+
121
+ def merge_soft_breaks(lines: list[str]) -> list[str]:
122
+ output: list[str] = []
123
+ index = 0
124
+ in_fence = False
125
+ while index < len(lines):
126
+ raw = lines[index]
127
+ line = raw.rstrip("\n")
128
+ if line.lstrip().startswith("```"):
129
+ in_fence = not in_fence
130
+ output.append(line)
131
+ index += 1
132
+ continue
133
+ if in_fence:
134
+ output.append(line)
135
+ index += 1
136
+ continue
137
+ if line.strip() == "":
138
+ output.append(line)
139
+ index += 1
140
+ continue
141
+ buffer_line = line
142
+ index += 1
143
+ while index < len(lines):
144
+ next_raw = lines[index].rstrip("\n")
145
+ if next_raw.strip() == "":
146
+ break
147
+ if next_raw.lstrip().startswith("```"):
148
+ break
149
+ stripped_next = next_raw.lstrip()
150
+ if is_new_logical_line(stripped_next):
151
+ break
152
+ if merge_without_space(buffer_line, stripped_next):
153
+ buffer_line = buffer_line.rstrip() + stripped_next
154
+ else:
155
+ buffer_line = f"{buffer_line.rstrip()} {stripped_next}"
156
+ index += 1
157
+ output.append(buffer_line)
158
+ return output
159
+
160
+
161
+ def reflow_merged_line(line: str) -> list[str]:
162
+ stripped = line.strip()
163
+ if stripped == "":
164
+ return [""]
165
+ if stripped.startswith("```"):
166
+ return [line]
167
+ if stripped.startswith("#"):
168
+ if len(stripped) <= MAX_WIDTH:
169
+ return [stripped]
170
+ title = stripped.lstrip("#").strip()
171
+ level = len(stripped) - len(stripped.lstrip("#"))
172
+ prefix = "#" * level + " "
173
+ return textwrap.fill(
174
+ title,
175
+ width=MAX_WIDTH,
176
+ initial_indent=prefix,
177
+ subsequent_indent=prefix,
178
+ break_long_words=False,
179
+ break_on_hyphens=False,
180
+ ).splitlines()
181
+ if stripped == "---":
182
+ return ["---"]
183
+ if is_link_reference_definition(stripped):
184
+ return [stripped]
185
+ if is_table_line(stripped):
186
+ return [stripped]
187
+ if stripped.startswith("</example>"):
188
+ return [stripped]
189
+ if stripped.startswith("<example>"):
190
+ inner = stripped[len("<example>") :].strip()
191
+ if not inner:
192
+ return ["<example>"]
193
+ tag = "<example> "
194
+ subsequent = " " * len(tag)
195
+ return textwrap.fill(
196
+ " ".join(inner.split()),
197
+ width=MAX_WIDTH,
198
+ initial_indent=tag,
199
+ subsequent_indent=subsequent,
200
+ break_long_words=False,
201
+ break_on_hyphens=False,
202
+ ).splitlines()
203
+
204
+ ordered = ORDERED_RE.match(line)
205
+ if ordered:
206
+ return wrap_list_item(ordered.group(1), ordered.group(2), ordered.group(3))
207
+
208
+ bullet = BULLET_RE.match(line)
209
+ if bullet:
210
+ return wrap_list_item(bullet.group(1), bullet.group(2), bullet.group(3))
211
+
212
+ return wrap_paragraph_plain(stripped)
213
+
214
+
215
+ def reflow_markdown_body(lines: list[str]) -> list[str]:
216
+ merged = merge_soft_breaks(lines)
217
+ output: list[str] = []
218
+ for each_line in merged:
219
+ if each_line.strip() == "":
220
+ output.append("")
221
+ continue
222
+ output.extend(reflow_merged_line(each_line))
223
+ return output
224
+
225
+
226
+ def wrap_long_bash_fence_lines(lines: list[str]) -> list[str]:
227
+ """Hard-wrap only ```bash fence bodies that still exceed MAX_WIDTH."""
228
+ output: list[str] = []
229
+ in_bash_fence = False
230
+ for line in lines:
231
+ stripped = line.lstrip()
232
+ if stripped.startswith("```"):
233
+ if not in_bash_fence:
234
+ lang = stripped[3:].strip().lower()
235
+ in_bash_fence = lang == "bash"
236
+ else:
237
+ in_bash_fence = False
238
+ output.append(line)
239
+ continue
240
+ if in_bash_fence and len(line) > MAX_WIDTH:
241
+ indent_len = len(line) - len(line.lstrip())
242
+ indent = line[:indent_len]
243
+ content = line.lstrip()
244
+ wrapped_segments: list[str] = []
245
+ rest = content
246
+ while len(rest) > MAX_WIDTH - len(indent):
247
+ room = MAX_WIDTH - len(indent) - 2
248
+ window = rest[:room]
249
+ break_at = window.rfind(" ")
250
+ if break_at <= 0:
251
+ break_at = room
252
+ piece = rest[:break_at].rstrip()
253
+ rest = rest[break_at:].lstrip()
254
+ wrapped_segments.append(indent + piece + " \\")
255
+ if rest:
256
+ wrapped_segments.append(indent + (" " if wrapped_segments else "") + rest)
257
+ output.extend(wrapped_segments)
258
+ else:
259
+ output.append(line)
260
+ return output
261
+
262
+
263
+ def main() -> None:
264
+ raw = SKILL_PATH.read_text(encoding="utf-8")
265
+ lines = raw.splitlines()
266
+ if not lines or lines[0].strip() != "---":
267
+ raise SystemExit("expected YAML front matter starting with ---")
268
+
269
+ out: list[str] = ["---"]
270
+ index = 1
271
+ while index < len(lines):
272
+ line = lines[index]
273
+ if line.startswith("description: >-"):
274
+ out.append(line)
275
+ index += 1
276
+ desc_lines, index = reflow_yaml_description_block(lines, index)
277
+ out.extend(desc_lines)
278
+ out.append("---")
279
+ break
280
+ out.append(line)
281
+ index += 1
282
+
283
+ body = reflow_markdown_body(lines[index:])
284
+ body = wrap_long_bash_fence_lines(body)
285
+
286
+ text = "\n".join(out + body) + "\n"
287
+ SKILL_PATH.write_text(text, encoding="utf-8", newline="\n")
288
+
289
+ all_lines = text.splitlines()
290
+ long_rows = [(i, len(ln)) for i, ln in enumerate(all_lines, 1) if len(ln) > MAX_WIDTH]
291
+ print("SKILL.md reflowed; lines:", len(all_lines))
292
+ print("lines longer than %d: %d" % (MAX_WIDTH, len(long_rows)))
293
+ if long_rows[:20]:
294
+ print("first long:", long_rows[:20])
295
+
296
+
297
+ if __name__ == "__main__":
298
+ main()
@@ -92,3 +92,12 @@ def test_constraints_warn_against_owned_mode_inside_orchestrator():
92
92
  constraints_text = _constraints_text()
93
93
  assert "orchestrator" in constraints_text.lower()
94
94
  assert "attach" in constraints_text
95
+
96
+
97
+ def test_skill_md_physical_lines_fit_eighty_column_limit():
98
+ skill_text = _skill_text()
99
+ for each_line_number, each_physical_line in enumerate(skill_text.splitlines(), 1):
100
+ assert len(each_physical_line) <= 80, (
101
+ "SKILL.md line %s exceeds 80 columns (%s chars)"
102
+ % (each_line_number, len(each_physical_line))
103
+ )