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>
108 lines
4.8 KiB
Python
108 lines
4.8 KiB
Python
"""The write surfaces say WHEN a note earns a check — not just that it can.
|
|
|
|
WHY THIS EXISTS
|
|
|
|
`verify_with` is a free-text field on the highest-volume record kind in the
|
|
product. A field described only as "how to verify this note" gets filled in on
|
|
every note within a week, and at that point the sweep returns the whole corpus
|
|
and means nothing. The signal is not "has a check" — it is "has a check AND
|
|
almost nothing else does".
|
|
|
|
So the guidance is not decoration on this feature, it IS the feature's
|
|
precondition, and product guidance lives in the tool docstrings and the skill
|
|
rather than in a Grimoire rule. That makes it exactly the kind of prose a later
|
|
docstring tidy-up deletes without noticing what it was for.
|
|
|
|
WHAT THIS PINS, AND WHAT IT DOES NOT
|
|
|
|
Structure, never wording, for `test_create_tools_disambiguate`'s reason: a test
|
|
that punishes rewriting is a test that gets deleted. Each write surface must
|
|
still (a) draw the norm-vs-constraint distinction in some form, (b) say the
|
|
empty case is normal, and (c) name where NOT to reach for it.
|
|
|
|
It cannot tell whether the guidance is any good — only that the paragraph
|
|
explaining when to leave the field alone has not quietly become a parameter
|
|
list.
|
|
"""
|
|
import re
|
|
|
|
import pytest
|
|
from tests.helpers import tool_doc as _doc
|
|
|
|
# The surfaces that OFFER the field. The read surfaces (the sweep, the stamp)
|
|
# explain what a RESULT means, which is a different job — they are deliberately
|
|
# not held to this.
|
|
_WRITE_SURFACES = [
|
|
("grimoire.mcp.tools.notes", "create_note"),
|
|
("grimoire.mcp.tools.notes", "update_note"),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("module,name", _WRITE_SURFACES)
|
|
def test_the_norm_versus_constraint_distinction_is_stated(module, name):
|
|
"""The whole discipline in one line: a decision cannot go stale, a claim
|
|
about someone else's software can. Without it, "how would you check this"
|
|
reads as a chore to complete rather than a question with a usual answer of
|
|
"you wouldn't"."""
|
|
doc = _doc(module, name).lower()
|
|
assert "norm" in doc and "constraint" in doc, (
|
|
f"{name} no longer draws the norm-vs-constraint distinction. Without "
|
|
f"it the field is just a box, and a box gets filled in."
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("module,name", _WRITE_SURFACES)
|
|
def test_the_empty_case_is_stated_as_normal(module, name):
|
|
"""Said POSITIVELY, or an empty field reads as an unfinished record. This
|
|
is the single sentence standing between the sweep and irrelevance."""
|
|
doc = _doc(module, name).lower()
|
|
assert re.search(r"leave (it|both|them|this|these)? ?empty|empty for", doc), (
|
|
f"{name} no longer says that leaving the check empty is the normal "
|
|
f"case. Most notes are decisions; the field's default must read as a "
|
|
f"deliberate state, not a gap."
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("module,name", _WRITE_SURFACES)
|
|
def test_the_wrong_places_to_reach_for_it_are_named(module, name):
|
|
"""A task's decay is its status; a snippet has verify_snippet. The service
|
|
refuses both — this is what should mean nobody ever hits that error."""
|
|
doc = _doc(module, name).lower()
|
|
assert "task" in doc and "snippet" in doc, (
|
|
f"{name} no longer names the records that must NOT carry a check. The "
|
|
f"gate still refuses them, but a refusal the caller could have "
|
|
f"foreseen is a worse door than one that explained itself."
|
|
)
|
|
|
|
|
|
def test_expires_when_is_described_as_a_state_not_a_date():
|
|
"""The one field whose obvious reading is wrong. A date invents a staleness
|
|
schedule nobody can justify; a constraint expires when the ground moves,
|
|
which is a condition and not a time."""
|
|
doc = _doc("grimoire.mcp.tools.notes", "create_note")
|
|
assert "STATE" in doc, (
|
|
"create_note no longer says expires_when is a STATE. Left to itself, "
|
|
'"expires" reads as a date, and every check would get an arbitrary one.'
|
|
)
|
|
|
|
|
|
def test_the_skill_carries_the_test_a_writer_can_actually_apply():
|
|
"""The docstrings are read by whatever is holding the tool; the skill is
|
|
read while deciding what to write. The one-question form has to be in the
|
|
second place too, or the guidance only reaches callers who already opened
|
|
the tool."""
|
|
from tests.helpers import skill_text
|
|
|
|
# Reference files included: the full statement sits in writing-records.md
|
|
# and SKILL.md keeps the pointer.
|
|
text = " ".join(skill_text("using-grimoire").split())
|
|
assert "could this note become false without anyone editing it" in text.lower(), (
|
|
"the using-grimoire skill no longer carries the one-question test. That "
|
|
"question is what makes the distinction applicable rather than merely "
|
|
"true."
|
|
)
|
|
assert "notes_due_for_verification" in text, (
|
|
"the skill names the fields but not the surface that reads them — "
|
|
"guidance for writing a check with no route to acting on one."
|
|
)
|