CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 56s
CI & Build / integration (push) Successful in 1m4s
CI & Build / Python tests (push) Successful in 1m55s
CI & Build / Build & push image (push) Successful in 44s
Step 2 of the Scribe -> Grimoire rename. - src/scribe -> src/grimoire, and every import, patch target, module path, hypercorn entry point and `python -m grimoire.restore` with it; pyproject, uv.lock and the frontend package are named grimoire. - Image git.fabledsword.com/bvandeusen/fabledgrimoire; CI's Postgres service runs as grimoire / grimoire_test. - Compose files default the DB user, database and (prod) volume to grimoire, each overridable from .env, so an install created as Scribe keeps its data by setting POSTGRES_USER, POSTGRES_DB and PGDATA_VOLUME. - Backups are written as grimoire-<ts>.tar with format grimoire-backup-tar. Archives named scribe-* with the scribe-backup-tar manifest still restore and are still counted at the destination (folder and S3), with tests. - Exports, the MCP auth realm, the ASGI scope key and the persistent logger carry the new name. Kept on purpose: the secret-sealing key label stays "scribe.secrets.v1", because every stored secret was derived under it. Plugin identity (plugin name, hooks, env vars, .scribe marker, skills) is step 3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
82 lines
3.6 KiB
Python
82 lines
3.6 KiB
Python
"""Every create_* tool says what it is NOT for.
|
|
|
|
WHY THIS EXISTS
|
|
|
|
Grimoire's record kinds are reached for interchangeably — a note written where
|
|
a task was owed, a rule written where a snippet belonged — and the moment of
|
|
choice is the only moment a correction is cheap. The tool docstring IS the
|
|
agent-facing contract (product guidance lives there and nowhere else), so a docstring that only documents parameters answers "how do I call
|
|
this" while leaving "should I be calling this at all" unasked.
|
|
|
|
The gap tends to be lopsided: `create_rule` and `start_planning` are where a
|
|
disambiguator gets written first, while `create_note` — far and away the
|
|
highest-volume surface — is where it matters most. Guidance that sits only in
|
|
the rarest tools misses the most common one.
|
|
|
|
WHAT THIS PINS, AND WHAT IT DOES NOT
|
|
|
|
It asserts STRUCTURE, never wording: each create surface must name at least
|
|
two sibling surfaces, so a caller who reached for the wrong one is told
|
|
where the right one is. Prose stays free to be rewritten — pinning phrasing
|
|
would make every improvement a test failure, and a test that punishes
|
|
editing is a test that gets deleted.
|
|
|
|
It cannot tell whether the guidance is any GOOD. It only catches the
|
|
regression that actually happens: a docstring rewritten down to its
|
|
parameters, with the "is this even the right tool" paragraph quietly gone.
|
|
"""
|
|
import re
|
|
|
|
import pytest
|
|
from tests.helpers import tool_doc as _doc
|
|
|
|
# The create surfaces and where they live. `start_planning` is here because
|
|
# it is a create in everything but name — it is how a plan comes into being.
|
|
_SURFACES = [
|
|
("grimoire.mcp.tools.notes", "create_note"),
|
|
("grimoire.mcp.tools.tasks", "create_task"),
|
|
("grimoire.mcp.tools.tasks", "create_records"),
|
|
("grimoire.mcp.tools.tasks", "start_planning"),
|
|
("grimoire.mcp.tools.snippets", "create_snippet"),
|
|
("grimoire.mcp.tools.processes", "create_process"),
|
|
("grimoire.mcp.tools.rulebooks", "create_rule"),
|
|
("grimoire.mcp.tools.rulebooks", "create_project_rule"),
|
|
]
|
|
|
|
# What a caller could have wanted instead. A surface naming two of these has
|
|
# pointed somewhere; naming none has left them where they were.
|
|
_ALTERNATIVES = [
|
|
"create_note", "create_task", "create_rule", "create_project_rule",
|
|
"create_snippet", "create_process", "start_planning", "design system",
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize(("module", "name"), _SURFACES)
|
|
def test_a_create_surface_names_at_least_two_alternatives(module, name):
|
|
"""Reaching for the wrong tool must still put the right one in view."""
|
|
doc = _doc(module, name)
|
|
named = {
|
|
alt for alt in _ALTERNATIVES
|
|
if alt != name and re.search(re.escape(alt), doc, re.IGNORECASE)
|
|
}
|
|
assert len(named) >= 2, (
|
|
f"{name}'s docstring names {sorted(named) or 'no'} alternative "
|
|
f"surface(s). A caller who reached for it by mistake gets no "
|
|
f"correction. Say what belongs elsewhere and why — see create_rule "
|
|
f"or create_note for the shape."
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(("module", "name"), _SURFACES)
|
|
def test_a_create_surface_still_documents_its_arguments(module, name):
|
|
"""The guard above must not be satisfiable by deleting the parameter docs.
|
|
|
|
Both halves matter and they pull in opposite directions: a docstring can
|
|
be made to pass the disambiguator check by becoming an essay about the
|
|
other tools, which would be a worse contract than the one being fixed.
|
|
"""
|
|
assert "Args:" in _doc(module, name), (
|
|
f"{name}'s docstring lost its Args: block — the parameter contract is "
|
|
f"what a caller reads to make the call at all."
|
|
)
|