forked from jason/echo
Fix SKILL.md description over marketplace 1024-char cap + build guard
The plugin upload validates each skills/**/SKILL.md `description` frontmatter against a 1024-char cap (distinct from plugin.json's <500 rule). The skill description was 1171 chars and got rejected on upload. - Trim the chorus-memory SKILL.md description to 1000 chars, preserving the save/recall/group triggers and the full "Do NOT" exclusion list. - Add MAX_SKILL_DESCRIPTION=1024 guard to build.py: scans every SKILL.md and fails the build with an over-by-N message so this can't silently regress. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -50,6 +50,8 @@ EXCLUDE_SUFFIXES = {".pyc", ".pyo"}
|
|||||||
FIXED_DATE = (2026, 1, 1, 0, 0, 0) # stable timestamp for reproducible archives
|
FIXED_DATE = (2026, 1, 1, 0, 0, 0) # stable timestamp for reproducible archives
|
||||||
MAX_DESCRIPTION = 500 # plugin-marketplace cap: plugin.json "description" must be UNDER this
|
MAX_DESCRIPTION = 500 # plugin-marketplace cap: plugin.json "description" must be UNDER this
|
||||||
# (it has silently regressed past the limit before — fail the build now)
|
# (it has silently regressed past the limit before — fail the build now)
|
||||||
|
MAX_SKILL_DESCRIPTION = 1024 # marketplace cap: each SKILL.md frontmatter "description" must be
|
||||||
|
# AT MOST this many chars (upload rejects it otherwise — fail here first)
|
||||||
|
|
||||||
# field name -> the constant assigned in chorus_config.py
|
# field name -> the constant assigned in chorus_config.py
|
||||||
_CONSTS = {"group": "DEFAULT_GROUP", "member": "DEFAULT_MEMBER",
|
_CONSTS = {"group": "DEFAULT_GROUP", "member": "DEFAULT_MEMBER",
|
||||||
@@ -90,6 +92,32 @@ def file_bytes(path: Path, arcname: str, bake: dict | None, strip_key: bool) ->
|
|||||||
return text.encode("utf-8")
|
return text.encode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def skill_description_violations() -> list[str]:
|
||||||
|
"""Return a message for every skills/**/SKILL.md whose frontmatter `description`
|
||||||
|
exceeds MAX_SKILL_DESCRIPTION. The marketplace validates this at upload time and
|
||||||
|
rejects the whole plugin, so catch it at build time instead."""
|
||||||
|
problems = []
|
||||||
|
for skill_md in sorted((SRC / "skills").rglob("SKILL.md")):
|
||||||
|
text = skill_md.read_text(encoding="utf-8")
|
||||||
|
fm = re.match(r"^---\n(.*?)\n---", text, re.S)
|
||||||
|
if not fm:
|
||||||
|
continue
|
||||||
|
# description is a single-line scalar: capture from `description:` to the next
|
||||||
|
# top-level `key:` line (or end of frontmatter).
|
||||||
|
dm = re.search(r"^description:[ \t]*(.*?)(?=\n[A-Za-z_][\w-]*:[ \t]|\Z)",
|
||||||
|
fm.group(1), re.S | re.M)
|
||||||
|
if not dm:
|
||||||
|
continue
|
||||||
|
desc = dm.group(1).strip()
|
||||||
|
if len(desc) > 2 and desc[0] in "\"'" and desc[-1] == desc[0]:
|
||||||
|
desc = desc[1:-1]
|
||||||
|
if len(desc) > MAX_SKILL_DESCRIPTION:
|
||||||
|
rel = skill_md.relative_to(SRC).as_posix()
|
||||||
|
problems.append(f"{rel}: description is {len(desc)} chars — must be at most "
|
||||||
|
f"{MAX_SKILL_DESCRIPTION} (over by {len(desc) - MAX_SKILL_DESCRIPTION}).")
|
||||||
|
return problems
|
||||||
|
|
||||||
|
|
||||||
def token_present(path: Path) -> bool:
|
def token_present(path: Path) -> bool:
|
||||||
"""True if any DEFAULT_* constant in chorus_config.py holds a non-empty value."""
|
"""True if any DEFAULT_* constant in chorus_config.py holds a non-empty value."""
|
||||||
text = path.read_text(encoding="utf-8")
|
text = path.read_text(encoding="utf-8")
|
||||||
@@ -190,6 +218,11 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
f"{MAX_DESCRIPTION} (marketplace cap). Trim \"description\" in {MANIFEST}.",
|
f"{MAX_DESCRIPTION} (marketplace cap). Trim \"description\" in {MANIFEST}.",
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
skill_problems = skill_description_violations()
|
||||||
|
for problem in skill_problems:
|
||||||
|
print(f"build: {problem} Trim the SKILL.md \"description\" frontmatter.", file=sys.stderr)
|
||||||
|
if skill_problems:
|
||||||
|
return 1
|
||||||
files = included_files()
|
files = included_files()
|
||||||
arcnames = {p.relative_to(SRC).as_posix() for p in files}
|
arcnames = {p.relative_to(SRC).as_posix() for p in files}
|
||||||
if MANIFEST not in arcnames:
|
if MANIFEST not in arcnames:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: chorus-memory
|
name: chorus-memory
|
||||||
description: Use the shared CHORUS Obsidian vault as the group's persistent memory across Claude/CoWork sessions — one vault, many members, every write attributed to the configured member. Use whenever the member asks to remember, save, note, log, or capture anything durable — facts, preferences, decisions, schedule changes, commitments — or asks what Claude knows about them or the team, what was discussed or decided before (by anyone in the group), to check their notes, load their memory/profile/context, add to their or the group's inbox, to see what other members have been working on, or to track a new project. Trigger even without memory phrasing when the request implies recalling or persisting state across sessions ("pick up where we left off", "anything on X before my meeting?", "did anyone already look into this?"). Also use proactively at the start of substantive work sessions to load context and at the end to log outcomes. Do NOT use for vaults outside this group, Obsidian/REST-API development questions, "memory" meaning RAM, timed reminders, email or local-file lookups, generic second-brain advice, or notes the member routes to a specific other file or app.
|
description: Use the shared CHORUS Obsidian vault as the group's persistent memory across Claude/CoWork sessions — one vault, many members, every write attributed to its member. Use whenever a member asks to remember, save, note, log, or capture anything durable — facts, preferences, decisions, commitments — or asks what Claude knows about them or the team, what was discussed or decided before, to check notes, load memory/profile/context, add to an inbox, or see what other members are working on. Trigger even without memory phrasing when the request implies recalling or persisting state across sessions ("pick up where we left off", "did anyone already look into this?"). Also use proactively at the start of a session to load context and at the end to log outcomes. Do NOT use for vaults outside this group, Obsidian/REST-API development questions, "memory" meaning RAM, timed reminders, email or local-file lookups, generic second-brain advice, or notes the member routes to a specific other file or app.
|
||||||
---
|
---
|
||||||
|
|
||||||
# CHORUS Memory
|
# CHORUS Memory
|
||||||
|
|||||||
Reference in New Issue
Block a user