claude-dev-env 8.88.7 → 8.90.0
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.
- package/.agents/skills/pr-visual/README.md +29 -0
- package/.agents/skills/pr-visual/SKILL.md +62 -0
- package/.agents/skills/pr-visual/assets/icons.symbols +27 -0
- package/.agents/skills/pr-visual/assets/viewer.html +147 -0
- package/.agents/skills/pr-visual/fixtures/bar.scene.json +193 -0
- package/.agents/skills/pr-visual/fixtures/flow.scene.json +201 -0
- package/.agents/skills/pr-visual/fixtures/head/metrics.json +16 -0
- package/.agents/skills/pr-visual/fixtures/integrated.scene.json +239 -0
- package/.agents/skills/pr-visual/fixtures/line.scene.json +193 -0
- package/.agents/skills/pr-visual/fixtures/scatter.scene.json +193 -0
- package/.agents/skills/pr-visual/fixtures/snapshot.json +21 -0
- package/.agents/skills/pr-visual/references/evidence.md +9 -0
- package/.agents/skills/pr-visual/references/formats.md +30 -0
- package/.agents/skills/pr-visual/references/scene-contract.md +48 -0
- package/.agents/skills/pr-visual/references/visual-contract.md +23 -0
- package/.agents/skills/pr-visual/scripts/collect.py +111 -0
- package/.agents/skills/pr-visual/scripts/metrics.py +99 -0
- package/.agents/skills/pr-visual/scripts/render.py +133 -0
- package/.agents/skills/pr-visual/scripts/test_collect.py +58 -0
- package/.agents/skills/pr-visual/scripts/test_metrics.py +47 -0
- package/.agents/skills/pr-visual/scripts/test_render.py +77 -0
- package/.agents/skills/pr-visual/scripts/verify.py +218 -0
- package/.agents/skills/pr-visual/scripts/visual_config/config/constants.py +104 -0
- package/bin/codex-skill-load-block.mjs +0 -25
- package/bin/codex-skill-load-block.test.mjs +5 -17
- package/bin/codex-trimmed-sol.test.mjs +0 -1
- package/bin/cursor-native-hooks.test.mjs +3 -3
- package/bin/ever-shipped-skills.mjs +1 -0
- package/bin/install-constants.mjs +3 -0
- package/bin/install-deepstack-plugin.mjs +42 -25
- package/bin/install-deepstack-plugin.test.mjs +37 -11
- package/bin/install-pstack-plugin.mjs +58 -11
- package/bin/install-pstack-plugin.test.mjs +54 -21
- package/bin/install.agents-home.test.mjs +0 -1
- package/bin/install.codex-rules.test.mjs +0 -2
- package/bin/install.cursor-rules.test.mjs +96 -21
- package/bin/install.mjs +48 -44
- package/bin/install.pr-visual.test.mjs +40 -0
- package/bin/install.prompt-context.test.mjs +3 -4
- package/bin/install.prune.test.mjs +0 -1
- package/bin/install.question-presentation.test.mjs +0 -4
- package/bin/install.settings-defaults.test.mjs +0 -1
- package/bin/install.shared-settings.test.mjs +0 -1
- package/bin/install.test.mjs +14 -44
- package/bin/install.transaction.test.mjs +0 -8
- package/bin/install.uninstall-transaction.test.mjs +0 -2
- package/bin/install.update-keeps-hooks.test.mjs +0 -1
- package/docs/BDD_SCENARIO_QUALITY.md +1 -1
- package/docs/BDD_TEST_LAYOUT.md +1 -1
- package/docs/CODE_RULES.md +1 -1
- package/docs/rule-guides/deepstack-wording.md +3 -5
- package/docs/rule-guides/session-title.md +5 -4
- package/hooks/advisory/owned_pr_review_loop.py +365 -0
- package/hooks/advisory/test_owned_pr_review_loop.py +369 -0
- package/hooks/blocking/session_title_turn_gate.py +287 -0
- package/hooks/blocking/test_session_title_format_gate.py +5 -0
- package/hooks/blocking/test_session_title_turn_gate.py +264 -0
- package/hooks/features/bash-dispatchers.md +3 -0
- package/hooks/features/conduct-gates.md +3 -0
- package/hooks/features/session-context.md +0 -4
- package/hooks/hooks.json +15 -5
- package/hooks/hooks_constants/mod_handoff_constants.py +0 -2
- package/hooks/hooks_constants/owned_pr_review_loop_constants.py +120 -0
- package/hooks/hooks_constants/session_title_constants.py +41 -3
- package/hooks/lifecycle/nested_project_hooks.py +2 -2
- package/hooks/lifecycle/test_nested_project_hooks.py +2 -2
- package/hooks/session/compaction_user_messages.py +2 -2
- package/package.json +2 -2
- package/rules/deepstack-wording.md +3 -3
- package/scripts/ci/install-playtest.mjs +58 -314
- package/scripts/ci/install-playtest.test.mjs +24 -6
- package/scripts/policy_lint/adapter_configuration.py +1 -0
- package/scripts/policy_lint/config/constants.py +1 -0
- package/scripts/test_deepstack_review_criterion.py +2 -3
- package/scripts/test_deepstack_wording_rule.py +6 -4
- package/scripts/tests/test_adapter_configuration.py +8 -0
- package/.agents/skills/deepen/SKILL.md +0 -26
- package/commands/deepen.md +0 -14
- package/docs/decisions/deepstack-gate.md +0 -32
- package/hooks/hooks_constants/deepen_first_prompt_constants.py +0 -187
- package/hooks/session/deepen_default_rewrite.py +0 -128
- package/hooks/session/deepen_first_prompt.py +0 -383
- package/hooks/session/deepen_role_gate.py +0 -171
- package/hooks/session/deepen_turn_marker.py +0 -75
- package/hooks/session/fixtures/deepstack_replay.json +0 -15
- package/hooks/session/grade_deepstack_replay.py +0 -78
- package/hooks/session/test_deepen_default_rewrite.py +0 -90
- package/hooks/session/test_deepen_first_prompt.py +0 -452
- package/hooks/session/test_deepen_role_gate.py +0 -83
- package/hooks/session/test_deepen_turn_marker.py +0 -59
- package/hooks/session/test_grade_deepstack_replay.py +0 -47
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
"""Named values for first-task deepening."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from hooks_constants.mod_handoff_constants import DEEPEN_FIRST_PROMPT_PLUGIN_NAME
|
|
6
|
-
from hooks_constants.skill_loaded_reminder_constants import (
|
|
7
|
-
CLAUDE_SUBAGENT_PROMPT_PREFIX,
|
|
8
|
-
CODEX_SUBAGENT_PROMPT_PREFIX,
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
__all__ = [
|
|
12
|
-
"ADDITIONAL_CONTEXT_KEY",
|
|
13
|
-
"ALL_HELPER_REPORT_MARKERS",
|
|
14
|
-
"ALL_LOOP_PROMPT_PREFIXES",
|
|
15
|
-
"ALL_NONINTERACTIVE_ENV_TRUE_VALUES",
|
|
16
|
-
"ALL_SPAWN_REWRITE_PREFIXES",
|
|
17
|
-
"ALL_TRIVIAL_PROMPTS",
|
|
18
|
-
"CLAUDE_HOST_ENV_VAR",
|
|
19
|
-
"CLAUDE_HOST_TRUE_VALUE",
|
|
20
|
-
"DEEPEN_AGENT_MARKER",
|
|
21
|
-
"DEEPEN_COMMAND_ENV_VAR",
|
|
22
|
-
"DEEPEN_COMMAND_TIMEOUT_SECONDS",
|
|
23
|
-
"DEEPEN_FIRST_PROMPT_PLUGIN_NAME",
|
|
24
|
-
"DEEPEN_MARKER",
|
|
25
|
-
"DEEPEN_OUTPUT_ENV_VAR",
|
|
26
|
-
"DEEPEN_OUTPUT_JSON",
|
|
27
|
-
"DEEPEN_OUTPUT_REWRITE",
|
|
28
|
-
"DEEPEN_REWRITE_ENV_VAR",
|
|
29
|
-
"ALL_EXAMPLE_NOUN_STOPWORDS",
|
|
30
|
-
"ALL_FOUR_QUESTION_STEMS",
|
|
31
|
-
"EXAMPLE_NOUN_PATTERN",
|
|
32
|
-
"HARD_CONSTRAINTS_LABEL",
|
|
33
|
-
"REFUSAL_DROPPED_HARD_CONSTRAINT",
|
|
34
|
-
"REFUSAL_EXAMPLE_NOUN_IN_SCOPE",
|
|
35
|
-
"REFUSAL_MISSING_ROLE_LINES",
|
|
36
|
-
"REFUSAL_RESTATES_FOUR_QUESTIONS",
|
|
37
|
-
"ROLE_BLOCKER_LABEL",
|
|
38
|
-
"ROLE_BLOCKER_NONE",
|
|
39
|
-
"ROLE_EXAMPLES_LABEL",
|
|
40
|
-
"BULLET_PREFIX",
|
|
41
|
-
"QUESTION_LINE_SEPARATOR",
|
|
42
|
-
"SCORE_ACCEPT",
|
|
43
|
-
"SCORE_REFUSE",
|
|
44
|
-
"ROLE_SCOPE_LABEL",
|
|
45
|
-
"DEEPEN_SLASH_COMMAND",
|
|
46
|
-
"DEEPEN_TIMEOUT_SECONDS",
|
|
47
|
-
"DEEPEN_TURN_MARKER_SUFFIX",
|
|
48
|
-
"DEEPEN_SESSION_MARKER_PREFIX",
|
|
49
|
-
"ALL_FIRST_PROMPT_EVENT_NAMES",
|
|
50
|
-
"CURSOR_BEFORE_SUBMIT_PROMPT_EVENT_NAME",
|
|
51
|
-
"HOOK_EVENT_NAME_KEY",
|
|
52
|
-
"HOOK_SPECIFIC_OUTPUT_KEY",
|
|
53
|
-
"JSONL_ROLE_KEY",
|
|
54
|
-
"JSONL_TYPE_KEY",
|
|
55
|
-
"NONINTERACTIVE_CODEX_ENV_VAR",
|
|
56
|
-
"NONINTERACTIVE_PRINT_ENV_VAR",
|
|
57
|
-
"PROMPT_FIELD_NAME",
|
|
58
|
-
"SESSION_ID_FIELD_NAME",
|
|
59
|
-
"SKIP_DEEPEN_ENV_VAR",
|
|
60
|
-
"SKIP_DEEPEN_TRUE_VALUE",
|
|
61
|
-
"TRANSCRIPT_PATH_FIELD_NAME",
|
|
62
|
-
"USER_PROMPT_SUBMIT_EVENT_NAME",
|
|
63
|
-
"USER_ROLE_NAME",
|
|
64
|
-
"USER_TYPE_NAME",
|
|
65
|
-
"UTF8_ENCODING",
|
|
66
|
-
]
|
|
67
|
-
|
|
68
|
-
DEEPEN_MARKER: str = "CDE_DEEPEN:v1"
|
|
69
|
-
DEEPEN_AGENT_MARKER: str = "CDE_DEEPEN_AGENT"
|
|
70
|
-
DEEPEN_SLASH_COMMAND: str = "/deepen"
|
|
71
|
-
DEEPEN_TURN_MARKER_SUFFIX: str = ".cde-deepen-turn1"
|
|
72
|
-
DEEPEN_SESSION_MARKER_PREFIX: str = "cde-deepen-"
|
|
73
|
-
DEEPEN_TIMEOUT_SECONDS: int = 10
|
|
74
|
-
DEEPEN_COMMAND_TIMEOUT_MARGIN_SECONDS: int = 2
|
|
75
|
-
DEEPEN_COMMAND_TIMEOUT_SECONDS: int = DEEPEN_TIMEOUT_SECONDS - DEEPEN_COMMAND_TIMEOUT_MARGIN_SECONDS
|
|
76
|
-
USER_PROMPT_SUBMIT_EVENT_NAME: str = "UserPromptSubmit"
|
|
77
|
-
CURSOR_BEFORE_SUBMIT_PROMPT_EVENT_NAME: str = "beforeSubmitPrompt"
|
|
78
|
-
ALL_FIRST_PROMPT_EVENT_NAMES: frozenset[str] = frozenset(
|
|
79
|
-
{USER_PROMPT_SUBMIT_EVENT_NAME, CURSOR_BEFORE_SUBMIT_PROMPT_EVENT_NAME}
|
|
80
|
-
)
|
|
81
|
-
PROMPT_FIELD_NAME: str = "prompt"
|
|
82
|
-
TRANSCRIPT_PATH_FIELD_NAME: str = "transcript_path"
|
|
83
|
-
SESSION_ID_FIELD_NAME: str = "session_id"
|
|
84
|
-
HOOK_SPECIFIC_OUTPUT_KEY: str = "hookSpecificOutput"
|
|
85
|
-
HOOK_EVENT_NAME_KEY: str = "hookEventName"
|
|
86
|
-
ADDITIONAL_CONTEXT_KEY: str = "additionalContext"
|
|
87
|
-
SKIP_DEEPEN_ENV_VAR: str = "CDE_SKIP_DEEPEN"
|
|
88
|
-
SKIP_DEEPEN_TRUE_VALUE: str = "1"
|
|
89
|
-
DEEPEN_REWRITE_ENV_VAR: str = "CDE_DEEPEN_REWRITE"
|
|
90
|
-
DEEPEN_COMMAND_ENV_VAR: str = "CDE_DEEPEN_COMMAND"
|
|
91
|
-
DEEPEN_OUTPUT_ENV_VAR: str = "CDE_DEEPEN_OUTPUT"
|
|
92
|
-
DEEPEN_OUTPUT_REWRITE: str = "rewrite"
|
|
93
|
-
DEEPEN_OUTPUT_JSON: str = "json"
|
|
94
|
-
NONINTERACTIVE_PRINT_ENV_VAR: str = "CLAUDE_CODE_NONINTERACTIVE"
|
|
95
|
-
NONINTERACTIVE_CODEX_ENV_VAR: str = "CODEX_EXEC"
|
|
96
|
-
CLAUDE_HOST_ENV_VAR: str = "CLAUDECODE"
|
|
97
|
-
CLAUDE_HOST_TRUE_VALUE: str = "1"
|
|
98
|
-
ALL_NONINTERACTIVE_ENV_TRUE_VALUES: frozenset[str] = frozenset({"1", "true", "yes"})
|
|
99
|
-
UTF8_ENCODING: str = "utf-8"
|
|
100
|
-
JSONL_TYPE_KEY: str = "type"
|
|
101
|
-
JSONL_ROLE_KEY: str = "role"
|
|
102
|
-
USER_TYPE_NAME: str = "user"
|
|
103
|
-
USER_ROLE_NAME: str = "user"
|
|
104
|
-
|
|
105
|
-
ALL_TRIVIAL_PROMPTS: frozenset[str] = frozenset(
|
|
106
|
-
{
|
|
107
|
-
"yes",
|
|
108
|
-
"y",
|
|
109
|
-
"no",
|
|
110
|
-
"n",
|
|
111
|
-
"ok",
|
|
112
|
-
"okay",
|
|
113
|
-
"thanks",
|
|
114
|
-
"thank you",
|
|
115
|
-
"thx",
|
|
116
|
-
"status",
|
|
117
|
-
"hello",
|
|
118
|
-
"hi",
|
|
119
|
-
"hey",
|
|
120
|
-
"k",
|
|
121
|
-
"sure",
|
|
122
|
-
"continue",
|
|
123
|
-
"go",
|
|
124
|
-
"?",
|
|
125
|
-
}
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
ALL_LOOP_PROMPT_PREFIXES: tuple[str, ...] = (
|
|
129
|
-
"/loop",
|
|
130
|
-
"<scheduled-task",
|
|
131
|
-
"scheduled task firing",
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
ALL_HELPER_REPORT_MARKERS: tuple[str, ...] = (
|
|
135
|
-
"<task-notification",
|
|
136
|
-
"background subagent reporting",
|
|
137
|
-
"a message another session sends",
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
ALL_SPAWN_REWRITE_PREFIXES: tuple[str, ...] = (
|
|
141
|
-
CLAUDE_SUBAGENT_PROMPT_PREFIX,
|
|
142
|
-
CODEX_SUBAGENT_PROMPT_PREFIX,
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
ROLE_SCOPE_LABEL: str = "SCOPE (role):"
|
|
146
|
-
ROLE_EXAMPLES_LABEL: str = "EXAMPLES (illustration only):"
|
|
147
|
-
ROLE_BLOCKER_LABEL: str = "ZOOM-IN BLOCKER:"
|
|
148
|
-
ROLE_BLOCKER_NONE: str = "none"
|
|
149
|
-
HARD_CONSTRAINTS_LABEL: str = "HARD CONSTRAINTS"
|
|
150
|
-
REFUSAL_RESTATES_FOUR_QUESTIONS: str = "restates-four-questions"
|
|
151
|
-
REFUSAL_MISSING_ROLE_LINES: str = "missing-role-lines"
|
|
152
|
-
REFUSAL_EXAMPLE_NOUN_IN_SCOPE: str = "example-noun-in-scope"
|
|
153
|
-
REFUSAL_DROPPED_HARD_CONSTRAINT: str = "dropped-hard-constraint"
|
|
154
|
-
BULLET_PREFIX: str = "- "
|
|
155
|
-
QUESTION_LINE_SEPARATOR: str = "\n"
|
|
156
|
-
SCORE_ACCEPT: str = "accept"
|
|
157
|
-
SCORE_REFUSE: str = "refuse"
|
|
158
|
-
EXAMPLE_NOUN_PATTERN: str = r"[A-Za-z][A-Za-z0-9_+-]{3,}"
|
|
159
|
-
ALL_FOUR_QUESTION_STEMS: tuple[str, ...] = (
|
|
160
|
-
"What a top person in that field would do that this prompt omitted.",
|
|
161
|
-
"The raw source to start from.",
|
|
162
|
-
"The methods and tools they would use.",
|
|
163
|
-
"What besides the agent or Jon would show the result is right.",
|
|
164
|
-
)
|
|
165
|
-
ALL_EXAMPLE_NOUN_STOPWORDS: frozenset[str] = frozenset(
|
|
166
|
-
{
|
|
167
|
-
"none",
|
|
168
|
-
"the",
|
|
169
|
-
"and",
|
|
170
|
-
"for",
|
|
171
|
-
"with",
|
|
172
|
-
"from",
|
|
173
|
-
"that",
|
|
174
|
-
"this",
|
|
175
|
-
"only",
|
|
176
|
-
"illustration",
|
|
177
|
-
"example",
|
|
178
|
-
"into",
|
|
179
|
-
"when",
|
|
180
|
-
"your",
|
|
181
|
-
"their",
|
|
182
|
-
"about",
|
|
183
|
-
"such",
|
|
184
|
-
"like",
|
|
185
|
-
"say",
|
|
186
|
-
}
|
|
187
|
-
)
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
"""Role-shaped default rewrite for a first task when no rewrite command is set.
|
|
2
|
-
|
|
3
|
-
``refusal_reason`` in ``deepen_role_gate.py`` refuses a rewrite that pastes the
|
|
4
|
-
four question stems back at the worker, and one without the SCOPE, EXAMPLES,
|
|
5
|
-
and ZOOM-IN BLOCKER lines. The default rewrite is built to carry those lines,
|
|
6
|
-
so a first task deepens on every host with nothing configured: the scope is
|
|
7
|
-
the task's own sentences with the marked examples taken out, the examples are
|
|
8
|
-
the parts the task marks as examples, and the blocker asks for confirmation
|
|
9
|
-
when the scope names one of those examples.
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
from __future__ import annotations
|
|
13
|
-
|
|
14
|
-
import re
|
|
15
|
-
|
|
16
|
-
from hooks_constants.deepen_default_rewrite_constants import (
|
|
17
|
-
ALL_EXAMPLE_END_MARKERS,
|
|
18
|
-
ALL_EXAMPLE_SENTENCE_MARKERS,
|
|
19
|
-
DEFAULT_BLOCKER_TEMPLATE,
|
|
20
|
-
DEFAULT_CLOSING_LINE,
|
|
21
|
-
DEFAULT_SCOPE_MAX_CHARACTERS,
|
|
22
|
-
DEFAULT_TASK_HEADING,
|
|
23
|
-
EXAMPLE_LEAD_PUNCTUATION,
|
|
24
|
-
EXAMPLE_LIST_SEPARATOR,
|
|
25
|
-
EXAMPLE_MARKER_ALTERNATION_SEPARATOR,
|
|
26
|
-
EXAMPLE_MARKER_PATTERN_TEMPLATE,
|
|
27
|
-
SCOPE_CLIPPED_SUFFIX,
|
|
28
|
-
SENTENCE_BREAK_PATTERN,
|
|
29
|
-
WORD_SEPARATOR,
|
|
30
|
-
)
|
|
31
|
-
from hooks_constants.deepen_first_prompt_constants import (
|
|
32
|
-
DEEPEN_MARKER,
|
|
33
|
-
HARD_CONSTRAINTS_LABEL,
|
|
34
|
-
REFUSAL_EXAMPLE_NOUN_IN_SCOPE,
|
|
35
|
-
ROLE_BLOCKER_LABEL,
|
|
36
|
-
ROLE_BLOCKER_NONE,
|
|
37
|
-
ROLE_EXAMPLES_LABEL,
|
|
38
|
-
ROLE_SCOPE_LABEL,
|
|
39
|
-
)
|
|
40
|
-
from deepen_role_gate import refusal_reason
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def _body_before_constraints(task_text: str) -> str:
|
|
44
|
-
all_body_lines: list[str] = []
|
|
45
|
-
for each_line in task_text.splitlines():
|
|
46
|
-
if each_line.strip().upper().startswith(HARD_CONSTRAINTS_LABEL):
|
|
47
|
-
break
|
|
48
|
-
all_body_lines.append(each_line)
|
|
49
|
-
return WORD_SEPARATOR.join(WORD_SEPARATOR.join(all_body_lines).split())
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def _sentences(body_text: str) -> list[str]:
|
|
53
|
-
return [each.strip() for each in re.split(SENTENCE_BREAK_PATTERN, body_text) if each.strip()]
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def _example_marker_pattern() -> re.Pattern[str]:
|
|
57
|
-
alternatives = EXAMPLE_MARKER_ALTERNATION_SEPARATOR.join(re.escape(each_marker) for each_marker in ALL_EXAMPLE_SENTENCE_MARKERS)
|
|
58
|
-
return re.compile(EXAMPLE_MARKER_PATTERN_TEMPLATE.format(alternatives=alternatives), re.IGNORECASE)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def _split_example(sentence: str) -> tuple[str, list[str]]:
|
|
62
|
-
"""Return the sentence without its marked examples, and the example texts.
|
|
63
|
-
|
|
64
|
-
A marker counts only as a whole phrase, so "for examples of" and "such
|
|
65
|
-
assumptions" stay in the scope. The earliest marker splits first, and the
|
|
66
|
-
text after the example is split again for later markers.
|
|
67
|
-
"""
|
|
68
|
-
marker_match = _example_marker_pattern().search(sentence)
|
|
69
|
-
if marker_match is None:
|
|
70
|
-
return sentence, []
|
|
71
|
-
head = sentence[: marker_match.start()].rstrip(EXAMPLE_LEAD_PUNCTUATION + "(")
|
|
72
|
-
tail = sentence[marker_match.end() :].lstrip(EXAMPLE_LEAD_PUNCTUATION)
|
|
73
|
-
end_index = min(
|
|
74
|
-
(tail.find(each_end) for each_end in ALL_EXAMPLE_END_MARKERS if tail.find(each_end) >= 0),
|
|
75
|
-
default=len(tail),
|
|
76
|
-
)
|
|
77
|
-
example = tail[:end_index].rstrip(".!? ")
|
|
78
|
-
remainder_scope, all_remainder_examples = _split_example(
|
|
79
|
-
tail[end_index:].lstrip(EXAMPLE_LEAD_PUNCTUATION + ")")
|
|
80
|
-
)
|
|
81
|
-
all_examples = ([example] if example else []) + all_remainder_examples
|
|
82
|
-
scope_part = f"{head} {remainder_scope}".strip() if head else ""
|
|
83
|
-
return scope_part, all_examples
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def _clipped(text: str) -> str:
|
|
87
|
-
if len(text) <= DEFAULT_SCOPE_MAX_CHARACTERS:
|
|
88
|
-
return text
|
|
89
|
-
cut = text[:DEFAULT_SCOPE_MAX_CHARACTERS]
|
|
90
|
-
kept = cut[: cut.rfind(" ")] if " " in cut else cut
|
|
91
|
-
return f"{kept} {SCOPE_CLIPPED_SUFFIX}"
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def _scope_and_examples(task_text: str) -> tuple[str, str]:
|
|
95
|
-
all_splits = [_split_example(each) for each in _sentences(_body_before_constraints(task_text))]
|
|
96
|
-
all_examples = [each_example for _, all_sentence_examples in all_splits for each_example in all_sentence_examples]
|
|
97
|
-
all_scope_parts = [each_part for each_part, _ in all_splits if each_part]
|
|
98
|
-
scope_text = WORD_SEPARATOR.join(all_scope_parts) or task_text.strip()
|
|
99
|
-
examples_text = EXAMPLE_LIST_SEPARATOR.join(all_examples) if all_examples else ROLE_BLOCKER_NONE
|
|
100
|
-
return _clipped(scope_text), examples_text
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def _rewrite_with_blocker(scope: str, examples: str, blocker: str, task_text: str) -> str:
|
|
104
|
-
return (
|
|
105
|
-
f"{DEEPEN_MARKER}\n\n"
|
|
106
|
-
f"{ROLE_SCOPE_LABEL} {scope}\n"
|
|
107
|
-
f"{ROLE_EXAMPLES_LABEL} {examples}\n"
|
|
108
|
-
f"{ROLE_BLOCKER_LABEL} {blocker}\n\n"
|
|
109
|
-
f"{DEFAULT_TASK_HEADING}\n{task_text}\n\n"
|
|
110
|
-
f"{DEFAULT_CLOSING_LINE}"
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def role_default_rewrite(task_text: str) -> str:
|
|
115
|
-
"""Return the default rewrite: role lines from the task, the task itself, and one closing order.
|
|
116
|
-
|
|
117
|
-
Args:
|
|
118
|
-
task_text: The first task with any leading /deepen removed.
|
|
119
|
-
|
|
120
|
-
Returns:
|
|
121
|
-
A rewrite that opens with the deepen marker and carries the three role lines.
|
|
122
|
-
"""
|
|
123
|
-
scope, examples = _scope_and_examples(task_text)
|
|
124
|
-
rewrite = _rewrite_with_blocker(scope, examples, ROLE_BLOCKER_NONE, task_text)
|
|
125
|
-
if refusal_reason(task_text, rewrite) != REFUSAL_EXAMPLE_NOUN_IN_SCOPE:
|
|
126
|
-
return rewrite
|
|
127
|
-
blocker = DEFAULT_BLOCKER_TEMPLATE.format(examples=examples)
|
|
128
|
-
return _rewrite_with_blocker(scope, examples, blocker, task_text)
|
|
@@ -1,383 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""UserPromptSubmit hook: deepen the first interactive task before work starts.
|
|
3
|
-
|
|
4
|
-
Cursor runs it on ``beforeSubmitPrompt`` too, and passes that event name to
|
|
5
|
-
the copy it imports from the Claude Code settings, so both names count as a
|
|
6
|
-
first prompt. When both copies run, the turn marker lets one inject.
|
|
7
|
-
|
|
8
|
-
Skip checks stay local. One bounded deepen call runs only for a first task.
|
|
9
|
-
Claude settings and Codex cannot replace the prompt string, so this hook emits
|
|
10
|
-
additionalContext. Claude desktop and CLI also ship a prompt.submit mod that
|
|
11
|
-
rewrites the visible text; this hook stays silent when that mod is on outside
|
|
12
|
-
a cloud session.
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
from __future__ import annotations
|
|
16
|
-
|
|
17
|
-
import json
|
|
18
|
-
import os
|
|
19
|
-
import shlex
|
|
20
|
-
import subprocess
|
|
21
|
-
import sys
|
|
22
|
-
from dataclasses import dataclass
|
|
23
|
-
from enum import StrEnum
|
|
24
|
-
from pathlib import Path
|
|
25
|
-
|
|
26
|
-
_hooks_dir = str(Path(__file__).resolve().parent.parent)
|
|
27
|
-
if _hooks_dir not in sys.path:
|
|
28
|
-
sys.path.insert(0, _hooks_dir)
|
|
29
|
-
|
|
30
|
-
from hooks_constants.cloud_session import is_cloud_session
|
|
31
|
-
from hooks_constants.deepen_first_prompt_constants import (
|
|
32
|
-
ADDITIONAL_CONTEXT_KEY,
|
|
33
|
-
ALL_FIRST_PROMPT_EVENT_NAMES,
|
|
34
|
-
ALL_HELPER_REPORT_MARKERS,
|
|
35
|
-
ALL_LOOP_PROMPT_PREFIXES,
|
|
36
|
-
ALL_NONINTERACTIVE_ENV_TRUE_VALUES,
|
|
37
|
-
ALL_SPAWN_REWRITE_PREFIXES,
|
|
38
|
-
ALL_TRIVIAL_PROMPTS,
|
|
39
|
-
CLAUDE_HOST_ENV_VAR,
|
|
40
|
-
CLAUDE_HOST_TRUE_VALUE,
|
|
41
|
-
DEEPEN_AGENT_MARKER,
|
|
42
|
-
DEEPEN_COMMAND_ENV_VAR,
|
|
43
|
-
DEEPEN_COMMAND_TIMEOUT_SECONDS,
|
|
44
|
-
DEEPEN_FIRST_PROMPT_PLUGIN_NAME,
|
|
45
|
-
DEEPEN_MARKER,
|
|
46
|
-
DEEPEN_OUTPUT_ENV_VAR,
|
|
47
|
-
DEEPEN_OUTPUT_JSON,
|
|
48
|
-
DEEPEN_OUTPUT_REWRITE,
|
|
49
|
-
DEEPEN_REWRITE_ENV_VAR,
|
|
50
|
-
DEEPEN_SLASH_COMMAND,
|
|
51
|
-
HOOK_EVENT_NAME_KEY,
|
|
52
|
-
HOOK_SPECIFIC_OUTPUT_KEY,
|
|
53
|
-
JSONL_ROLE_KEY,
|
|
54
|
-
JSONL_TYPE_KEY,
|
|
55
|
-
NONINTERACTIVE_CODEX_ENV_VAR,
|
|
56
|
-
NONINTERACTIVE_PRINT_ENV_VAR,
|
|
57
|
-
PROMPT_FIELD_NAME,
|
|
58
|
-
SKIP_DEEPEN_ENV_VAR,
|
|
59
|
-
SKIP_DEEPEN_TRUE_VALUE,
|
|
60
|
-
TRANSCRIPT_PATH_FIELD_NAME,
|
|
61
|
-
USER_PROMPT_SUBMIT_EVENT_NAME,
|
|
62
|
-
USER_ROLE_NAME,
|
|
63
|
-
USER_TYPE_NAME,
|
|
64
|
-
UTF8_ENCODING,
|
|
65
|
-
)
|
|
66
|
-
from hooks_constants.mod_handoff import is_mod_plugin_enabled
|
|
67
|
-
from hooks_constants.pre_tool_use_stdin import read_hook_input_dictionary_from_stdin
|
|
68
|
-
from hooks_constants.subprocess_window import hidden_window_creation_flags
|
|
69
|
-
from deepen_default_rewrite import role_default_rewrite
|
|
70
|
-
from deepen_role_gate import refusal_reason
|
|
71
|
-
from deepen_turn_marker import claim_turn_marker, turn_marker_exists
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
class SkipReason(StrEnum):
|
|
75
|
-
"""Why the first-task gate left a prompt unchanged."""
|
|
76
|
-
|
|
77
|
-
LATER_TURN = "later-turn"
|
|
78
|
-
MARKER_PRESENT = "marker-present"
|
|
79
|
-
SPAWN_REWRITE = "spawn-rewrite"
|
|
80
|
-
SKIP_FLAG = "skip-flag"
|
|
81
|
-
NONINTERACTIVE = "noninteractive"
|
|
82
|
-
TRIVIAL = "trivial"
|
|
83
|
-
SLASH_COMMAND = "slash-command"
|
|
84
|
-
DEEPEN_AGENT = "deepen-agent"
|
|
85
|
-
LOOP_OR_HELPER = "loop-or-helper"
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
@dataclass(frozen=True)
|
|
89
|
-
class Skip:
|
|
90
|
-
"""Leave the submitted prompt text unchanged."""
|
|
91
|
-
|
|
92
|
-
reason: SkipReason
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
@dataclass(frozen=True)
|
|
96
|
-
class Deepen:
|
|
97
|
-
"""Replace the first task with a deeper rewrite."""
|
|
98
|
-
|
|
99
|
-
rewritten_prompt: str
|
|
100
|
-
additional_context: str
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
@dataclass(frozen=True)
|
|
104
|
-
class Refuse:
|
|
105
|
-
"""Leave the submitted prompt unchanged because the candidate rewrite failed."""
|
|
106
|
-
|
|
107
|
-
reason: str
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
DeepenDecision = Skip | Deepen | Refuse
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def submitted_prompt_text(all_hook_fields: dict[str, object]) -> str:
|
|
114
|
-
"""Return the submitted prompt text, or empty when the payload has none."""
|
|
115
|
-
prompt = all_hook_fields.get(PROMPT_FIELD_NAME)
|
|
116
|
-
if isinstance(prompt, str):
|
|
117
|
-
return prompt
|
|
118
|
-
return ""
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def _env_flag_is_true(variable_name: str) -> bool:
|
|
122
|
-
return os.environ.get(variable_name, "").strip().lower() in ALL_NONINTERACTIVE_ENV_TRUE_VALUES
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def _normalized_prompt(prompt: str) -> str:
|
|
126
|
-
return prompt.strip().lower()
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
def _first_token(prompt: str) -> str:
|
|
130
|
-
stripped = prompt.strip()
|
|
131
|
-
if not stripped:
|
|
132
|
-
return ""
|
|
133
|
-
return stripped.split(None, 1)[0]
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
def _parsed_transcript_entry(transcript_line: str) -> dict[str, object] | None:
|
|
137
|
-
stripped_line = transcript_line.strip()
|
|
138
|
-
if not stripped_line:
|
|
139
|
-
return None
|
|
140
|
-
try:
|
|
141
|
-
parsed_entry = json.loads(stripped_line)
|
|
142
|
-
except json.JSONDecodeError:
|
|
143
|
-
return None
|
|
144
|
-
return parsed_entry if isinstance(parsed_entry, dict) else None
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
def _is_user_entry(all_entry_fields: dict[str, object] | None) -> bool:
|
|
148
|
-
if all_entry_fields is None:
|
|
149
|
-
return False
|
|
150
|
-
if all_entry_fields.get(JSONL_TYPE_KEY) == USER_TYPE_NAME:
|
|
151
|
-
return True
|
|
152
|
-
if all_entry_fields.get(JSONL_ROLE_KEY) == USER_ROLE_NAME:
|
|
153
|
-
return True
|
|
154
|
-
message = all_entry_fields.get("message")
|
|
155
|
-
return isinstance(message, dict) and message.get(JSONL_ROLE_KEY) == USER_ROLE_NAME
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
def _transcript_has_prior_user_turn(transcript_path: object) -> bool:
|
|
159
|
-
if not isinstance(transcript_path, str) or not transcript_path:
|
|
160
|
-
return False
|
|
161
|
-
try:
|
|
162
|
-
with Path(transcript_path).open(encoding=UTF8_ENCODING) as transcript:
|
|
163
|
-
return any(_is_user_entry(_parsed_transcript_entry(each_line)) for each_line in transcript)
|
|
164
|
-
except OSError:
|
|
165
|
-
return False
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
def _environment_skip_reason() -> SkipReason | None:
|
|
169
|
-
if os.environ.get(SKIP_DEEPEN_ENV_VAR, "").strip() == SKIP_DEEPEN_TRUE_VALUE:
|
|
170
|
-
return SkipReason.SKIP_FLAG
|
|
171
|
-
if _env_flag_is_true(NONINTERACTIVE_PRINT_ENV_VAR) or _env_flag_is_true(
|
|
172
|
-
NONINTERACTIVE_CODEX_ENV_VAR
|
|
173
|
-
):
|
|
174
|
-
return SkipReason.NONINTERACTIVE
|
|
175
|
-
return None
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
def _marker_skip_reason(prompt: str) -> SkipReason | None:
|
|
179
|
-
if DEEPEN_MARKER in prompt:
|
|
180
|
-
return SkipReason.MARKER_PRESENT
|
|
181
|
-
if DEEPEN_AGENT_MARKER in prompt:
|
|
182
|
-
return SkipReason.DEEPEN_AGENT
|
|
183
|
-
if any(prompt.startswith(each_prefix) for each_prefix in ALL_SPAWN_REWRITE_PREFIXES):
|
|
184
|
-
return SkipReason.SPAWN_REWRITE
|
|
185
|
-
if any(prompt.lstrip().lower().startswith(each_prefix) for each_prefix in ALL_LOOP_PROMPT_PREFIXES):
|
|
186
|
-
return SkipReason.LOOP_OR_HELPER
|
|
187
|
-
if any(each_marker in prompt.lower() for each_marker in ALL_HELPER_REPORT_MARKERS):
|
|
188
|
-
return SkipReason.LOOP_OR_HELPER
|
|
189
|
-
return None
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
def _shape_skip_reason(prompt: str) -> SkipReason | None:
|
|
193
|
-
first_token = _first_token(prompt)
|
|
194
|
-
if first_token.startswith("/") and first_token.lower() != DEEPEN_SLASH_COMMAND:
|
|
195
|
-
return SkipReason.SLASH_COMMAND
|
|
196
|
-
if first_token.lower() == DEEPEN_SLASH_COMMAND and prompt.strip().lower() == DEEPEN_SLASH_COMMAND:
|
|
197
|
-
return SkipReason.SLASH_COMMAND
|
|
198
|
-
if _normalized_prompt(prompt) in ALL_TRIVIAL_PROMPTS or not prompt.strip():
|
|
199
|
-
return SkipReason.TRIVIAL
|
|
200
|
-
return None
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
def skip_reason_for(all_hook_fields: dict[str, object]) -> SkipReason | None:
|
|
204
|
-
"""Return the first skip that matches, or None when the gate should deepen."""
|
|
205
|
-
event_name = all_hook_fields.get("hook_event_name")
|
|
206
|
-
if event_name not in ALL_FIRST_PROMPT_EVENT_NAMES and event_name not in (None, ""):
|
|
207
|
-
return SkipReason.LATER_TURN
|
|
208
|
-
prompt = submitted_prompt_text(all_hook_fields)
|
|
209
|
-
reason = _environment_skip_reason() or _marker_skip_reason(prompt) or _shape_skip_reason(prompt)
|
|
210
|
-
if reason is not None:
|
|
211
|
-
return reason
|
|
212
|
-
if turn_marker_exists(all_hook_fields) or _transcript_has_prior_user_turn(
|
|
213
|
-
all_hook_fields.get(TRANSCRIPT_PATH_FIELD_NAME)
|
|
214
|
-
):
|
|
215
|
-
return SkipReason.LATER_TURN
|
|
216
|
-
return None
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
def task_text_for_deepen(prompt: str) -> str:
|
|
220
|
-
"""Return the task body, stripping a leading /deepen command when present."""
|
|
221
|
-
stripped = prompt.strip()
|
|
222
|
-
first_token = _first_token(stripped)
|
|
223
|
-
if first_token.lower() != DEEPEN_SLASH_COMMAND:
|
|
224
|
-
return stripped
|
|
225
|
-
remainder = stripped[len(first_token) :].strip()
|
|
226
|
-
return remainder or stripped
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
def _with_deepen_marker(rewritten_body: str) -> str:
|
|
230
|
-
if DEEPEN_MARKER in rewritten_body:
|
|
231
|
-
return rewritten_body
|
|
232
|
-
return f"{DEEPEN_MARKER}\n\n{rewritten_body}"
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
def _command_rewrite(task_text: str) -> str | None:
|
|
236
|
-
command = os.environ.get(DEEPEN_COMMAND_ENV_VAR, "").strip()
|
|
237
|
-
if not command:
|
|
238
|
-
return None
|
|
239
|
-
try:
|
|
240
|
-
completed = subprocess.run(
|
|
241
|
-
shlex.split(command),
|
|
242
|
-
input=task_text,
|
|
243
|
-
capture_output=True,
|
|
244
|
-
text=True,
|
|
245
|
-
timeout=DEEPEN_COMMAND_TIMEOUT_SECONDS,
|
|
246
|
-
check=False,
|
|
247
|
-
creationflags=hidden_window_creation_flags(),
|
|
248
|
-
)
|
|
249
|
-
except (OSError, subprocess.TimeoutExpired):
|
|
250
|
-
return None
|
|
251
|
-
command_text = completed.stdout.strip()
|
|
252
|
-
if completed.returncode != 0 or not command_text:
|
|
253
|
-
return None
|
|
254
|
-
return _with_deepen_marker(command_text)
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
def _default_rewrite(task_text: str) -> str:
|
|
258
|
-
"""Return the role-shaped default rewrite the gate accepts."""
|
|
259
|
-
return role_default_rewrite(task_text)
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
def expand_first_task(prompt: str) -> str:
|
|
263
|
-
"""Rewrite a first task so the worker does the omitted expert job."""
|
|
264
|
-
stub_rewrite = os.environ.get(DEEPEN_REWRITE_ENV_VAR, "").strip()
|
|
265
|
-
task_text = task_text_for_deepen(prompt)
|
|
266
|
-
if stub_rewrite:
|
|
267
|
-
return _with_deepen_marker(stub_rewrite.replace("{prompt}", task_text))
|
|
268
|
-
command_rewrite = _command_rewrite(task_text)
|
|
269
|
-
if command_rewrite is not None:
|
|
270
|
-
return command_rewrite
|
|
271
|
-
return _default_rewrite(task_text)
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
def additional_context_for(rewritten_prompt: str) -> str:
|
|
275
|
-
"""Return the context a command hook can inject beside the original prompt."""
|
|
276
|
-
return (
|
|
277
|
-
f"{DEEPEN_MARKER}\n"
|
|
278
|
-
"Treat the submitted prompt as this rewritten task and do only that job.\n\n"
|
|
279
|
-
f"{rewritten_prompt}"
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
def decide_first_prompt(all_hook_fields: dict[str, object]) -> DeepenDecision:
|
|
284
|
-
"""Skip, refuse, or deepen one UserPromptSubmit payload."""
|
|
285
|
-
reason = skip_reason_for(all_hook_fields)
|
|
286
|
-
if reason is not None:
|
|
287
|
-
return Skip(reason)
|
|
288
|
-
prompt = submitted_prompt_text(all_hook_fields)
|
|
289
|
-
rewritten_prompt = expand_first_task(prompt)
|
|
290
|
-
refusal = refusal_reason(task_text_for_deepen(prompt), rewritten_prompt)
|
|
291
|
-
if refusal is not None:
|
|
292
|
-
return Refuse(refusal)
|
|
293
|
-
return Deepen(
|
|
294
|
-
rewritten_prompt=rewritten_prompt,
|
|
295
|
-
additional_context=additional_context_for(rewritten_prompt),
|
|
296
|
-
)
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
def _print_skip_json(reason: SkipReason) -> None:
|
|
300
|
-
sys.stdout.write(json.dumps({"decision": "skip", "reason": reason.value}) + "\n")
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
def _print_refuse_json(reason: str) -> None:
|
|
304
|
-
sys.stdout.write(json.dumps({"decision": "refuse", "reason": reason}) + "\n")
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
def _print_deepen_json(deepen: Deepen) -> None:
|
|
308
|
-
sys.stdout.write(
|
|
309
|
-
json.dumps(
|
|
310
|
-
{
|
|
311
|
-
"decision": "deepen",
|
|
312
|
-
"rewrittenPrompt": deepen.rewritten_prompt,
|
|
313
|
-
"additionalContext": deepen.additional_context,
|
|
314
|
-
}
|
|
315
|
-
)
|
|
316
|
-
+ "\n"
|
|
317
|
-
)
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
def _print_additional_context(deepen: Deepen) -> None:
|
|
321
|
-
sys.stdout.write(
|
|
322
|
-
json.dumps(
|
|
323
|
-
{
|
|
324
|
-
HOOK_SPECIFIC_OUTPUT_KEY: {
|
|
325
|
-
HOOK_EVENT_NAME_KEY: USER_PROMPT_SUBMIT_EVENT_NAME,
|
|
326
|
-
ADDITIONAL_CONTEXT_KEY: deepen.additional_context,
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
)
|
|
330
|
-
+ "\n"
|
|
331
|
-
)
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
def should_leave_rewrite_to_mod() -> bool:
|
|
335
|
-
"""Return True when the Claude prompt.submit mod will rewrite the visible text.
|
|
336
|
-
|
|
337
|
-
Claude Code sets ``CLAUDECODE=1`` for every command hook. Codex reads the same
|
|
338
|
-
Claude settings file but runs no Claude mod, so a Codex run keeps the envelope.
|
|
339
|
-
"""
|
|
340
|
-
is_claude_host = os.environ.get(CLAUDE_HOST_ENV_VAR, "").strip() == CLAUDE_HOST_TRUE_VALUE
|
|
341
|
-
return (
|
|
342
|
-
is_claude_host
|
|
343
|
-
and is_mod_plugin_enabled(DEEPEN_FIRST_PROMPT_PLUGIN_NAME)
|
|
344
|
-
and not is_cloud_session()
|
|
345
|
-
)
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
def emit_decision(decision: DeepenDecision, all_hook_fields: dict[str, object]) -> None:
|
|
349
|
-
"""Write the host encoding for one decision."""
|
|
350
|
-
output_mode = os.environ.get(DEEPEN_OUTPUT_ENV_VAR, "").strip().lower()
|
|
351
|
-
if isinstance(decision, Skip):
|
|
352
|
-
if output_mode == DEEPEN_OUTPUT_JSON:
|
|
353
|
-
_print_skip_json(decision.reason)
|
|
354
|
-
return
|
|
355
|
-
if isinstance(decision, Refuse):
|
|
356
|
-
if output_mode == DEEPEN_OUTPUT_JSON:
|
|
357
|
-
_print_refuse_json(decision.reason)
|
|
358
|
-
return
|
|
359
|
-
if not claim_turn_marker(all_hook_fields):
|
|
360
|
-
return
|
|
361
|
-
if output_mode == DEEPEN_OUTPUT_JSON:
|
|
362
|
-
_print_deepen_json(decision)
|
|
363
|
-
return
|
|
364
|
-
if output_mode == DEEPEN_OUTPUT_REWRITE:
|
|
365
|
-
sys.stdout.write(decision.rewritten_prompt)
|
|
366
|
-
if not decision.rewritten_prompt.endswith("\n"):
|
|
367
|
-
sys.stdout.write("\n")
|
|
368
|
-
return
|
|
369
|
-
if should_leave_rewrite_to_mod():
|
|
370
|
-
return
|
|
371
|
-
_print_additional_context(decision)
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
def main() -> None:
|
|
375
|
-
"""Read one UserPromptSubmit payload and emit a skip or a deepened rewrite."""
|
|
376
|
-
hook_payload = read_hook_input_dictionary_from_stdin()
|
|
377
|
-
if hook_payload is None:
|
|
378
|
-
return
|
|
379
|
-
emit_decision(decide_first_prompt(hook_payload), hook_payload)
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
if __name__ == "__main__":
|
|
383
|
-
main()
|