| 226 | |
| 227 | |
| 228 | def validate_cursor(report: Report) -> None: |
| 229 | root = WORKTREE / ".cursor-plugin" |
| 230 | if not root.is_dir(): |
| 231 | return |
| 232 | |
| 233 | # 1. marketplace.json shape |
| 234 | marketplace = root / "marketplace.json" |
| 235 | if marketplace.is_file(): |
| 236 | try: |
| 237 | data = json.loads(marketplace.read_text()) |
| 238 | except json.JSONDecodeError as e: |
| 239 | report.add( |
| 240 | severity="error", |
| 241 | harness="cursor", |
| 242 | path=marketplace, |
| 243 | message=f"JSON parse error: {e}", |
| 244 | remediation="Regenerate via `make generate HARNESS=cursor --all`.", |
| 245 | ) |
| 246 | return |
| 247 | if "owner" not in data: |
| 248 | report.add( |
| 249 | severity="error", |
| 250 | harness="cursor", |
| 251 | path=marketplace, |
| 252 | message="marketplace.json missing required `owner` field", |
| 253 | remediation="Cursor 2.5+ requires owner.name in marketplace.json.", |
| 254 | ) |
| 255 | for entry in data.get("plugins", []): |
| 256 | if "source" not in entry: |
| 257 | report.add( |
| 258 | severity="error", |
| 259 | harness="cursor", |
| 260 | path=marketplace, |
| 261 | message=f"plugin entry {entry.get('name', '<unnamed>')} missing `source`", |
| 262 | remediation="Cursor uses `source` (not `path` or `url`) per plugin entry.", |
| 263 | ) |
| 264 | |
| 265 | # 2. Each per-plugin manifest parses |
| 266 | plugins_dir = root / "plugins" |
| 267 | if plugins_dir.is_dir(): |
| 268 | for manifest in plugins_dir.glob("*.json"): |
| 269 | try: |
| 270 | data = json.loads(manifest.read_text()) |
| 271 | except json.JSONDecodeError as e: |
| 272 | report.add( |
| 273 | severity="error", |
| 274 | harness="cursor", |
| 275 | path=manifest, |
| 276 | message=f"JSON parse error: {e}", |
| 277 | remediation="Regenerate via `make generate HARNESS=cursor`.", |
| 278 | ) |
| 279 | continue |
| 280 | if "name" not in data: |
| 281 | report.add( |
| 282 | severity="error", |
| 283 | harness="cursor", |
| 284 | path=manifest, |
| 285 | message="missing required `name` field", |