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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Chock changelog

## Unreleased

- **The plugins page states each client's own answer to a crashed guard.** Every tree's
`PLUGINS.md` said a crashed guard "asks -- on Codex CLI, denies", naming Codex on the Cursor,
Copilot and Devin pages alike. The sentence is now taken from the tested `honours_ask` claim for
the tree's client: an ask where the client prompts (Claude Code, Cursor, VS Code Copilot), a
refusal where it cannot (Codex CLI, Devin), and an untested ask where chock holds no tested claim.

## 0.11.1 — The plugins page tells a gate from a guard

- **The generated plugins page tells a gate from a guard.** `chock marketplace build` wrote one
Expand Down
16 changes: 13 additions & 3 deletions src/chock/plugin/catalog_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

from agentseam import packaging

from chock import vendors
from chock import evidence, vendors
from chock.plugin.marketplace_core import CLAUDE_TREE, NEWLINE, _manifest_rel
from chock.vendors import CHOCK_AGENT

Expand Down Expand Up @@ -96,15 +96,25 @@ def _event_list(events: list[str]) -> str:
return quoted[0] if len(quoted) == 1 else ", ".join(quoted[:-1]) + " and " + quoted[-1]


def _on_crash(tree: str) -> str:
"""What this client does with a crashed guard's ask, per chock's tested claim for it."""
row = evidence.claim(CHOCK_AGENT[tree], evidence.HONOURS_ASK)
if row is None or row.evidence != evidence.TESTED:
return "returns an ask this client has not been tested to honour"
if row.honours:
return "asks for confirmation rather than allowing silently"
return "refuses the command: this client cannot prompt for confirmation"


def _explain(tree: str, guards: int, guard_events: list[str], gates: int, gate_events: list[str]) -> str:
"""What an enforcing package here ships and does, derived from what was published."""
parts: list[str] = []
if guards:
parts.append(
f"A guard package ships a guard script and a stdlib-only adapter, hooked at "
f"{_event_list(guard_events)}, and can deny a shell command before the client runs it. "
"It fails open when `python3` or a usable `bash` is unavailable, and asks -- on Codex "
"CLI, denies -- when the guard crashes."
"It fails open when `python3` or a usable `bash` is unavailable. When the guard itself "
f"crashes, the hook {_on_crash(tree)}."
)
if gates:
judges_write = vendors.pre_tool_event(CHOCK_AGENT[tree]) in gate_events
Expand Down
17 changes: 17 additions & 0 deletions tests/test_catalog_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,20 @@ def test_catalog_page_names_each_client_s_own_events(gate_dist: Path) -> None:
assert "hooked at `beforeShellExecution`" in body
assert "hooked at `preToolUse` and `stop`" in body
assert "`PreToolUse`" not in body


@pytest.mark.parametrize(
("tree", "on_crash"),
[
("claude", "the hook asks for confirmation rather than allowing silently."),
("cursor", "the hook asks for confirmation rather than allowing silently."),
("codex", "the hook refuses the command: this client cannot prompt for confirmation."),
],
)
def test_catalog_page_states_this_client_s_own_crash_answer(gate_dist: Path, tree: str, on_crash: str) -> None:
"""A crashed guard's answer comes from the tested claim for this client, not another's."""
marketplace_main(["build", "--dist", str(gate_dist), "--tree", tree])
body = (gate_dist / CATALOG_PAGE).read_text(encoding="utf-8")

assert f"When the guard itself crashes, {on_crash}" in body
assert "Codex CLI" not in body
Loading