claude-dev-env 8.23.0 → 8.23.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.
@@ -9,12 +9,12 @@ __all__ = [
9
9
  "COMPACTION_REMINDER",
10
10
  "COMPACTION_SOURCE",
11
11
  "NOT_LOADED_REMINDER",
12
- "POTETO_MODE_SKILL_NAME",
12
+ "ALL_POTETO_MODE_SKILL_NAMES",
13
13
  "PRE_TOOL_USE_EVENT_NAME",
14
14
  "PROMPT_SEPARATOR",
15
15
  "SESSION_START_EVENT_NAME",
16
16
  "SKILL_TOOL_NAME",
17
- "SLASH_COMMAND_MARKER",
17
+ "ALL_SLASH_COMMAND_MARKERS",
18
18
  "SUBAGENT_START_EVENT_NAME",
19
19
  "CLAUDE_SUBAGENT_PROMPT_PREFIX",
20
20
  "CODEX_SUBAGENT_PROMPT_PREFIX",
@@ -34,9 +34,12 @@ WORKFLOW_SUBAGENT_TYPE = "workflow-subagent"
34
34
 
35
35
  ALL_SELF_LOADING_SUBAGENT_TYPES = frozenset({"pstack:poteto-agent", "poteto-agent"})
36
36
 
37
- POTETO_MODE_SKILL_NAME = "pstack:poteto-mode"
37
+ ALL_POTETO_MODE_SKILL_NAMES = ("poteto-mode", "pstack:poteto-mode")
38
38
  SKILL_TOOL_NAME = "Skill"
39
- SLASH_COMMAND_MARKER = "<command-name>/pstack:poteto-mode</command-name>"
39
+ ALL_SLASH_COMMAND_MARKERS = (
40
+ "<command-name>/poteto-mode</command-name>",
41
+ "<command-name>/pstack:poteto-mode</command-name>",
42
+ )
40
43
  PROMPT_SEPARATOR = "\n\n"
41
44
 
42
45
  ASSISTANT_ENTRY_TYPE = "assistant"
@@ -45,11 +48,11 @@ TOOL_USE_BLOCK_TYPE = "tool_use"
45
48
  COMPACT_BOUNDARY_SUBTYPE = "compact_boundary"
46
49
 
47
50
  CLAUDE_SUBAGENT_PROMPT_PREFIX = (
48
- "Before any other work, invoke the pstack:poteto-mode skill with the Skill tool. "
51
+ "Before any other work, invoke the poteto-mode skill with the Skill tool. "
49
52
  "Every prompt you write for a subagent opens with the same instruction."
50
53
  )
51
54
 
52
- CODEX_SUBAGENT_PROMPT_PREFIX = "$pstack:poteto-mode"
55
+ CODEX_SUBAGENT_PROMPT_PREFIX = "$poteto-mode"
53
56
 
54
57
  ALL_SPAWN_PROMPT_FIELDS_AND_PREFIXES_BY_TOOL_NAME = {
55
58
  "Agent": ("prompt", CLAUDE_SUBAGENT_PROMPT_PREFIX),
@@ -58,13 +61,13 @@ ALL_SPAWN_PROMPT_FIELDS_AND_PREFIXES_BY_TOOL_NAME = {
58
61
  }
59
62
 
60
63
  NOT_LOADED_REMINDER = (
61
- "The pstack:poteto-mode skill is not loaded in this context. "
62
- "Your next tool call is the Skill tool with skill pstack:poteto-mode, before any other "
64
+ "The poteto-mode skill is not loaded in this context. "
65
+ "Your next tool call is the Skill tool with skill poteto-mode, before any other "
63
66
  "tool call."
64
67
  )
65
68
 
66
69
  COMPACTION_REMINDER = (
67
- "The context was just compacted and the pstack:poteto-mode skill rules were dropped. "
68
- "Your next tool call is the Skill tool with skill pstack:poteto-mode, before any other "
70
+ "The context was just compacted and the poteto-mode skill rules may have been dropped. "
71
+ "Your next tool call is the Skill tool with skill poteto-mode, before any other "
69
72
  "tool call. Then re-read the request that started this session and continue."
70
73
  )
@@ -3,10 +3,10 @@
3
3
 
4
4
  ::
5
5
 
6
- Agent or Codex spawn_agent, skill not named -> prompt opens with "invoke pstack:poteto-mode"
7
- Workflow script helper starts -> "invoke pstack:poteto-mode now"
8
- context compacted mid-run -> "invoke pstack:poteto-mode again"
9
- user turn, skill not loaded since compacting -> "invoke pstack:poteto-mode now"
6
+ Agent or Codex spawn_agent, skill not named -> prompt opens with "invoke poteto-mode"
7
+ Workflow script helper starts -> "invoke poteto-mode now"
8
+ context compacted mid-run -> "invoke poteto-mode again"
9
+ user turn, skill not loaded since compacting -> "invoke poteto-mode now"
10
10
  user turn, skill already loaded -> nothing
11
11
 
12
12
  A session that loaded the skill hears nothing more until a compaction drops it.
@@ -31,12 +31,12 @@ from hooks_constants.skill_loaded_reminder_constants import (
31
31
  COMPACTION_REMINDER,
32
32
  COMPACTION_SOURCE,
33
33
  NOT_LOADED_REMINDER,
34
- POTETO_MODE_SKILL_NAME,
34
+ ALL_POTETO_MODE_SKILL_NAMES,
35
35
  PRE_TOOL_USE_EVENT_NAME,
36
36
  PROMPT_SEPARATOR,
37
37
  SESSION_START_EVENT_NAME,
38
38
  SKILL_TOOL_NAME,
39
- SLASH_COMMAND_MARKER,
39
+ ALL_SLASH_COMMAND_MARKERS,
40
40
  SUBAGENT_START_EVENT_NAME,
41
41
  ALL_SPAWN_PROMPT_FIELDS_AND_PREFIXES_BY_TOOL_NAME,
42
42
  TOOL_USE_BLOCK_TYPE,
@@ -58,9 +58,9 @@ def subagent_input_with_poteto_mode(
58
58
  ::
59
59
 
60
60
  Agent {"prompt": "Reply leaf."} -> {"prompt": "Before any ...\\n\\nReply leaf."}
61
- spawn_agent {"message": "Fix it."} -> {"message": "$pstack:poteto-mode\\n\\nFix it."}
62
- Agent {"prompt": "Invoke pstack:poteto-mode, then ..."} -> None
63
- Agent {"subagent_type": "pstack:poteto-agent", ...} -> None, it loads the skill
61
+ spawn_agent {"message": "Fix it."} -> {"message": "$poteto-mode\\n\\nFix it."}
62
+ Agent {"prompt": "Invoke poteto-mode, then ..."} -> None
63
+ Agent {"subagent_type": "poteto-agent", ...} -> None, it loads the skill
64
64
 
65
65
  Claude Code spawns through Agent or Task and Codex through spawn_agent, so
66
66
  each tool name carries its own prompt field and invocation text.
@@ -71,7 +71,7 @@ def subagent_input_with_poteto_mode(
71
71
  """
72
72
  field_name, invocation_prefix = ALL_SPAWN_PROMPT_FIELDS_AND_PREFIXES_BY_TOOL_NAME[tool_name]
73
73
  prompt = all_tool_input_fields.get(field_name)
74
- if not isinstance(prompt, str) or POTETO_MODE_SKILL_NAME in prompt:
74
+ if not isinstance(prompt, str) or ALL_POTETO_MODE_SKILL_NAMES[0] in prompt:
75
75
  return None
76
76
  if all_tool_input_fields.get("subagent_type") in ALL_SELF_LOADING_SUBAGENT_TYPES:
77
77
  return None
@@ -84,7 +84,7 @@ def _invokes_poteto_mode(all_entry_fields: dict[str, object]) -> bool:
84
84
  return False
85
85
  content_blocks = message.get("content")
86
86
  if all_entry_fields.get("type") == USER_ENTRY_TYPE and isinstance(content_blocks, str):
87
- return SLASH_COMMAND_MARKER in content_blocks
87
+ return any(each_marker in content_blocks for each_marker in ALL_SLASH_COMMAND_MARKERS)
88
88
  if all_entry_fields.get("type") != ASSISTANT_ENTRY_TYPE or not isinstance(content_blocks, list):
89
89
  return False
90
90
  return any(
@@ -92,7 +92,7 @@ def _invokes_poteto_mode(all_entry_fields: dict[str, object]) -> bool:
92
92
  and each_block.get("type") == TOOL_USE_BLOCK_TYPE
93
93
  and each_block.get("name") == SKILL_TOOL_NAME
94
94
  and isinstance(each_block.get("input"), dict)
95
- and each_block["input"].get("skill") == POTETO_MODE_SKILL_NAME
95
+ and each_block["input"].get("skill") in ALL_POTETO_MODE_SKILL_NAMES
96
96
  for each_block in content_blocks
97
97
  )
98
98
 
@@ -100,7 +100,7 @@ def _invokes_poteto_mode(all_entry_fields: dict[str, object]) -> bool:
100
100
  def _marker_entry(transcript_line: str) -> dict[str, object] | None:
101
101
  if (
102
102
  COMPACT_BOUNDARY_SUBTYPE not in transcript_line
103
- and POTETO_MODE_SKILL_NAME not in transcript_line
103
+ and ALL_POTETO_MODE_SKILL_NAMES[0] not in transcript_line
104
104
  ):
105
105
  return None
106
106
  try:
@@ -121,9 +121,9 @@ def is_poteto_mode_loaded(all_transcript_lines: Iterable[str]) -> bool:
121
121
 
122
122
  ::
123
123
 
124
- Skill(pstack:poteto-mode) ... Read ... Edit -> True
125
- /pstack:poteto-mode typed as a command ... Read -> True
126
- Skill(pstack:poteto-mode) ... compact_boundary ... Read -> False
124
+ Skill(poteto-mode) ... Read ... Edit -> True
125
+ /poteto-mode typed as a command ... Read -> True
126
+ Skill(poteto-mode) ... compact_boundary ... Read -> False
127
127
  Read ... Edit -> False
128
128
 
129
129
  Only lines naming the skill or a compaction are parsed, so a long transcript
@@ -27,7 +27,7 @@ SKILL_CALL_LINE = json.dumps(
27
27
  {
28
28
  "type": "tool_use",
29
29
  "name": "Skill",
30
- "input": {"skill": "pstack:poteto-mode"},
30
+ "input": {"skill": "poteto-mode"},
31
31
  }
32
32
  ],
33
33
  },
@@ -38,7 +38,7 @@ TYPED_COMMAND_LINE = json.dumps(
38
38
  "type": "user",
39
39
  "message": {
40
40
  "role": "user",
41
- "content": "<command-name>/pstack:poteto-mode</command-name>",
41
+ "content": "<command-name>/poteto-mode</command-name>",
42
42
  },
43
43
  }
44
44
  )
@@ -58,7 +58,7 @@ TOOL_RESULT_QUOTING_THE_SKILL_LINE = json.dumps(
58
58
  "content": [
59
59
  {
60
60
  "type": "tool_result",
61
- "content": 'SKILL = "pstack:poteto-mode" and <command-name>/pstack:poteto-mode</command-name>',
61
+ "content": 'SKILL = "poteto-mode" and <command-name>/poteto-mode</command-name>',
62
62
  }
63
63
  ]
64
64
  },
@@ -107,7 +107,7 @@ class TestSubagentSpawn:
107
107
  rewritten_input = hook_output["updatedInput"]
108
108
  assert hook_output["permissionDecision"] == "allow"
109
109
  assert rewritten_input["prompt"].startswith(
110
- "Before any other work, invoke the pstack:poteto-mode skill with the Skill tool."
110
+ "Before any other work, invoke the poteto-mode skill with the Skill tool."
111
111
  )
112
112
  assert rewritten_input["prompt"].endswith("\n\nList every caller of main.")
113
113
  assert rewritten_input["subagent_type"] == "Explore"
@@ -115,10 +115,10 @@ class TestSubagentSpawn:
115
115
 
116
116
  def test_the_legacy_task_tool_name_is_rewritten_too(self) -> None:
117
117
  emitted = json.loads(_run_main(_agent_call({"prompt": "Go."}, tool_name="Task")))
118
- assert "pstack:poteto-mode" in emitted["hookSpecificOutput"]["updatedInput"]["prompt"]
118
+ assert "poteto-mode" in emitted["hookSpecificOutput"]["updatedInput"]["prompt"]
119
119
 
120
120
  def test_a_prompt_that_already_names_the_skill_is_left_alone(self) -> None:
121
- assert _run_main(_agent_call({"prompt": "Invoke pstack:poteto-mode first. Then go."})) == ""
121
+ assert _run_main(_agent_call({"prompt": "Invoke poteto-mode first. Then go."})) == ""
122
122
 
123
123
  def test_a_poteto_agent_is_left_alone_because_it_loads_the_skill_itself(self) -> None:
124
124
  assert (
@@ -161,14 +161,14 @@ class TestCodexSpawn:
161
161
  )
162
162
  )
163
163
  rewritten_input = emitted["hookSpecificOutput"]["updatedInput"]
164
- assert rewritten_input["message"] == "$pstack:poteto-mode\n\nFix the failing test."
164
+ assert rewritten_input["message"] == "$poteto-mode\n\nFix the failing test."
165
165
  assert rewritten_input["model"] == "gpt-6-luna"
166
166
  assert "prompt" not in rewritten_input
167
167
 
168
168
  def test_a_codex_spawn_message_that_already_mentions_the_skill_is_left_alone(self) -> None:
169
169
  assert (
170
170
  _run_main(
171
- _agent_call({"message": "$pstack:poteto-mode\n\nFix it."}, tool_name="spawn_agent")
171
+ _agent_call({"message": "$poteto-mode\n\nFix it."}, tool_name="spawn_agent")
172
172
  )
173
173
  == ""
174
174
  )
@@ -181,6 +181,14 @@ class TestIsPotetoModeLoaded:
181
181
  def test_a_typed_slash_command_loads_it(self) -> None:
182
182
  assert reminder.is_poteto_mode_loaded([TYPED_COMMAND_LINE, READ_CALL_LINE])
183
183
 
184
+ def test_a_prefixed_skill_call_loads_it(self) -> None:
185
+ prefixed_skill_call_line = SKILL_CALL_LINE.replace("poteto-mode", "pstack:poteto-mode")
186
+ assert reminder.is_poteto_mode_loaded([prefixed_skill_call_line])
187
+
188
+ def test_a_prefixed_typed_command_loads_it(self) -> None:
189
+ prefixed_command_line = TYPED_COMMAND_LINE.replace("/poteto-mode", "/pstack:poteto-mode")
190
+ assert reminder.is_poteto_mode_loaded([prefixed_command_line])
191
+
184
192
  def test_a_compaction_after_the_skill_call_drops_it(self) -> None:
185
193
  assert not reminder.is_poteto_mode_loaded(
186
194
  [SKILL_CALL_LINE, COMPACT_BOUNDARY_LINE, READ_CALL_LINE]
@@ -193,7 +201,7 @@ class TestIsPotetoModeLoaded:
193
201
 
194
202
  def test_a_plain_user_prompt_does_not_load_it(self) -> None:
195
203
  plain_prompt_line = json.dumps(
196
- {"type": "user", "message": {"content": "Read pstack:poteto-mode docs."}}
204
+ {"type": "user", "message": {"content": "Read poteto-mode docs."}}
197
205
  )
198
206
  assert not reminder.is_poteto_mode_loaded([plain_prompt_line])
199
207
 
@@ -210,7 +218,7 @@ class TestReminderFor:
210
218
  hook_output = emitted["hookSpecificOutput"]
211
219
  assert hook_output["hookEventName"] == "UserPromptSubmit"
212
220
  assert hook_output["additionalContext"] == NOT_LOADED_REMINDER
213
- assert "pstack:poteto-mode" in NOT_LOADED_REMINDER
221
+ assert "poteto-mode" in NOT_LOADED_REMINDER
214
222
 
215
223
  def test_a_user_turn_in_a_session_that_loaded_the_skill_prints_nothing(
216
224
  self, tmp_path: Path
@@ -229,7 +237,7 @@ class TestReminderFor:
229
237
  hook_output = emitted["hookSpecificOutput"]
230
238
  assert hook_output["hookEventName"] == "SessionStart"
231
239
  assert hook_output["additionalContext"] == COMPACTION_REMINDER
232
- assert "pstack:poteto-mode" in COMPACTION_REMINDER
240
+ assert "poteto-mode" in COMPACTION_REMINDER
233
241
 
234
242
  def test_a_resumed_session_keeps_its_context_and_prints_nothing(self) -> None:
235
243
  assert _run_main({"hook_event_name": "SessionStart", "source": "resume"}) == ""
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "8.23.0",
3
+ "version": "8.23.2",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {
@@ -63,10 +63,13 @@ Count the repetitions.
63
63
 
64
64
  ## Where the control lands
65
65
 
66
- A control lands in this package, so every session that loads this environment
67
- carries it, on any machine, under any model. A control written into one
68
- project's instructions reaches that project alone, and a control written into a
69
- chat reply reaches that conversation alone.
66
+ A control lands in the repository whose code, CI, or pipeline it guards. A
67
+ control for one repository's workflows, review bots, pipelines, or agents lands
68
+ in that repository. This package takes only repo-agnostic controls, the
69
+ environment and development pieces any repository could use, so every session
70
+ that loads this environment carries them. A control written into one project's
71
+ instructions reaches that project alone, and a control written into a chat reply
72
+ reaches that conversation alone.
70
73
 
71
74
  Memory holds the decision. The control holds the behavior. A correction that
72
75
  produced a memory file and nothing else has been recorded and never encoded, so
@@ -120,8 +123,9 @@ Effort rules out no layer; a missing capability does, and you name which one.
120
123
  The same correction arriving twice means the layer was too low. Move it up one
121
124
  layer and say so.
122
125
 
123
- Land the control in the shared environment package, so every session carries
124
- it. A memory file records the decision and encodes nothing.
126
+ Land the control in the repository whose code, CI, or pipeline it guards. The
127
+ shared environment package takes only repo-agnostic controls that any
128
+ repository could use. A memory file records the decision and encodes nothing.
125
129
  ```
126
130
 
127
131
  ## Sibling rules