rename(547): the plugin is grimoire — manifest, MCP key, hooks, env, skills
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 1m2s
CI & Build / Python tests (push) Failing after 1m26s
CI & Build / Build & push image (push) Skipped
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 1m2s
CI & Build / Python tests (push) Failing after 1m26s
CI & Build / Build & push image (push) Skipped
Step 3 of the Scribe -> Grimoire rename. - Plugin `grimoire` in marketplace `grimoire-plugin`, MCP server key `grimoire`, so tools reach a session as mcp__plugin_grimoire_grimoire__*. - hooks/scribe_* -> hooks/grimoire_*, their shell functions and the TMPDIR ledgers with them; SCRIBE_URL / SCRIBE_TOKEN / SCRIBE_HOOK_DIR / SCRIBE_LEDGER_DIRS -> GRIMOIRE_*; the project marker is `.grimoire`. - skills/using-scribe -> using-grimoire, /grimoire:sync, stored processes arrive as grimoire-proc-<slug>; moment_actions names them the same way. - Migration 0124 rewrites stored skill names (rule_moments, rule_moment_judgments, moment_mappings) from scribe:/scribe-proc- to grimoire:/grimoire-proc-, so a rule mounted on a skill keeps firing. - Settings' install commands and MCP-only defaults, the docs and the tests follow. - scripts/check_plugin.py drops the smoke event for a report-check hook that has no file in plugin/hooks. Kept on purpose: the secret-sealing label, and the names that let backups written as Scribe restore and be counted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -34,7 +34,7 @@ from tests.helpers import fake_rule, http_sink, need_tools
|
||||
_REAL_DELIVER = md.deliver_for_act
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
HOOK = ROOT / "plugin" / "hooks" / "scribe_moment.sh"
|
||||
HOOK = ROOT / "plugin" / "hooks" / "grimoire_moment.sh"
|
||||
|
||||
|
||||
def _rule(rid, title="Done means delivered and checked"):
|
||||
@@ -269,20 +269,20 @@ def test_the_hook_is_registered_on_every_tool():
|
||||
manifest = json.loads((ROOT / "plugin" / "hooks" / "hooks.json").read_text())
|
||||
entries = {m.get("matcher"): [h["command"] for h in m["hooks"]]
|
||||
for m in manifest["hooks"]["PreToolUse"]}
|
||||
assert any("scribe_moment.sh" in c for c in entries.get("*", []))
|
||||
assert any("grimoire_moment.sh" in c for c in entries.get("*", []))
|
||||
|
||||
|
||||
def test_the_hook_and_the_routes_agree_on_every_name():
|
||||
"""Parity across the shell/Python seam: a renamed arg fails silently."""
|
||||
src = HOOK.read_text()
|
||||
assert "/api/plugin/moment-tools" in src and "/api/plugin/moment" in src
|
||||
assert "exclude_rule_ids" in src and "scribe_held_query" in src
|
||||
assert "scribe_rules_live" in src and "scribe_rules_append" in src
|
||||
assert "exclude_rule_ids" in src and "grimoire_held_query" in src
|
||||
assert "grimoire_rules_live" in src and "grimoire_rules_append" in src
|
||||
# The SHARED ledger, not one of its own.
|
||||
assert '"${TMPDIR:-/tmp}/scribe-priorart"' in src and ".rules.ids" in src
|
||||
assert '"${TMPDIR:-/tmp}/grimoire-priorart"' in src and ".rules.ids" in src
|
||||
for field in (".tools", ".rule_ids", ".context", ".shape_keys"):
|
||||
assert f"'{field}'" in src
|
||||
assert "shapes_seen" in src and "scribe_shapes_append" in src
|
||||
assert "shapes_seen" in src and "grimoire_shapes_append" in src
|
||||
|
||||
from grimoire.routes import plugin as routes
|
||||
|
||||
@@ -293,19 +293,19 @@ def test_the_hook_and_the_routes_agree_on_every_name():
|
||||
|
||||
|
||||
def test_the_hook_leaves_scribes_own_tools_to_their_responses():
|
||||
assert "mcp__*scribe*__*) exit 0" in HOOK.read_text()
|
||||
assert "mcp__*grimoire*__*) exit 0" in HOOK.read_text()
|
||||
|
||||
|
||||
def test_the_hook_never_returns_a_permission_decision():
|
||||
"""A mount is a recall aid: it informs the act and never holds it."""
|
||||
code = [ln for ln in HOOK.read_text().splitlines() if not ln.lstrip().startswith("#")]
|
||||
assert not any("permissionDecision" in ln or "scribe_json_deny" in ln for ln in code)
|
||||
assert not any("permissionDecision" in ln or "grimoire_json_deny" in ln for ln in code)
|
||||
|
||||
|
||||
def _hook(tmp_path, port, tool="Bash", command="git push origin dev", session="s-moment"):
|
||||
need_tools("bash", "curl", "awk")
|
||||
env = {"PATH": os.environ["PATH"], "SCRIBE_URL": f"http://127.0.0.1:{port}",
|
||||
"SCRIBE_TOKEN": "t", "TMPDIR": str(tmp_path), "HOME": str(tmp_path)}
|
||||
env = {"PATH": os.environ["PATH"], "GRIMOIRE_URL": f"http://127.0.0.1:{port}",
|
||||
"GRIMOIRE_TOKEN": "t", "TMPDIR": str(tmp_path), "HOME": str(tmp_path)}
|
||||
out = subprocess.run(
|
||||
["bash", str(HOOK)],
|
||||
input=json.dumps({"session_id": session, "cwd": str(tmp_path), "tool_name": tool,
|
||||
@@ -341,7 +341,7 @@ def test_a_listed_tool_is_asked_about_and_its_rules_injected(tmp_path):
|
||||
def test_an_unlisted_tool_never_leaves_the_machine(tmp_path):
|
||||
with http_sink(by_path=TOOLS) as (port, seen):
|
||||
assert _hook(tmp_path, port, tool="Read") == ""
|
||||
assert _hook(tmp_path, port, tool="mcp__plugin_scribe_scribe__update_task") == ""
|
||||
assert _hook(tmp_path, port, tool="mcp__plugin_grimoire_grimoire__update_task") == ""
|
||||
assert [e["_path"] for e in seen] == ["/api/plugin/moment-tools"]
|
||||
|
||||
|
||||
@@ -371,16 +371,16 @@ def _attached_names(path: Path) -> dict[str, set[str]]:
|
||||
return out
|
||||
|
||||
|
||||
def test_every_scribe_tool_the_defaults_name_attaches_its_moment_rules():
|
||||
def test_every_grimoire_tool_the_defaults_name_attaches_its_moment_rules():
|
||||
tools_dir = ROOT / "src" / "grimoire" / "mcp" / "tools"
|
||||
defined: dict[str, dict[str, set[str]]] = {}
|
||||
scribe_tools: set[str] = set()
|
||||
grimoire_tools: set[str] = set()
|
||||
for path in tools_dir.glob("*.py"):
|
||||
tree = ast.parse(path.read_text())
|
||||
scribe_tools |= {n.name for n in tree.body if isinstance(n, ast.AsyncFunctionDef)}
|
||||
grimoire_tools |= {n.name for n in tree.body if isinstance(n, ast.AsyncFunctionDef)}
|
||||
defined[path.name] = _attached_names(path)
|
||||
|
||||
shipped = {a.tool for a in moment_actions.DEFAULT_ACTIONS} & scribe_tools
|
||||
shipped = {a.tool for a in moment_actions.DEFAULT_ACTIONS} & grimoire_tools
|
||||
assert {"update_task", "create_rule", "update_milestone"} <= shipped
|
||||
for tool in sorted(shipped):
|
||||
named = set().union(*(per_file.get(tool, set()) for per_file in defined.values()))
|
||||
|
||||
Reference in New Issue
Block a user