Files
FabledGrimoire/tests/test_session_context_failure.py
T
bvandeusenandClaude Opus 5.5 c5eeec471f
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
rename(547): the plugin is grimoire — manifest, MCP key, hooks, env, skills
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>
2026-10-22 10:52:55 -04:00

55 lines
2.1 KiB
Python

"""A SessionStart that loads no project says why, and names the first move.
A fetch written as `curl -f … || body=""` collapses a timeout, an HTTP error
and a refused key into one sentence, and a fallback that suggests
`enter_project()` "as needed" reads as optional. A session that skips it starts
with no recent milestones or open tasks, and so has no way to know which prior
work exists to look for. These pin both halves on the path that needs no
server: the cause is named, and the fallback is a concrete first step.
"""
from __future__ import annotations
import json
import os
import subprocess
from pathlib import Path
from tests.helpers import need_tools
HOOK = Path(__file__).resolve().parents[1] / "plugin" / "hooks" / "grimoire_session_context.sh"
def run_hook(tmp_path: Path, url: str, marker_project: int | None = None) -> str:
need_tools("bash", "curl", "awk")
if marker_project is not None:
(tmp_path / ".grimoire").write_text(json.dumps({
"instance": url, "project_id": marker_project, "project": "P",
}))
env = {**os.environ, "GRIMOIRE_URL": url, "GRIMOIRE_TOKEN": "t",
"CLAUDE_PROJECT_DIR": str(tmp_path)}
r = subprocess.run(["bash", str(HOOK)], input=b'{"source":"startup"}',
capture_output=True, env=env, timeout=60)
assert r.returncode == 0, r.stderr.decode()
return json.loads(r.stdout)["hookSpecificOutput"]["additionalContext"]
# Port 9 (discard) on loopback: refused at once, so both tries fail fast.
DEAD = "http://127.0.0.1:9"
def test_an_unreachable_instance_is_named_as_one(tmp_path):
out = run_hook(tmp_path, DEAD)
assert "could not be reached (curl exit 7, after a retry)" in out
def test_the_fallback_is_a_first_step_not_an_option(tmp_path):
out = run_hook(tmp_path, DEAD)
assert "as needed" not in out
assert "Start by finding this repo's project" in out
assert "before any other work" in out
def test_a_marker_names_the_exact_call(tmp_path):
out = run_hook(tmp_path, DEAD, marker_project=31)
assert "Start by calling `enter_project(31)`" in out