claude-dev-env 8.98.9 → 8.98.10
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.md +1 -1
- package/bin/install.prompt-context.test.mjs +1 -1
- package/docs/agents-guidance/asks.md +13 -12
- package/docs/rule-guides/session-title.md +15 -4
- package/hooks/blocking/open_tasks_stop_gate.py +60 -259
- package/hooks/blocking/open_tasks_transcript.py +104 -90
- package/hooks/blocking/session_title_turn_gate.py +142 -56
- package/hooks/blocking/test_open_tasks_stop_gate.py +203 -500
- package/hooks/blocking/test_open_tasks_transcript.py +123 -15
- package/hooks/blocking/test_session_title_turn_gate.py +173 -90
- package/hooks/features/conduct-gates.md +9 -9
- package/hooks/hooks.json +1 -1
- package/hooks/hooks_constants/open_tasks_stop_gate_constants.py +27 -60
- package/hooks/hooks_constants/session_title_constants.py +19 -1
- package/hooks/open_task_asks.py +6 -101
- package/hooks/session/test_tracking_nudge.py +124 -0
- package/hooks/session/tracking_nudge.py +113 -0
- package/hooks/test_open_task_asks.py +6 -13
- package/package.json +1 -1
- package/rules/index.md +1 -1
- package/scripts/policy_lint/adapter_configuration.py +6 -6
- package/hooks/session/open_task_ask_recorder.py +0 -38
- package/hooks/session/test_open_task_ask_recorder.py +0 -57
|
@@ -2,7 +2,7 @@ from pathlib import Path
|
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
4
|
|
|
5
|
-
from open_task_asks import
|
|
5
|
+
from open_task_asks import claude_home, is_ask
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
@pytest.fixture(autouse=True)
|
|
@@ -41,17 +41,10 @@ def test_should_classify_work_prompts_as_asks(prompt_text: str, is_expected_ask:
|
|
|
41
41
|
assert is_ask(prompt_text) is is_expected_ask
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
def
|
|
45
|
-
|
|
46
|
-
assert
|
|
47
|
-
assert record_prompt("s-1", "Submit them") == "ask-2"
|
|
48
|
-
assert [each_prompt.ask_id for each_prompt in recorded_prompts("s-1")] == [
|
|
49
|
-
"ask-1",
|
|
50
|
-
None,
|
|
51
|
-
"ask-2",
|
|
52
|
-
]
|
|
44
|
+
def test_should_read_the_claude_home_from_claude_config_dir(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
|
45
|
+
monkeypatch.setenv("CLAUDE_CONFIG_DIR", str(tmp_path / "profile"))
|
|
46
|
+
assert claude_home() == tmp_path / "profile"
|
|
53
47
|
|
|
54
48
|
|
|
55
|
-
def
|
|
56
|
-
assert
|
|
57
|
-
assert recorded_prompts("../escape") == []
|
|
49
|
+
def test_should_default_the_claude_home_to_the_user_home(tmp_path: Path) -> None:
|
|
50
|
+
assert claude_home() == tmp_path / ".claude"
|
package/package.json
CHANGED
package/rules/index.md
CHANGED
|
@@ -24,7 +24,7 @@ Each line gives a rule in brief. When its trigger matches your task, open the gu
|
|
|
24
24
|
**Shipping**
|
|
25
25
|
- **Pull request lifecycle**. Before a commit, push, pull request action, or merge. Invoke the `pr-lifecycle` skill; a hook denies those commands until it loads.
|
|
26
26
|
- **Proof before a pull request** ([guide](../docs/rule-guides/proof-before-pull-request.md)). Before you open a pull request. Run the change with and without it, and quote both under "Proof in practice".
|
|
27
|
-
- **Session title** ([guide](../docs/rule-guides/session-title.md)). The session has a tool whose name ends in `__set_session_title`. Set `<emoji> <name>`
|
|
27
|
+
- **Session title** ([guide](../docs/rule-guides/session-title.md)). The session has a tool whose name ends in `__set_session_title`. Set `<emoji> <name>` when the status changes.
|
|
28
28
|
|
|
29
29
|
**Memory**
|
|
30
30
|
- **Durable facts only** ([guide](../docs/rule-guides/memory-stores-durable-facts.md)). Writing auto-memory or a `MEMORY.md` entry. Keep only a fact that helps a fresh session weeks later.
|
|
@@ -175,8 +175,9 @@ def _names_exempt_registration_path(registered_string: str) -> bool:
|
|
|
175
175
|
asks the model to check a hedged claim, and the call it reads stays in place.
|
|
176
176
|
|
|
177
177
|
The session title gates keep the session title in its status format. One
|
|
178
|
-
denies a malformed title, and the other asks for a title
|
|
179
|
-
|
|
178
|
+
denies a malformed title, and the other asks for a new title when the
|
|
179
|
+
shown status stops fitting. They decide the shape of a title and no code
|
|
180
|
+
or safety policy.
|
|
180
181
|
|
|
181
182
|
The artifact page gate denies a page publish whose text is unreadable in
|
|
182
183
|
dark mode. It decides how a page renders and no code or safety policy.
|
|
@@ -193,10 +194,9 @@ def _names_exempt_registration_path(registered_string: str) -> bool:
|
|
|
193
194
|
names open review findings. It reads only the wording of a chat card and
|
|
194
195
|
decides no code or safety policy; the merge review gate holds the merge.
|
|
195
196
|
|
|
196
|
-
The open tasks Stop gate holds the end of a turn
|
|
197
|
-
list
|
|
198
|
-
|
|
199
|
-
safety policy.
|
|
197
|
+
The open tasks Stop gate holds the end of a turn once when the turn worked
|
|
198
|
+
on an ask and made no task list call. It decides when a turn may end and
|
|
199
|
+
no code or safety policy.
|
|
200
200
|
|
|
201
201
|
The owned pull request Stop gate holds the end of a turn while a pull
|
|
202
202
|
request the session opened on an owned repository is still open and the
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""UserPromptSubmit hook that records each prompt for the open tasks Stop gate.
|
|
3
|
-
|
|
4
|
-
A prompt that asks for work becomes ``#ask-N`` in
|
|
5
|
-
``~/.claude/open-task-asks/<session id>.json``. The gate then holds the turn
|
|
6
|
-
until a reply closes it with ``Done: #ask-N``, or with
|
|
7
|
-
``Dropped: #ask-N - "<the user's words>"`` quoting a later prompt. The hook
|
|
8
|
-
prints nothing and always exits 0.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
from __future__ import annotations
|
|
12
|
-
|
|
13
|
-
import sys
|
|
14
|
-
from pathlib import Path
|
|
15
|
-
|
|
16
|
-
hooks_root_directory = str(Path(__file__).resolve().parent.parent)
|
|
17
|
-
if hooks_root_directory not in sys.path:
|
|
18
|
-
sys.path.insert(0, hooks_root_directory)
|
|
19
|
-
|
|
20
|
-
from hooks_constants.open_tasks_stop_gate_constants import (
|
|
21
|
-
ALLOW_EXIT_CODE,
|
|
22
|
-
PROMPT_KEY,
|
|
23
|
-
SESSION_ID_KEY,
|
|
24
|
-
)
|
|
25
|
-
from hooks_constants.pre_tool_use_stdin import read_hook_input_dictionary_from_stdin
|
|
26
|
-
from open_task_asks import record_prompt
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def main() -> int:
|
|
30
|
-
hook_input = read_hook_input_dictionary_from_stdin()
|
|
31
|
-
prompt_value = hook_input.get(PROMPT_KEY) if hook_input is not None else None
|
|
32
|
-
if hook_input is not None and isinstance(prompt_value, str):
|
|
33
|
-
record_prompt(hook_input.get(SESSION_ID_KEY), prompt_value)
|
|
34
|
-
return ALLOW_EXIT_CODE
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if __name__ == "__main__":
|
|
38
|
-
sys.exit(main())
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import subprocess
|
|
3
|
-
import sys
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
|
|
6
|
-
RECORDER_PATH = Path(__file__).resolve().parent / "open_task_ask_recorder.py"
|
|
7
|
-
SESSION_ID = "s-123"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def submit_prompt(home_directory: Path, prompt_text: str) -> subprocess.CompletedProcess[str]:
|
|
11
|
-
hook_input = {
|
|
12
|
-
"hook_event_name": "UserPromptSubmit",
|
|
13
|
-
"session_id": SESSION_ID,
|
|
14
|
-
"prompt": prompt_text,
|
|
15
|
-
}
|
|
16
|
-
return subprocess.run(
|
|
17
|
-
[sys.executable, str(RECORDER_PATH)],
|
|
18
|
-
input=json.dumps(hook_input),
|
|
19
|
-
capture_output=True,
|
|
20
|
-
text=True,
|
|
21
|
-
env={
|
|
22
|
-
"HOME": str(home_directory),
|
|
23
|
-
"USERPROFILE": str(home_directory),
|
|
24
|
-
"PATH": "",
|
|
25
|
-
},
|
|
26
|
-
check=False,
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def recorded_ask_ids(home_directory: Path) -> list[str]:
|
|
31
|
-
asks_path = home_directory / ".claude" / "open-task-asks" / f"{SESSION_ID}.json"
|
|
32
|
-
all_prompts = json.loads(asks_path.read_text(encoding="utf-8"))["prompts"]
|
|
33
|
-
return [each_prompt["ask_id"] for each_prompt in all_prompts if each_prompt["ask_id"]]
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def test_should_record_each_ask_outside_the_task_store_and_stay_silent(
|
|
37
|
-
tmp_path: Path,
|
|
38
|
-
) -> None:
|
|
39
|
-
first_process = submit_prompt(tmp_path, "Export the OneUI8 and OneUI9 APKs and submit them")
|
|
40
|
-
submit_prompt(tmp_path, "did the export fail?")
|
|
41
|
-
submit_prompt(tmp_path, "<task-notification>\n<task-id>b1</task-id>\n</task-notification>")
|
|
42
|
-
submit_prompt(tmp_path, "can you check the release dates in neon?")
|
|
43
|
-
assert (first_process.returncode, first_process.stdout) == (0, "")
|
|
44
|
-
assert recorded_ask_ids(tmp_path) == ["ask-1", "ask-2"]
|
|
45
|
-
assert not (tmp_path / ".claude" / "tasks").exists()
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def test_should_allow_malformed_input(tmp_path: Path) -> None:
|
|
49
|
-
completed_process = subprocess.run(
|
|
50
|
-
[sys.executable, str(RECORDER_PATH)],
|
|
51
|
-
input="not json",
|
|
52
|
-
capture_output=True,
|
|
53
|
-
text=True,
|
|
54
|
-
env={"HOME": str(tmp_path), "USERPROFILE": str(tmp_path), "PATH": ""},
|
|
55
|
-
check=False,
|
|
56
|
-
)
|
|
57
|
-
assert (completed_process.returncode, completed_process.stdout) == (0, "")
|