diff --git a/build.py b/build.py index bf09211..14a3f0a 100644 --- a/build.py +++ b/build.py @@ -50,6 +50,8 @@ EXCLUDE_SUFFIXES = {".pyc", ".pyo"} 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 # (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 _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") +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: """True if any DEFAULT_* constant in chorus_config.py holds a non-empty value.""" 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}.", file=sys.stderr) 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() arcnames = {p.relative_to(SRC).as_posix() for p in files} if MANIFEST not in arcnames: diff --git a/chorus-memory.plugin.src/skills/chorus-memory/SKILL.md b/chorus-memory.plugin.src/skills/chorus-memory/SKILL.md index 073e465..77a30fe 100644 --- a/chorus-memory.plugin.src/skills/chorus-memory/SKILL.md +++ b/chorus-memory.plugin.src/skills/chorus-memory/SKILL.md @@ -1,6 +1,6 @@ --- 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