mirror of
https://github.com/recoupable/skills.git
synced 2026-09-14 20:26:58 +08:00
aaf9c06c42
* feat: consolidate skills + plugins into single recoupable/skills repo
Unify the open skills library, rich plugins, and the marketplace registry
into one repo to kill the drift between recoupable/marketplace (live) and
recoupable/skills (stale), and the Claude/Codex registry mismatches.
Structure:
- skills/ generic, install-anywhere library skills
- plugins/{research,catalog,content}/ rich plugins (commands, agents, evals, refs)
- .claude-plugin/marketplace.json + .agents/plugins/marketplace.json
unified registries listing the library (source ".") + 3 plugins
Content reconciliation (single-source the duplicated short-video):
- Promote the newer short-video to the library and bundle its references
(short-video-manual.md, song-sourcing.md) inside skills/short-video/
so the library skill is self-contained.
- Remove the duplicated short-video + content-creation from plugins/content.
- Rewrite recoup-content-create to inline the async pipeline and reference
library skills (short-video, content-creation, create-artist) by name,
decoupling it from in-tree relative paths.
- Refresh plugins/content README + legacy command to drop dead in-tree refs.
Validated e2e: all manifest sources resolve, every SKILL.md has valid
frontmatter, no duplicate skill names (42 distinct), JSON parses clean.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat: make all 42 skills cross-harness portable (self-contained)
Reject the Claude-only ${CLAUDE_PLUGIN_ROOT} pattern (verified: doesn't
expand in markdown bodies, runtime CWD is the user project) in favor of
the compound-engineering self-containment model so every skill runs on
any harness (Claude Code, Codex, Cursor, bare npx skills), not just as a
Claude marketplace plugin.
- Portable Skill Contract added to AGENTS.md + contributing.md
- scripts/: portability_lint, check_vendored (file + recursive-dir drift),
validate_manifests (+ dual-manifest parity), vendored.json (27 groups)
- research (10): vendor the used shared refs per skill; md-links -> backtick
- catalog (17): vendor plugin-root refs + deal-workspace template +
demo-data-room fixtures + full 17-script set into 8 script-bearing skills;
kill cross-skill dep (royalty-audit<-catalog-analysis); rephrase prose
- library (14) + content (1): fix mir links, de-path cross-skill prose,
replace dead content-strategy.md ref with inline guidance
- ci: .github/workflows/validate.yml runs lint + drift + manifests
Verified: lint 42/42 portable, drift 174 copies/27 groups, manifests
13 files + parity OK. Vendored catalog scripts execute from skill dir.
Co-authored-by: Cursor <cursoragent@cursor.com>
* refactor: vendor catalog scripts as per-skill dependency closures
The first portability pass copied the full 17-script catalog toolkit into
all 8 script-bearing skills (136 copies). Most skills invoke far fewer:
dashboard 1, kickoff 1, report 2. Replace the superset with each skill's
true closure — scripts named in SKILL.md plus their transitive deps:
run-deal-checks's 5 subprocess validators, auto-column-map's importlib
normalize target, and the shared _helpers import.
Drops 80 dead copies (174 -> 94 tracked). Verified: lint 42/42, drift
94/27 green, and the dynamic subprocess/importlib chains execute from
each tightened skill dir.
Co-authored-by: Cursor <cursoragent@cursor.com>
* refactor(deals): fold 14 deal skills into 6 workflow stages
Collapse the deal skills into start / ingest / analysis / dashboard /
report / demo so the slash menu reads as a natural workflow. Most users
only run the orchestrator, so granular skills became modes, params, and
bundled reference playbooks. Nothing dropped.
- start: absorbs workspace-setup + carries deal-type modes
(buy-side / seller-prep / financing / post-close)
- analysis: absorbs rights-reviewer, royalties-auditor,
financing-underwriter, seller-prep, post-close-admin as references/*.md
- dashboard: absorbs quality-checker pre-share QC gate
- report: absorbs memo-maker (memo assembly + packages)
- vendored.json slug-remapped + resynced; hooks.json Gate B fixed;
plugin README rewritten to the true 6
Gates green: portability_lint (31 portable), check_vendored (85 copies /
25 groups), validate_manifests (13 files + parity). Repo-wide sweep:
zero lingering old deal slugs.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: address cubic review findings on deal validators + docs
Resolves 9 review comments from cubic-dev-ai on PR #33.
P1:
- protect-source-files.sh: also match relative `deals/*/source/*` paths
(Write/Edit tools pass repo-relative paths; the leading-segment glob alone
missed them, leaving immutable source files writable).
- validate-evidence-ledger.py: guard non-dict top-level JSON (array/primitive
no longer crashes with AttributeError; emits a clean error).
- deal-workspace.md: workpapers list now `nps-bridge.json` + `nls-bridge.json`
(two files, matching the dashboard validator) instead of a combined file.
P2:
- validate-deal-workspace.py: catch OSError/UnicodeDecodeError, not just
JSONDecodeError, so unreadable/non-UTF8 files report cleanly.
- validate-normalized-ledger.py: report empty `ledger_line_id` values as a
data-integrity error instead of silently accepting them.
- validate-findings-evidence.py: handle malformed JSON in both loaders;
emit a structured `invalid_findings_file` result instead of a traceback.
- content plugin manifests: author email -> agent@recoupable.com to match
marketplace.json + branding.
- deal-workspace.md: `valuation-scenarios.md` -> `valuation-summary.json`
(matches the workspace + dashboard validators).
P3:
- AGENTS.md: align Skill Format (templates/, fixtures/) with the Portable
Contract; drop the contradictory assets/ reference.
Vendored copies resynced (20). Gates green: portability 31, vendored 85, manifests 13.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: rename root plugin recoupable-skills -> recoup-skills
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: drop -plugin suffix from plugin names (recoup-deals/research/content)
Co-authored-by: Cursor <cursoragent@cursor.com>
* docs: rebrand root library to "Recoup Skills" + add CLAUDE.md symlink
- README.md / AGENTS.md titles: "Recoupable Skills" -> "Recoup Skills"
- displayName fields (marketplace + codex manifest) -> "Recoup Skills"
- README skill-dir tree: assets/ -> templates/ + fixtures/ (matches AGENTS.md contract)
- CLAUDE.md -> AGENTS.md symlink so Claude Code reads the same instructions
Co-authored-by: Cursor <cursoragent@cursor.com>
* docs: add Plugins section to README; drop ' Plugin' from research/content titles
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat: add recoup-song-* skills for Music Flamingo audio analysis
Add a P0 router skill (recoup-song-analyzer) plus five focused P1
workflow skills built on the Music Flamingo /api/songs/analyze endpoint:
- recoup-song-analyzer: entry point — resolves audio URL, picks preset,
routes to focused workflows
- recoup-song-metadata-tagger: structured catalog metadata
- recoup-song-lyrics-transcriber: sectioned lyric sheet (draft)
- recoup-song-playlist-pitch-maker: DSP/editorial pitch + positioning
- recoup-song-sync-brief-maker: supervisor sync brief + clearance risk
- recoup-song-mix-feedback-reviewer: technical mix critique
Canonical endpoint reference lives in recoup-song-analyzer and is
vendored byte-identical into the other five (registered in
scripts/vendored.json). README skill table updated. All three gates
pass (portability, vendor drift, manifests).
Co-authored-by: Cursor <cursoragent@cursor.com>
* refactor: promote recoup-song-* skills into recoup-songs plugin
Convert the six loose recoup-song-* skills into a cohesive recoup-songs
plugin, matching the structure of recoup-deals/research/content.
- git mv skills/recoup-song-* -> plugins/songs/skills/ (history preserved)
- promote canonical flamingo-api.md to plugins/songs/references/ and vendor
it byte-identical into all 6 skills (analyzer included) — fixes the prior
smell where recoup-song-analyzer owned the reference the others depended on
- add plugin manifests (.claude-plugin + .cursor-plugin), README, LICENSE
- register recoup-songs in both marketplace files (.claude-plugin + .agents)
- root README: drop the analyzer skills-table row, add Recoup Songs to the
Plugins table
All 3 gates pass: portability (37), vendor drift (91 copies / 26 groups),
manifests (15 files + dual-manifest parity).
Co-authored-by: Cursor <cursoragent@cursor.com>
* refactor: shorten recoup-song-* P1 skill names (drop action suffixes)
Drop the action-noun suffix from the five P1 skills so the names read as
the thing they produce, not the actor:
- recoup-song-metadata-tagger -> recoup-song-metadata
- recoup-song-lyrics-transcriber -> recoup-song-lyrics
- recoup-song-playlist-pitch-maker -> recoup-song-playlist-pitch
- recoup-song-sync-brief-maker -> recoup-song-sync-brief
- recoup-song-mix-feedback-reviewer-> recoup-song-mix-feedback
git mv preserves history. Updated SKILL.md name frontmatter, analyzer
decision-tree handoffs, sibling cross-references, plugin README skill
table, and scripts/vendored.json paths. recoup-song-analyzer (router)
unchanged. All 3 gates pass.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: address cubic review comments on PR #33
- validate-findings-evidence.py: a present-but-malformed evidence-ledger.json
is now a hard error (invalid_ledger_file) instead of being silently
downgraded to an empty set, which could report status: ok without
validating any evidence references. Missing ledger stays tolerable.
- validate-normalized-ledger.py: only inspect ledger_line_id values when the
column exists, so empty strings no longer leak into the dedup sets and
produce a confusing "duplicate empty id" error on top of the real
missing-column error.
- Standardize all plugin author emails on agent@recoupable.com (documented
support email) across marketplace + plugin.json manifests for consistency.
- Re-vendored 9 byte-identical script copies; all 3 gates green.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
124 lines
4.5 KiB
Python
124 lines
4.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Validate plugin/marketplace manifests + connector configs.
|
|
|
|
Checks:
|
|
- every marketplace.json / plugin.json / .mcp.json is valid JSON
|
|
(catches the invalid-JSON class of bug, e.g. a missing comma in .mcp.json);
|
|
- marketplace plugins have name + source + semver version, source dir exists,
|
|
names are unique;
|
|
- dual-manifest PARITY: .claude-plugin/marketplace.json and
|
|
.agents/plugins/marketplace.json list the same plugins (name/source/version);
|
|
the .agents copy may add an `interface{}` block, which is ignored.
|
|
|
|
Usage: python3 scripts/validate_manifests.py
|
|
Exit 0 = valid; 1 = problems (printed). Standard library only.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
SEMVER = re.compile(r"^\d+\.\d+\.\d+([-+].+)?$")
|
|
|
|
CLAUDE_MARKET = REPO_ROOT / ".claude-plugin" / "marketplace.json"
|
|
AGENTS_MARKET = REPO_ROOT / ".agents" / "plugins" / "marketplace.json"
|
|
|
|
|
|
def load_json(path: Path) -> tuple[dict | list | None, str | None]:
|
|
try:
|
|
return json.loads(path.read_text(encoding="utf-8")), None
|
|
except json.JSONDecodeError as exc:
|
|
return None, f"invalid JSON: {exc}"
|
|
except OSError as exc:
|
|
return None, f"cannot read: {exc}"
|
|
|
|
|
|
def discover(name: str) -> list[Path]:
|
|
return sorted(p for p in REPO_ROOT.rglob(name) if "node_modules" not in p.parts)
|
|
|
|
|
|
def validate_marketplace(path: Path, problems: list[str]) -> list[dict]:
|
|
data, err = load_json(path)
|
|
rel = path.relative_to(REPO_ROOT)
|
|
if err:
|
|
problems.append(f"{rel}: {err}")
|
|
return []
|
|
if not isinstance(data, dict) or not isinstance(data.get("plugins"), list):
|
|
problems.append(f"{rel}: missing top-level 'plugins' array")
|
|
return []
|
|
|
|
plugins = data["plugins"]
|
|
seen_names: set[str] = set()
|
|
for i, plugin in enumerate(plugins):
|
|
loc = f"{rel} plugins[{i}]"
|
|
for field in ("name", "source", "version"):
|
|
if field not in plugin:
|
|
problems.append(f"{loc}: missing required field '{field}'")
|
|
name = plugin.get("name", "")
|
|
if name in seen_names:
|
|
problems.append(f"{loc}: duplicate plugin name '{name}'")
|
|
seen_names.add(name)
|
|
version = plugin.get("version", "")
|
|
if version and not SEMVER.match(str(version)):
|
|
problems.append(f"{loc}: version '{version}' is not semver")
|
|
source = plugin.get("source")
|
|
if source:
|
|
src_dir = (REPO_ROOT / source).resolve()
|
|
if not src_dir.is_dir():
|
|
problems.append(f"{loc}: source '{source}' is not a directory")
|
|
return plugins
|
|
|
|
|
|
def parity(claude: list[dict], agents: list[dict], problems: list[str]) -> None:
|
|
def key_set(plugins: list[dict]) -> set[tuple[str, str, str]]:
|
|
return {(p.get("name", ""), p.get("source", ""), str(p.get("version", ""))) for p in plugins}
|
|
|
|
c, a = key_set(claude), key_set(agents)
|
|
only_claude = c - a
|
|
only_agents = a - c
|
|
for name, source, version in sorted(only_claude):
|
|
problems.append(f"PARITY: in .claude-plugin only: {name} ({source} @ {version})")
|
|
for name, source, version in sorted(only_agents):
|
|
problems.append(f"PARITY: in .agents only: {name} ({source} @ {version})")
|
|
|
|
|
|
def main() -> int:
|
|
problems: list[str] = []
|
|
|
|
# 1. JSON validity for every manifest/connector file.
|
|
checked = 0
|
|
for fname in ("marketplace.json", "plugin.json", ".mcp.json"):
|
|
for path in discover(fname):
|
|
checked += 1
|
|
_, err = load_json(path)
|
|
if err:
|
|
problems.append(f"{path.relative_to(REPO_ROOT)}: {err}")
|
|
|
|
# 2. Marketplace structure.
|
|
claude_plugins = validate_marketplace(CLAUDE_MARKET, problems) if CLAUDE_MARKET.exists() else []
|
|
if not CLAUDE_MARKET.exists():
|
|
problems.append(f"missing {CLAUDE_MARKET.relative_to(REPO_ROOT)}")
|
|
agents_plugins = validate_marketplace(AGENTS_MARKET, problems) if AGENTS_MARKET.exists() else []
|
|
if not AGENTS_MARKET.exists():
|
|
problems.append(f"missing {AGENTS_MARKET.relative_to(REPO_ROOT)}")
|
|
|
|
# 3. Dual-manifest parity.
|
|
if claude_plugins and agents_plugins:
|
|
parity(claude_plugins, agents_plugins, problems)
|
|
|
|
if problems:
|
|
print(f"MANIFEST VALIDATION FAILED — {len(problems)} problem(s):\n")
|
|
for p in problems:
|
|
print(f" ✗ {p}")
|
|
return 1
|
|
|
|
print(f"MANIFEST VALIDATION PASSED — {checked} manifest/connector file(s) valid; dual-manifest parity OK.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|