feat(410): the skills own the full reflexes, in words any client can read (#4029)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / integration (push) Successful in 53s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / Python tests (push) Successful in 1m34s
CI & Build / Build & push image (push) Successful in 16s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / integration (push) Successful in 53s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / Python tests (push) Successful in 1m34s
CI & Build / Build & push image (push) Successful in 16s
Step 2 of milestone 410 "One owner per piece of guidance". Skills are the part of every client package shared verbatim (Agent Skills, decision #4027), so they state each reflex in full and name no particular client. using-scribe gains what only the static session context said: - a retrieved rule outranks a default habit; ask when no rule speaks to it - log on completing a task and on hitting a problem, not only successes - the systems_hint on an untagged record is the tagging question, answered at the moment of work Client-specific text leaves the skills, rewritten as the universal idea: - using-scribe: "keep one copy" no longer names CLAUDE.md, MEMORY.md, native auto-memory or autoMemoryEnabled; "this plugin" becomes Scribe - reusing-code / shape-accounting: Write/Edit and Bash become editor tools and shell edits; the prior-art "hook" becomes the prior-art hint; a plugin version number is dropped tests/test_guidance_ownership.py: - test_the_skills_name_no_particular_client fails on any Claude Code path, memory file, slash command, hook event or tool name in a skill, each marker commented with why it is client-specific; a companion test shows it can fail - three registry topics for what using-scribe now owns; the loss guard stays green Plugin version minted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -112,6 +112,9 @@ TOPICS: tuple[Topic, ...] = (
|
||||
Topic("an id exists only once a create returns it", "skill:using-scribe",
|
||||
("exists only once a create", "{{ref:")),
|
||||
Topic("tag records to systems as you write", "skill:using-scribe", ("system_ids", "create_system")),
|
||||
Topic("answer the systems_hint at the moment of work", "skill:using-scribe", ("systems_hint",)),
|
||||
Topic("a retrieved rule outranks a default habit", "skill:using-scribe", ("outranks a default habit",)),
|
||||
Topic("log on completion and on a problem", "skill:using-scribe", ("hit or discover a problem",)),
|
||||
Topic("the project's design system binds ui", "skill:using-scribe", ("resolve_design_system",)),
|
||||
Topic("name the record, never just its number", "skill:using-scribe", ("name the record",)),
|
||||
Topic("project inception is a decision", "skill:using-scribe", ("decide_project_inception",)),
|
||||
@@ -196,3 +199,55 @@ def test_the_loss_guard_can_fail():
|
||||
reported = missing_topics((split, absent, whole), surfaces)
|
||||
assert len(reported) == 2
|
||||
assert reported[0].startswith("'split'") and "nowhere" in reported[1]
|
||||
|
||||
|
||||
# ── The skills are client-neutral (milestone 410 step 2) ────────────────
|
||||
#
|
||||
# Agent Skills is an open format read by dozens of clients (#4023), so the
|
||||
# skills folder is the part of every client package that is shared verbatim.
|
||||
# Anything only one client understands belongs in that client's adapter —
|
||||
# for Claude Code, the plugin's static context, hooks and commands — or the
|
||||
# skill reads as nonsense everywhere else. Each marker below names a thing
|
||||
# exactly one client has:
|
||||
# - "claude", "~/.claude", ".claude/" the client itself and its paths
|
||||
# - "claude.md", "memory.md", "auto-memory", "automemoryenabled"
|
||||
# Claude Code's local memory files/setting
|
||||
# - "/compact", "/scribe:" Claude Code slash commands
|
||||
# - "sessionstart", "userpromptsubmit", "pretooluse", "posttooluse"
|
||||
# Claude Code hook event names
|
||||
# - "write/edit" Claude Code's editor tool names
|
||||
# - "claude_plugin_root" the Claude Code plugin root variable
|
||||
# `Bash` is matched case-sensitively as a word: it is Claude Code's shell tool
|
||||
# name, while "bash" in prose is just the shell.
|
||||
CLIENT_SPECIFIC = (
|
||||
"claude", "~/.claude", ".claude/", "claude.md", "memory.md", "auto-memory",
|
||||
"automemoryenabled", "/compact", "/scribe:", "sessionstart", "userpromptsubmit",
|
||||
"pretooluse", "posttooluse", "write/edit", "claude_plugin_root",
|
||||
)
|
||||
CLIENT_TOOL_NAME = re.compile(r"\bBash\b")
|
||||
|
||||
|
||||
def client_specific_hits(text: str) -> list[str]:
|
||||
hits = [m for m in CLIENT_SPECIFIC if m in text.lower()]
|
||||
if CLIENT_TOOL_NAME.search(text):
|
||||
hits.append("Bash")
|
||||
return hits
|
||||
|
||||
|
||||
def test_the_skills_name_no_particular_client():
|
||||
offenders = {
|
||||
str(p.relative_to(ROOT)): client_specific_hits(p.read_text())
|
||||
for p in sorted((ROOT / "plugin/skills").glob("*/SKILL.md"))
|
||||
}
|
||||
offenders = {path: hits for path, hits in offenders.items() if hits}
|
||||
assert not offenders, (
|
||||
f"skills that name one client: {offenders}. Skills are shared by every "
|
||||
f"client package (decision #4027); say the universal thing in the skill "
|
||||
f"and put the client's own name for it in that client's adapter."
|
||||
)
|
||||
|
||||
|
||||
def test_the_client_guard_can_fail():
|
||||
assert client_specific_hits("keep a copy in CLAUDE.md") == ["claude", "claude.md"]
|
||||
assert client_specific_hits("edits made through Bash") == ["Bash"]
|
||||
assert client_specific_hits("edits made through a bash shell") == []
|
||||
|
||||
Reference in New Issue
Block a user