Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions .chock/bin/claude_code.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by agentseam 0.3.2 -- bundle("claude_code"). Do not hand-edit, except the
# Generated by agentseam 0.3.3 -- bundle("claude_code"). Do not hand-edit, except the
# HANDLER block below (between "agentseam handler >>>" and "<<< agentseam handler"),
# which is exactly what this file leaves for you to fill in.
#
Expand All @@ -23,7 +23,7 @@
import warnings as _warnings

# ------------------------------------------------------------------------------
# contract (agentseam 0.3.2)
# contract (agentseam 0.3.3)

"""Canonical event vocabulary, normalized envelope, and decision type."""

Expand Down Expand Up @@ -585,13 +585,14 @@ def _flat_list_wrapper(hook_entry, reverse, canonical_events, command, matcher):

def _cursor_wrapper(cfg, reverse, canonical_events, command, *, fail_closed):
gates = cfg["verdicts"]["answer_events"]
allow_silent = cfg["verdicts"].get("allow_silent_events", ())
hooks = {}
for ev in canonical_events:
name = reverse.get(ev)
if not name:
continue
entry = {"command": command}
if fail_closed and name in gates:
if fail_closed and name in gates and name not in allow_silent:
entry["failClosed"] = True
hooks.setdefault(name, []).append(entry)
return {"version": 1, "hooks": hooks}
Expand Down Expand Up @@ -964,7 +965,8 @@ def writes_for(event, gate):
"""What this event puts under judgement: the call's own text, or what the turn left behind."""
if event.event == PRE_TOOL:
return writes_from_event(event)
if (event.raw or {}).get('stop_hook_active'):
raw = event.raw or {}
if raw.get('stop_hook_active') or raw.get('loop_count'):
return {}
return writes_from_worktree(repo_root_for(event, gate))

Expand Down
10 changes: 6 additions & 4 deletions .chock/bin/codex_cli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by agentseam 0.3.2 -- bundle("codex_cli"). Do not hand-edit, except the
# Generated by agentseam 0.3.3 -- bundle("codex_cli"). Do not hand-edit, except the
# HANDLER block below (between "agentseam handler >>>" and "<<< agentseam handler"),
# which is exactly what this file leaves for you to fill in.
#
Expand All @@ -22,7 +22,7 @@
import warnings as _warnings

# ------------------------------------------------------------------------------
# contract (agentseam 0.3.2)
# contract (agentseam 0.3.3)

"""Canonical event vocabulary, normalized envelope, and decision type."""

Expand Down Expand Up @@ -571,13 +571,14 @@ def _flat_list_wrapper(hook_entry, reverse, canonical_events, command, matcher):

def _cursor_wrapper(cfg, reverse, canonical_events, command, *, fail_closed):
gates = cfg["verdicts"]["answer_events"]
allow_silent = cfg["verdicts"].get("allow_silent_events", ())
hooks = {}
for ev in canonical_events:
name = reverse.get(ev)
if not name:
continue
entry = {"command": command}
if fail_closed and name in gates:
if fail_closed and name in gates and name not in allow_silent:
entry["failClosed"] = True
hooks.setdefault(name, []).append(entry)
return {"version": 1, "hooks": hooks}
Expand Down Expand Up @@ -950,7 +951,8 @@ def writes_for(event, gate):
"""What this event puts under judgement: the call's own text, or what the turn left behind."""
if event.event == PRE_TOOL:
return writes_from_event(event)
if (event.raw or {}).get('stop_hook_active'):
raw = event.raw or {}
if raw.get('stop_hook_active') or raw.get('loop_count'):
return {}
return writes_from_worktree(repo_root_for(event, gate))

Expand Down
21 changes: 16 additions & 5 deletions .chock/bin/cursor.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by agentseam 0.3.2 -- bundle("cursor"). Do not hand-edit, except the
# Generated by agentseam 0.3.3 -- bundle("cursor"). Do not hand-edit, except the
# HANDLER block below (between "agentseam handler >>>" and "<<< agentseam handler"),
# which is exactly what this file leaves for you to fill in.
#
Expand All @@ -22,7 +22,7 @@
import warnings as _warnings

# ------------------------------------------------------------------------------
# contract (agentseam 0.3.2)
# contract (agentseam 0.3.3)

"""Canonical event vocabulary, normalized envelope, and decision type."""

Expand Down Expand Up @@ -372,13 +372,14 @@ def _flat_list_wrapper(hook_entry, reverse, canonical_events, command, matcher):

def _cursor_wrapper(cfg, reverse, canonical_events, command, *, fail_closed):
gates = cfg["verdicts"]["answer_events"]
allow_silent = cfg["verdicts"].get("allow_silent_events", ())
hooks = {}
for ev in canonical_events:
name = reverse.get(ev)
if not name:
continue
entry = {"command": command}
if fail_closed and name in gates:
if fail_closed and name in gates and name not in allow_silent:
entry["failClosed"] = True
hooks.setdefault(name, []).append(entry)
return {"version": 1, "hooks": hooks}
Expand Down Expand Up @@ -513,6 +514,13 @@ def _refusal_reason(v, gate, decision, name):
return _because(decision.reason, note % name)
return decision.reason

def _followup_payload(v, decision, name):
"""STOP: the turn has ended; a refusal comes back as the follow-up the agent runs next."""
gate = v["gates"].get(name)
if gate is None or decision.outcome not in (DENY, ESCALATE, TRANSFORM):
return "", 0
return _json.dumps({"followup_message": _refusal_reason(v, gate, decision, name)}), 0

def _gate_payload(v, gate, decision, name):
"""The PRE_TOOL gate's (permission, reason) pair, before the shared trailing message rule."""
words = v["words"]
Expand All @@ -533,6 +541,8 @@ def cursor_respond(cfg, decision, event):
return "", 0
if canonical in (POST_TOOL, TOOL_FAILURE):
return _flag_payload(v, decision, name)
if canonical == STOP:
return _followup_payload(v, decision, name)
gate = v["gates"].get(name)
if canonical == PROMPT_SUBMIT and gate is not None:
return _prompt_submit_payload(v, gate, decision, name)
Expand All @@ -551,7 +561,7 @@ def cursor_respond(cfg, decision, event):

AGENT = "cursor"

VENDOR = {'agent': 'cursor', 'claims': {'mode': 'shape_inferred'}, 'config_format': 'json', 'config_path': '.cursor/hooks.json', 'display': 'Cursor', 'events': {'afterFileEdit': 'file_changed', 'afterMCPExecution': 'post_tool', 'afterShellExecution': 'post_tool', 'afterTabFileEdit': 'file_changed', 'beforeMCPExecution': 'pre_tool', 'beforeReadFile': 'pre_tool', 'beforeShellExecution': 'pre_tool', 'beforeSubmitPrompt': 'prompt_submit', 'beforeTabFileRead': 'pre_tool', 'postToolUse': 'post_tool', 'postToolUseFailure': 'tool_failure', 'preCompact': 'pre_compact', 'preToolUse': 'pre_tool', 'sessionEnd': 'session_end', 'sessionStart': 'session_start', 'stop': 'stop', 'subagentStart': 'subagent_start', 'subagentStop': 'subagent_stop'}, 'evidence': {'claims': {'basis': 'live-run-partial', 'date': '2026-08-27', 'test': 'tests/test_examples.py::test_each_payload_is_claimed_by_its_own_adapter'}, 'config_path': {'basis': 'live-run-partial', 'date': '2026-08-27', 'test': 'tests/test_vendor_config.py::test_config_path_agrees_with_matrix'}, 'events': {'basis': 'live-run-partial', 'date': '2026-08-27', 'test': 'tests/test_examples.py::test_each_payload_parses_to_the_event_it_is_filed_under'}, 'family': {'basis': 'live-run-partial', 'date': '2026-08-27', 'test': 'tests/test_golden_fixtures.py::test_wire_output_matches_the_frozen_fixture'}, 'fields': {'basis': 'live-run-partial', 'date': '2026-08-27', 'test': 'tests/test_vendor_config.py::test_entries_match_recount'}, 'hook_entry': {'basis': 'live-run-partial', 'date': '2026-08-27', 'test': 'tests/test_golden_fixtures.py::test_hook_config_matches_the_frozen_fixture_on_both_matcher_paths'}, 'tools': {'basis': 'live-run-partial', 'date': '2026-08-27', 'test': 'tests/test_vendor_config.py::test_entries_match_recount'}, 'verdicts': {'basis': 'live-run-partial', 'date': '2026-08-27', 'test': 'tests/test_golden_fixtures.py::test_wire_output_matches_the_frozen_fixture'}}, 'family': 'cursor', 'fields': {'command': ('command', 'tool_input.command'), 'content': ('edits[].new_string', 'tool_input.content', 'tool_input.new_string', 'content'), 'cwd': ('cwd',), 'output': ('tool_output', 'output', 'result_json'), 'path': ('file_path', 'tool_input.file_path', 'tool_input.path'), 'prompt': ('prompt',), 'session_id': ('conversation_id',), 'tool': ('tool_name',), 'tool_use_id': ('tool_use_id',)}, 'hook_entry': {'matcher': False, 'wrapper': 'cursor'}, 'needs_trust': False, 'tools': {}, 'verdicts': {'answer_events': ('beforeMCPExecution', 'beforeReadFile', 'beforeShellExecution', 'beforeSubmitPrompt', 'beforeTabFileRead', 'preToolUse'), 'bare_allow': 'required', 'default_wire_event': 'beforeShellExecution', 'degrade_notes': {'escalate': '%s cannot prompt for confirmation, so this is a block', 'escalate_from_transform': '%s cannot modify the input, so this is a block', 'transform': 'input requires modification, which this gate cannot express', 'transform_missing_input': 'no replacement input was supplied'}, 'flag_note': 'observed after the fact (%s cannot prevent it): %s', 'flag_note_default': 'policy violation', 'gates': {'beforeMCPExecution': {'grammar': 'G4', 'honours_escalate': True, 'honours_transform': False}, 'beforeReadFile': {'grammar': 'G4', 'honours_escalate': False, 'honours_transform': False}, 'beforeShellExecution': {'grammar': 'G4', 'honours_escalate': True, 'honours_transform': False}, 'beforeSubmitPrompt': {'grammar': 'G4', 'honours_escalate': False, 'honours_transform': False}, 'beforeTabFileRead': {'grammar': 'G4', 'honours_escalate': False, 'honours_transform': False}, 'preToolUse': {'grammar': 'G4', 'honours_escalate': False, 'honours_transform': True}}, 'transform_grammar': 'permission_updated_input', 'vocabulary': ('allow', 'ask', 'deny'), 'vocabulary_basis': 'verified', 'words': {'allow': 'allow', 'block': 'deny', 'escalate': 'ask'}}, 'wire_events': {'file_changed': 'afterFileEdit', 'post_tool': 'postToolUse', 'pre_tool': 'preToolUse'}}
VENDOR = {'agent': 'cursor', 'claims': {'mode': 'shape_inferred'}, 'config_format': 'json', 'config_path': '.cursor/hooks.json', 'display': 'Cursor', 'events': {'afterFileEdit': 'file_changed', 'afterMCPExecution': 'post_tool', 'afterShellExecution': 'post_tool', 'afterTabFileEdit': 'file_changed', 'beforeMCPExecution': 'pre_tool', 'beforeReadFile': 'pre_tool', 'beforeShellExecution': 'pre_tool', 'beforeSubmitPrompt': 'prompt_submit', 'beforeTabFileRead': 'pre_tool', 'postToolUse': 'post_tool', 'postToolUseFailure': 'tool_failure', 'preCompact': 'pre_compact', 'preToolUse': 'pre_tool', 'sessionEnd': 'session_end', 'sessionStart': 'session_start', 'stop': 'stop', 'subagentStart': 'subagent_start', 'subagentStop': 'subagent_stop'}, 'evidence': {'claims': {'basis': 'live-run-partial', 'date': '2026-09-23', 'test': 'tests/test_examples.py::test_each_payload_is_claimed_by_its_own_adapter'}, 'config_path': {'basis': 'live-run-partial', 'date': '2026-09-23', 'test': 'tests/test_vendor_config.py::test_config_path_agrees_with_matrix'}, 'events': {'basis': 'live-run-partial', 'date': '2026-09-23', 'test': 'tests/test_examples.py::test_each_payload_parses_to_the_event_it_is_filed_under'}, 'family': {'basis': 'live-run-partial', 'date': '2026-09-23', 'test': 'tests/test_golden_fixtures.py::test_wire_output_matches_the_frozen_fixture'}, 'fields': {'basis': 'live-run-partial', 'date': '2026-09-23', 'test': 'tests/test_vendor_config.py::test_entries_match_recount'}, 'hook_entry': {'basis': 'live-run-partial', 'date': '2026-09-23', 'test': 'tests/test_golden_fixtures.py::test_hook_config_matches_the_frozen_fixture_on_both_matcher_paths'}, 'tools': {'basis': 'live-run-partial', 'date': '2026-09-23', 'test': 'tests/test_vendor_lookups.py::test_cursor_write_tools_are_recorded_from_the_live_witness'}, 'verdicts': {'basis': 'live-run-partial', 'date': '2026-09-23', 'test': 'tests/test_golden_fixtures.py::test_wire_output_matches_the_frozen_fixture'}}, 'family': 'cursor', 'fields': {'command': ('command', 'tool_input.command'), 'content': ('edits[].new_string', 'tool_input.content', 'tool_input.new_string', 'content'), 'cwd': ('cwd',), 'output': ('tool_output', 'output', 'result_json'), 'path': ('file_path', 'tool_input.file_path', 'tool_input.path'), 'prompt': ('prompt',), 'session_id': ('conversation_id',), 'tool': ('tool_name',), 'tool_use_id': ('tool_use_id',)}, 'hook_entry': {'matcher': False, 'wrapper': 'cursor'}, 'needs_trust': False, 'tools': {'write': ('Write',)}, 'verdicts': {'allow_silent_events': ('stop',), 'answer_events': ('beforeMCPExecution', 'beforeReadFile', 'beforeShellExecution', 'beforeSubmitPrompt', 'beforeTabFileRead', 'preToolUse', 'stop'), 'bare_allow': 'required', 'default_wire_event': 'beforeShellExecution', 'degrade_notes': {'escalate': '%s cannot prompt for confirmation, so this is a block', 'escalate_from_transform': '%s cannot modify the input, so this is a block', 'transform': 'input requires modification, which this gate cannot express', 'transform_missing_input': 'no replacement input was supplied'}, 'flag_note': 'observed after the fact (%s cannot prevent it): %s', 'flag_note_default': 'policy violation', 'gates': {'beforeMCPExecution': {'grammar': 'G4', 'honours_escalate': True, 'honours_transform': False}, 'beforeReadFile': {'grammar': 'G4', 'honours_escalate': False, 'honours_transform': False}, 'beforeShellExecution': {'grammar': 'G4', 'honours_escalate': True, 'honours_transform': False}, 'beforeSubmitPrompt': {'grammar': 'G4', 'honours_escalate': False, 'honours_transform': False}, 'beforeTabFileRead': {'grammar': 'G4', 'honours_escalate': False, 'honours_transform': False}, 'preToolUse': {'grammar': 'G4', 'honours_escalate': False, 'honours_transform': True}, 'stop': {'grammar': 'G6', 'honours_escalate': False, 'honours_transform': False}}, 'transform_grammar': 'permission_updated_input', 'vocabulary': ('allow', 'ask', 'deny'), 'vocabulary_basis': 'verified', 'words': {'allow': 'allow', 'block': 'deny', 'escalate': 'ask'}}, 'wire_events': {'file_changed': 'afterFileEdit', 'post_tool': 'postToolUse', 'pre_tool': 'preToolUse'}}


def claims(raw):
Expand Down Expand Up @@ -869,7 +879,8 @@ def writes_for(event, gate):
"""What this event puts under judgement: the call's own text, or what the turn left behind."""
if event.event == PRE_TOOL:
return writes_from_event(event)
if (event.raw or {}).get('stop_hook_active'):
raw = event.raw or {}
if raw.get('stop_hook_active') or raw.get('loop_count'):
return {}
return writes_from_worktree(repo_root_for(event, gate))

Expand Down
10 changes: 6 additions & 4 deletions .chock/bin/devin.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by agentseam 0.3.2 -- bundle("devin"). Do not hand-edit, except the
# Generated by agentseam 0.3.3 -- bundle("devin"). Do not hand-edit, except the
# HANDLER block below (between "agentseam handler >>>" and "<<< agentseam handler"),
# which is exactly what this file leaves for you to fill in.
#
Expand All @@ -22,7 +22,7 @@
import warnings as _warnings

# ------------------------------------------------------------------------------
# contract (agentseam 0.3.2)
# contract (agentseam 0.3.3)

"""Canonical event vocabulary, normalized envelope, and decision type."""

Expand Down Expand Up @@ -553,13 +553,14 @@ def _flat_list_wrapper(hook_entry, reverse, canonical_events, command, matcher):

def _cursor_wrapper(cfg, reverse, canonical_events, command, *, fail_closed):
gates = cfg["verdicts"]["answer_events"]
allow_silent = cfg["verdicts"].get("allow_silent_events", ())
hooks = {}
for ev in canonical_events:
name = reverse.get(ev)
if not name:
continue
entry = {"command": command}
if fail_closed and name in gates:
if fail_closed and name in gates and name not in allow_silent:
entry["failClosed"] = True
hooks.setdefault(name, []).append(entry)
return {"version": 1, "hooks": hooks}
Expand Down Expand Up @@ -932,7 +933,8 @@ def writes_for(event, gate):
"""What this event puts under judgement: the call's own text, or what the turn left behind."""
if event.event == PRE_TOOL:
return writes_from_event(event)
if (event.raw or {}).get('stop_hook_active'):
raw = event.raw or {}
if raw.get('stop_hook_active') or raw.get('loop_count'):
return {}
return writes_from_worktree(repo_root_for(event, gate))

Expand Down
10 changes: 6 additions & 4 deletions .chock/bin/gemini_cli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by agentseam 0.3.2 -- bundle("gemini_cli"). Do not hand-edit, except the
# Generated by agentseam 0.3.3 -- bundle("gemini_cli"). Do not hand-edit, except the
# HANDLER block below (between "agentseam handler >>>" and "<<< agentseam handler"),
# which is exactly what this file leaves for you to fill in.
#
Expand All @@ -22,7 +22,7 @@
import warnings as _warnings

# ------------------------------------------------------------------------------
# contract (agentseam 0.3.2)
# contract (agentseam 0.3.3)

"""Canonical event vocabulary, normalized envelope, and decision type."""

Expand Down Expand Up @@ -553,13 +553,14 @@ def _flat_list_wrapper(hook_entry, reverse, canonical_events, command, matcher):

def _cursor_wrapper(cfg, reverse, canonical_events, command, *, fail_closed):
gates = cfg["verdicts"]["answer_events"]
allow_silent = cfg["verdicts"].get("allow_silent_events", ())
hooks = {}
for ev in canonical_events:
name = reverse.get(ev)
if not name:
continue
entry = {"command": command}
if fail_closed and name in gates:
if fail_closed and name in gates and name not in allow_silent:
entry["failClosed"] = True
hooks.setdefault(name, []).append(entry)
return {"version": 1, "hooks": hooks}
Expand Down Expand Up @@ -932,7 +933,8 @@ def writes_for(event, gate):
"""What this event puts under judgement: the call's own text, or what the turn left behind."""
if event.event == PRE_TOOL:
return writes_from_event(event)
if (event.raw or {}).get('stop_hook_active'):
raw = event.raw or {}
if raw.get('stop_hook_active') or raw.get('loop_count'):
return {}
return writes_from_worktree(repo_root_for(event, gate))

Expand Down
10 changes: 6 additions & 4 deletions .chock/bin/grok.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by agentseam 0.3.2 -- bundle("grok"). Do not hand-edit, except the
# Generated by agentseam 0.3.3 -- bundle("grok"). Do not hand-edit, except the
# HANDLER block below (between "agentseam handler >>>" and "<<< agentseam handler"),
# which is exactly what this file leaves for you to fill in.
#
Expand All @@ -22,7 +22,7 @@
import warnings as _warnings

# ------------------------------------------------------------------------------
# contract (agentseam 0.3.2)
# contract (agentseam 0.3.3)

"""Canonical event vocabulary, normalized envelope, and decision type."""

Expand Down Expand Up @@ -536,13 +536,14 @@ def _flat_list_wrapper(hook_entry, reverse, canonical_events, command, matcher):

def _cursor_wrapper(cfg, reverse, canonical_events, command, *, fail_closed):
gates = cfg["verdicts"]["answer_events"]
allow_silent = cfg["verdicts"].get("allow_silent_events", ())
hooks = {}
for ev in canonical_events:
name = reverse.get(ev)
if not name:
continue
entry = {"command": command}
if fail_closed and name in gates:
if fail_closed and name in gates and name not in allow_silent:
entry["failClosed"] = True
hooks.setdefault(name, []).append(entry)
return {"version": 1, "hooks": hooks}
Expand Down Expand Up @@ -915,7 +916,8 @@ def writes_for(event, gate):
"""What this event puts under judgement: the call's own text, or what the turn left behind."""
if event.event == PRE_TOOL:
return writes_from_event(event)
if (event.raw or {}).get('stop_hook_active'):
raw = event.raw or {}
if raw.get('stop_hook_active') or raw.get('loop_count'):
return {}
return writes_from_worktree(repo_root_for(event, gate))

Expand Down
Loading
Loading