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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
|
|
37
|
+
ALL_POTETO_MODE_SKILL_NAMES = ("poteto-mode", "pstack:poteto-mode")
|
|
38
38
|
SKILL_TOOL_NAME = "Skill"
|
|
39
|
-
|
|
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
|
|
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 = "$
|
|
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
|
|
62
|
-
"Your next tool call is the Skill tool with skill
|
|
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
|
|
68
|
-
"Your next tool call is the Skill tool with skill
|
|
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
|
|
7
|
-
Workflow script helper starts -> "invoke
|
|
8
|
-
context compacted mid-run -> "invoke
|
|
9
|
-
user turn, skill not loaded since compacting -> "invoke
|
|
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
|
-
|
|
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
|
-
|
|
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": "$
|
|
62
|
-
Agent {"prompt": "Invoke
|
|
63
|
-
Agent {"subagent_type": "
|
|
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
|
|
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
|
|
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")
|
|
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
|
|
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(
|
|
125
|
-
/
|
|
126
|
-
Skill(
|
|
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": "
|
|
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>/
|
|
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 = "
|
|
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
|
|
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 "
|
|
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
|
|
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"] == "$
|
|
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": "$
|
|
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
|
|
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 "
|
|
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 "
|
|
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
package/rules/correction-lens.md
CHANGED
|
@@ -63,10 +63,13 @@ Count the repetitions.
|
|
|
63
63
|
|
|
64
64
|
## Where the control lands
|
|
65
65
|
|
|
66
|
-
A control lands in
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
124
|
-
|
|
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
|