claude-dev-env 8.46.2 → 8.46.4

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.
@@ -61,7 +61,6 @@ def test_should_keep_every_constant_free_of_a_sonnet_model_id() -> None:
61
61
  each_file.name
62
62
  for each_file in CONSTANTS_DIRECTORY.glob("*.py")
63
63
  if not each_file.name.startswith("test_")
64
- and each_file.name != "subagent_model_pin_hook_constants.py"
65
64
  and "claude-sonnet" in each_file.read_text(encoding="utf-8")
66
65
  ]
67
66
  assert all_offending_files == []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "8.46.2",
3
+ "version": "8.46.4",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,7 +11,7 @@ paths:
11
11
 
12
12
  # Flag non-breaking findings
13
13
 
14
- **When:** When writing a local gate or changing a check's severity.
14
+ **When:** Writing a local gate or changing a check's severity.
15
15
 
16
16
  Give every check ID a breaking or smell severity row; style and structure findings are smells. Fail the gate and stop for a bug, secret, broken test, syntax error, or instruction file that cannot load. Record a smell in `.claude/followups/smells.jsonl`, proceed, and fix it in a later pull request; ledger failure leaves the gate decision unchanged. Treat an unclassified check as breaking.
17
17
 
@@ -5,7 +5,7 @@ paths:
5
5
 
6
6
  # Rules prose names only hooks that run
7
7
 
8
- **When:** When describing a hook or retiring a gate.
8
+ **When:** Describing a hook or retiring a gate.
9
9
 
10
10
  Verify the module and its hook registration or dispatcher roster before claiming it runs; describe a retired hook in past tense or name the check that carries its work now. When retiring a gate, remove the forced agent calls, tokens, extra steps, thresholds, audit rubrics, and review prompts it required across every instruction lane. Add a retired hook path to the installer roster when deleting its module.
11
11
 
@@ -18,11 +18,15 @@ from codex_compat_materializer import (
18
18
  )
19
19
 
20
20
  FRONTMATTER_DELIMITER = "---"
21
+ FENCE_MARKER = "```"
21
22
  FULL_TEXT_LINK_PATTERN = re.compile(
22
23
  r"^\*\*Full text:\*\*.*?\]\((\.\./docs/rule-guides/[^)#\s]+\.md)\)", re.MULTILINE
23
24
  )
24
25
  TITLE_PATTERN = re.compile(r"^# \S", re.MULTILINE)
25
26
  WHEN_LINE_PATTERN = re.compile(r"^\*\*When(?: this applies)?:\*\* \S", re.MULTILINE)
27
+ DOUBLED_WHEN_LEAD_IN_PATTERN = re.compile(
28
+ r"^\*\*When(?: this applies)?:\*\* When\b", re.MULTILINE
29
+ )
26
30
 
27
31
  MAXIMUM_ENTRY_BYTES = 1_500
28
32
  MAXIMUM_ALWAYS_ON_BYTES = 12_000
@@ -90,16 +94,74 @@ def test_each_entry_opens_with_a_title_after_its_frontmatter() -> None:
90
94
  assert untitled == []
91
95
 
92
96
 
97
+ def _entry_body_before_full_text(entry_text: str) -> str:
98
+ _, entry_body = _split_frontmatter(entry_text)
99
+ all_kept_lines: list[str] = []
100
+ is_inside_fence = False
101
+ for each_line in entry_body.splitlines():
102
+ if FULL_TEXT_LINK_PATTERN.match(each_line):
103
+ break
104
+ if each_line.lstrip().startswith(FENCE_MARKER):
105
+ is_inside_fence = not is_inside_fence
106
+ continue
107
+ if not is_inside_fence:
108
+ all_kept_lines.append(each_line)
109
+ return "\n".join(all_kept_lines)
110
+
111
+
112
+ def _states_when_it_applies(entry_text: str) -> bool:
113
+ return bool(WHEN_LINE_PATTERN.search(_entry_body_before_full_text(entry_text)))
114
+
115
+
93
116
  def test_each_entry_states_when_it_applies() -> None:
94
117
  without_when_line = [
95
118
  each_path.name
96
119
  for each_path in _entry_paths()
97
120
  if each_path.name not in INDEX_SHAPE_EXEMPT_ENTRY_NAMES
98
- and not WHEN_LINE_PATTERN.search(_entry_text(each_path))
121
+ and not _states_when_it_applies(_entry_text(each_path))
99
122
  ]
100
123
  assert without_when_line == []
101
124
 
102
125
 
126
+ def test_when_line_inside_a_fenced_block_does_not_count() -> None:
127
+ entry_text = (
128
+ "# Example rule\n\n"
129
+ "```markdown\n**When:** Writing an example.\n```\n\n"
130
+ "**Full text:** [`docs/rule-guides/example.md`]"
131
+ "(../docs/rule-guides/example.md).\n"
132
+ )
133
+ assert not _states_when_it_applies(entry_text)
134
+
135
+
136
+ def test_when_line_below_the_full_text_link_does_not_count() -> None:
137
+ entry_text = (
138
+ "# Example rule\n\n"
139
+ "**Full text:** [`docs/rule-guides/example.md`]"
140
+ "(../docs/rule-guides/example.md).\n\n"
141
+ "**When:** Writing an example.\n"
142
+ )
143
+ assert not _states_when_it_applies(entry_text)
144
+
145
+
146
+ def test_when_line_in_the_entry_body_counts() -> None:
147
+ entry_text = (
148
+ "---\npaths:\n - \"**/*.py\"\n---\n\n# Example rule\n\n"
149
+ "**When:** Writing an example.\n\n"
150
+ "**Full text:** [`docs/rule-guides/example.md`]"
151
+ "(../docs/rule-guides/example.md).\n"
152
+ )
153
+ assert _states_when_it_applies(entry_text)
154
+
155
+
156
+ def test_no_entry_repeats_when_after_its_when_label() -> None:
157
+ doubled_lead_in = [
158
+ each_path.name
159
+ for each_path in _entry_paths()
160
+ if DOUBLED_WHEN_LEAD_IN_PATTERN.search(_entry_text(each_path))
161
+ ]
162
+ assert doubled_lead_in == []
163
+
164
+
103
165
  def _full_text_link_problems(entry_path: Path) -> list[str]:
104
166
  all_guide_names = _linked_guide_names(_entry_text(entry_path))
105
167
  if not all_guide_names: