claude-dev-env 8.46.4 → 8.46.6

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.
@@ -135,7 +135,7 @@ Consult the advisor the spawn ticket names. When the ticket names the orchestrat
135
135
  [3] Parameters and returns typed; no Any / no type-ignore directives?
136
136
  [4] Do not add code comments. Preserve existing comments. Docstrings remain allowed.
137
137
  [5] When a change touches code that an existing comment describes or is attached to, remove that comment in the same change and carry its meaning through clear names and structure. Leave comments tied to untouched code unchanged. Keep comment cleanup inside the requested task.
138
- [6] Production and tests follow one rule. Changed directive, TODO, FIXME, HACK, XXX, and type-ignore comments are removed rather than added or justified.
138
+ [6] Production and tests follow one rule. Changed directive, TODO, FIXME, HACK, XXX, and type-ignore comments are removed. Do not add or justify them.
139
139
  [7] Magic values and UPPER_SNAKE live in config/ where required?
140
140
  [8] Function short; one job; guards over else-chains?
141
141
  [9] Pre-check --check clean for the real destination path?
@@ -63,7 +63,7 @@ Tradeoff for category-restricted mode: parallel category invocation loses cross-
63
63
 
64
64
  Leave comments tied to untouched code unchanged. Findings on production and test code report only on new code added by the diff; comments on lines that remain otherwise unchanged stay outside the audit's scope. New inline comments added by this PR's diff are themselves a category J finding (code self-documents through naming).
65
65
  When a change touches code that an existing comment describes or is attached to, remove that comment in the same change and carry its meaning through clear names and structure. Leave comments tied to untouched code unchanged. Keep comment cleanup inside the requested task.
66
- Production and tests follow one rule. Changed directive, TODO, FIXME, HACK, XXX, and type-ignore comments are removed rather than added or justified.
66
+ Production and tests follow one rule. Changed directive, TODO, FIXME, HACK, XXX, and type-ignore comments are removed. Do not add or justify them.
67
67
 
68
68
  ## Read-Only Stance
69
69
 
@@ -63,7 +63,7 @@ You edit code and run tests for one closed task. You never commit, push, or call
63
63
 
64
64
  Do not add code comments. Preserve existing comments. Docstrings remain allowed.
65
65
  When a change touches code that an existing comment describes or is attached to, remove that comment in the same change and carry its meaning through clear names and structure. Leave comments tied to untouched code unchanged. Keep comment cleanup inside the requested task.
66
- Production and tests follow one rule. Changed directive, TODO, FIXME, HACK, XXX, and type-ignore comments are removed rather than added or justified.
66
+ Production and tests follow one rule. Changed directive, TODO, FIXME, HACK, XXX, and type-ignore comments are removed. Do not add or justify them.
67
67
 
68
68
  ## Scope
69
69
 
package/AGENTS.md CHANGED
@@ -68,7 +68,7 @@ Match requested status, paths, links, and format. Name files, checks, blockers,
68
68
 
69
69
  Do not add code comments. Preserve existing comments. Docstrings remain allowed.
70
70
  When a change touches code that an existing comment describes or is attached to, remove that comment in the same change and carry its meaning through clear names and structure. Leave comments tied to untouched code unchanged. Keep comment cleanup inside the requested task.
71
- Production and tests follow one rule. Changed directive, TODO, FIXME, HACK, XXX, and type-ignore comments are removed rather than added or justified.
71
+ Production and tests follow one rule. Changed directive, TODO, FIXME, HACK, XXX, and type-ignore comments are removed. Do not add or justify them.
72
72
 
73
73
  Banned word: real
74
74
 
@@ -7,7 +7,7 @@ Use this index for edits. [`.cursor/BUGBOT.md`](../../../.cursor/BUGBOT.md) poin
7
7
  Do not add code comments. Preserve existing comments. Docstrings remain allowed.
8
8
 
9
9
  When a change touches code that an existing comment describes or is attached to, remove that comment in the same change and carry its meaning through clear names and structure. Leave comments tied to untouched code unchanged. Keep comment cleanup inside the requested task.
10
- Production and tests follow one rule. Changed directive, TODO, FIXME, HACK, XXX, and type-ignore comments are removed rather than added or justified.
10
+ Production and tests follow one rule. Changed directive, TODO, FIXME, HACK, XXX, and type-ignore comments are removed. Do not add or justify them.
11
11
 
12
12
  A keep marker is the one comment that may be added and kept: a comment that opens with a prefix the repository lists under `comment_keep_markers` in `.claude/policy-lint.json`. Open [details](code-rules/comment-preservation.md) when editing comments.
13
13
 
@@ -201,10 +201,10 @@ def _is_keep_occurrence(comment_text: str, all_keep_markers: tuple[str, ...]) ->
201
201
  """Return True for a comment that opens with an accepted keep marker.
202
202
 
203
203
  A keep marker is meant to be freely added and to survive edits to the
204
- line it sits on, unlike ``noqa`` or ``TODO``, which AGENTS.md requires to
205
- be removed rather than added or justified. It is excluded here rather
206
- than through the shared exempt-marker set that ``check_comment_changes``
207
- deliberately still blocks on add.
204
+ line it sits on. AGENTS.md requires ``noqa`` and ``TODO`` comments to be
205
+ removed and forbids adding or justifying them. This check excludes keep
206
+ markers itself, outside the shared exempt-marker set that
207
+ ``check_comment_changes`` deliberately still blocks on add.
208
208
  """
209
209
  return comment_text.startswith("#") and comment_text[1:].lstrip().startswith(
210
210
  all_keep_markers
@@ -13,6 +13,8 @@ if hooks_directory not in sys.path:
13
13
  sys.path.insert(0, hooks_directory)
14
14
 
15
15
  from hooks_constants.pr_lifecycle_skill_gate_constants import (
16
+ AGENT_ID_FIELD,
17
+ AGENT_ID_PATTERN,
16
18
  ALL_API_ACTION_NAMES,
17
19
  ALL_COMMAND_PREFIX_WORDS,
18
20
  ALL_GITHUB_MCP_TOOL_SUFFIXES,
@@ -40,8 +42,11 @@ from hooks_constants.pr_lifecycle_skill_gate_constants import (
40
42
  PERMISSION_DECISION_REASON_KEY,
41
43
  PULL_REQUEST_SCRIPT_NAME,
42
44
  PYTHON_OPTIONS_WITH_VALUE,
45
+ SESSION_TRANSCRIPT_PATH_FIELD,
43
46
  SHELL_TOOL_NAMES,
44
47
  SKILL_NAME,
48
+ SUBAGENT_TRANSCRIPT_DIRECTORY_NAME,
49
+ SUBAGENT_TRANSCRIPT_FILE_TEMPLATE,
45
50
  )
46
51
  from hooks_constants.pre_tool_use_stdin import read_hook_input_dictionary_from_stdin
47
52
  from hooks_constants.setup_project_paths_constants import DECODE_ERRORS_POLICY, UTF8_ENCODING
@@ -174,6 +179,21 @@ def _transcript_load_status(path: str) -> bool | None:
174
179
  return None
175
180
 
176
181
 
182
+ def _subagent_transcript_path(all_payload_fields: dict[str, object]) -> str | None:
183
+ session_path = all_payload_fields.get(SESSION_TRANSCRIPT_PATH_FIELD)
184
+ agent_id = all_payload_fields.get(AGENT_ID_FIELD)
185
+ if not isinstance(session_path, str) or not session_path:
186
+ return None
187
+ if not isinstance(agent_id, str) or not re.fullmatch(AGENT_ID_PATTERN, agent_id):
188
+ return None
189
+ subagent_path = (
190
+ Path(session_path).with_suffix("")
191
+ / SUBAGENT_TRANSCRIPT_DIRECTORY_NAME
192
+ / SUBAGENT_TRANSCRIPT_FILE_TEMPLATE.format(agent_id=agent_id)
193
+ )
194
+ return str(subagent_path) if subagent_path.is_file() else None
195
+
196
+
177
197
  def decision_for(all_payload_fields: dict[str, object]) -> dict[str, object] | None:
178
198
  """Return a deny for a governed action with readable, unloaded transcripts.
179
199
 
@@ -183,7 +203,8 @@ def decision_for(all_payload_fields: dict[str, object]) -> dict[str, object] | N
183
203
  if not _is_governed_action(all_payload_fields):
184
204
  return None
185
205
  paths = [all_payload_fields.get(field) for field in ALL_TRANSCRIPT_PATH_FIELDS]
186
- readable_paths = [path for path in paths if isinstance(path, str) and path]
206
+ paths.append(_subagent_transcript_path(all_payload_fields))
207
+ readable_paths = list(dict.fromkeys(path for path in paths if isinstance(path, str) and path))
187
208
  if not readable_paths:
188
209
  return None
189
210
  statuses = [_transcript_load_status(path) for path in readable_paths]
@@ -82,6 +82,49 @@ def test_agent_transcript_can_supply_invocation(tmp_path: Path) -> None:
82
82
  assert gate.decision_for(payload) is None
83
83
 
84
84
 
85
+ def _subagent_transcript(tmp_path: Path, agent_id: str, skill_name: str | None) -> Path:
86
+ subagent_directory = tmp_path / "session" / "subagents"
87
+ subagent_directory.mkdir(parents=True)
88
+ subagent_path = subagent_directory / f"agent-{agent_id}.jsonl"
89
+ lines = [json.dumps({"type": "user", "message": {"content": "Task."}})]
90
+ if skill_name:
91
+ lines.append(json.dumps({"type": "assistant", "message": {"content": [{"type": "tool_use", "name": "Skill", "input": {"skill": skill_name}}]}}))
92
+ subagent_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
93
+ return subagent_path
94
+
95
+
96
+ def test_subagent_transcript_located_by_agent_id_can_supply_invocation(tmp_path: Path) -> None:
97
+ session_path = _transcript(tmp_path)
98
+ _subagent_transcript(tmp_path, "a8865a3f3c71595ce", "pr-lifecycle")
99
+ payload = _payload("git commit", session_path)
100
+ payload["agent_id"] = "a8865a3f3c71595ce"
101
+ assert _run_main(payload) == (0, "")
102
+
103
+
104
+ def test_subagent_without_invocation_is_denied(tmp_path: Path) -> None:
105
+ session_path = _transcript(tmp_path)
106
+ _subagent_transcript(tmp_path, "a8865a3f3c71595ce", None)
107
+ payload = _payload("git push", session_path)
108
+ payload["agent_id"] = "a8865a3f3c71595ce"
109
+ _assert_denied(payload)
110
+
111
+
112
+ def test_missing_subagent_transcript_keeps_session_deny(tmp_path: Path) -> None:
113
+ payload = _payload("git push", _transcript(tmp_path))
114
+ payload["agent_id"] = "a8865a3f3c71595ce"
115
+ _assert_denied(payload)
116
+
117
+
118
+ def test_agent_id_with_path_separator_is_not_followed(tmp_path: Path) -> None:
119
+ session_path = _transcript(tmp_path)
120
+ escaped_path = tmp_path / "session" / "agent-x.jsonl"
121
+ escaped_path.parent.mkdir(parents=True)
122
+ escaped_path.write_text(json.dumps({"type": "assistant", "message": {"content": [{"type": "tool_use", "name": "Skill", "input": {"skill": "pr-lifecycle"}}]}}) + "\n", encoding="utf-8")
123
+ payload = _payload("git push", session_path)
124
+ payload["agent_id"] = "../agent-x"
125
+ _assert_denied(payload)
126
+
127
+
85
128
  def test_unreadable_or_missing_transcript_allows_silently(tmp_path: Path) -> None:
86
129
  payload = _payload("gh pr create", tmp_path / "missing.jsonl")
87
130
  assert _run_main(payload) == (0, "")
@@ -18,6 +18,11 @@ ALL_GITHUB_MCP_TOOL_SUFFIXES = (
18
18
  "__update_pull_request",
19
19
  )
20
20
  ALL_TRANSCRIPT_PATH_FIELDS = ("transcript_path", "agent_transcript_path")
21
+ SESSION_TRANSCRIPT_PATH_FIELD = "transcript_path"
22
+ AGENT_ID_FIELD = "agent_id"
23
+ AGENT_ID_PATTERN = r"[A-Za-z0-9_-]+"
24
+ SUBAGENT_TRANSCRIPT_DIRECTORY_NAME = "subagents"
25
+ SUBAGENT_TRANSCRIPT_FILE_TEMPLATE = "agent-{agent_id}.jsonl"
21
26
  COMMAND_SEPARATORS = ";|&\n()`"
22
27
  COMMAND_WHITESPACE = " \t\r"
23
28
  OPTION_AND_VALUE_WORD_COUNT = 2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "8.46.4",
3
+ "version": "8.46.6",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {
@@ -116,7 +116,7 @@
116
116
  - Preserve existing comments.
117
117
  - Do not add code comments.
118
118
  - When a change touches code that an existing comment describes or is attached to, remove that comment in the same change and carry its meaning through clear names and structure. Leave comments tied to untouched code unchanged. Keep comment cleanup inside the requested task.
119
- - Production and tests follow one rule. Changed directive, TODO, FIXME, HACK, XXX, and type-ignore comments are removed rather than added or justified.
119
+ - Production and tests follow one rule. Changed directive, TODO, FIXME, HACK, XXX, and type-ignore comments are removed. Do not add or justify them.
120
120
  - Express new explanations through self-documenting names
121
121
  - Docstrings remain allowed.
122
122