feat(500): the reply shapes become server product content - a compact core and per-kind slices, each tied to its moment (#5493)
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / integration (push) Successful in 1m12s
CI & Build / Python tests (push) Failing after 1m29s
CI & Build / Build & push image (push) Skipped

services/reply_shapes.py is the single source of the default reply shapes:
the core (every reply, ~1,800 chars against a 2,200 budget) and three
slices - completion on work.finish, asks on reply.ask, plan on work.plan.
Read through list_reply_shapes (MCP, read-only) and GET
/api/retrieval/reply-shapes, one service behind both doors.

Nothing delivers them yet; that is step 3. The skill still carries its
copy until step 4 shrinks it to the long-form reference.

The software-only vocabulary guard moves into tests/helpers.py
(DEV_ONLY, dev_only_hits) rather than becoming a fourth copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-09 14:29:44 -04:00
co-authored by Claude Opus 5.5
parent acda59fc73
commit a0390d9da7
8 changed files with 393 additions and 13 deletions
+16
View File
@@ -498,6 +498,22 @@ async def rule_row(rule_id: int):
return await s.get(Rule, rule_id)
# Software-only vocabulary. Scribe's product text speaks for any work a person
# drives through an agent — configuration, infrastructure, documents — so a
# catalog or a shipped shape that reads as software-only is wrong on most of
# the installs it ships to. The actions particular to one kind of work belong
# in data (mappings, rulebooks, preferences), not in the product's words.
DEV_ONLY = (r"\bCI\b", r"\bcommit", r"\bpull request", r"\bcode\b", r"\btest",
r"\bgit\b", r"\brepo(s|sitor\w*)?\b", r"\bbranch", r"\bcompil", r"\bbuild\b")
def dev_only_hits(text: str) -> list[str]:
"""The DEV_ONLY patterns this text contains, case-insensitively."""
import re
return [w for w in DEV_ONLY if re.search(w, text, re.IGNORECASE)]
def skill_text(name: str) -> str:
"""Everything a bundled skill states: its SKILL.md, then each reference file.