Files
msd-core/.github/PULL_REQUEST_TEMPLATE/registry-entry.md
Tom Boucher 90771ddf02 enh(#2904): add a reviewer entry type so third-party reviewer lanes are discoverable (#2912)
* feat(#2904): add a `reviewer` entry type so third-party reviewer lanes are discoverable

ADR-2782 made a reviewer lane installable by a third party, but neither
discoverability catalog could hold one. The Community Capability Registry
requires a non-empty `loopExtensionPoints` and forbids a lane from declaring
any hook kind, so a `role: "reviewer"` entry is unsatisfiable by construction;
the EoS Registry is for ADR-1239 host integrations, which a lane is not.

Adds a third catalog — `docs/registries/reviewers.json` →
`docs/registries/reviewer-registry.md` — whose `interactions` describes the
lane: slug, flags, transport, evidenceClass, reviewsSection, requiresBinaries,
configKeys, runtimeCompat.

The lane vocabulary is a hand-written mirror of `capability-validator.cjs`
(the same pattern as `AXES` mirroring `HOST_INTEGRATION_AXES`), with parity
enforced by tests/registry-reviewer-parity.test.cjs. `slug` deliberately uses
the runtime `LANE_SLUG_RE` grammar rather than the registry's kebab-only `id`
rule, so real lanes (`lm_studio`, `4o-mini`) are not rejected.

Two binary type branches became three-way Map dispatch. Both now fail loudly
on an unrecognized type instead of silently treating it as a capability —
`renderMarkdown` in particular writes a committed catalog file, so a silent
wrong-title render was the worst failure mode available.

Also fixed while here: `gen-registry.cjs` parsed source JSON with no error
handling, so a malformed or non-array `capabilities.json` surfaced as a raw
SyntaxError/TypeError instead of an actionable CLI error.

Closes #2904

* fix(#2904): bound and sanitize untrusted registry `interactions` strings

Review findings from the pre-PR passes.

Security (isolated pass): `interactions` string fields reached the generated,
committed Markdown catalog with no control-character check and no length
bound. A `reviewsSection` carrying ESC and a `requiresBinaries` element
carrying NUL plus 5000 characters validated clean and landed verbatim in the
rendered page — `mdInline` escapes Markdown metacharacters and collapses CRLF,
but nothing else. The identical gap already existed on the capability type's
`configKeys`/`requires`/`runtimeCompat`/`produces`/`consumes`, so it is fixed
there too rather than inherited into a third type.

`hasDisallowedControlChar` is lifted to module scope so exactly one
implementation exists, and a shared `validateStringArrayField` enforces
control-character rejection, a 200-character element cap and a 50-element
array cap for both types.

Correctness (standards pass): `renderMarkdown`'s per-entry summary builder was
still an if/else-if chain whose final `else` was the capability branch — the
one per-type dispatch point this change had not converted, and the same silent
fallthrough it removes elsewhere. It now lives in `RENDER_META` alongside the
title, so a fourth type cannot silently inherit capability's rendering. All
three types' rendered output is byte-identical to before the refactor.

Also corrects a test comment that still claimed the reviewer suites were
failing-first against an unmodified module.

* chore(#2904): backfill changeset PR number (#2912)
2026-07-31 08:11:57 -04:00

5.1 KiB

Registry Entry PR

Using the wrong template? — Bug fix: use fix.md — New feature (not a registry listing): use feature.md — Enhancement to existing behavior: use enhancement.md

Full schema and process: docs/registries/README.md.


Registry type

  • Capability Registry entry — adds/updates one object in docs/registries/capabilities.json
  • EoS Registry entry — adds/updates one object in docs/registries/eos.json
  • Reviewer Lane Registry entry — adds/updates one object in docs/registries/reviewers.json

The entry

{
  "id": "",
  "name": "",
  "type": "",
  "repo": "",
  "description": "",
  "author": "",
  "license": "",
  "enginesGsd": "",
  "install": "",
  "uninstall": "",
  "interactions": {},
  "discussion": ""
}

Required-field checklist

  • id, name, type, repo, description, author, license, enginesGsd, install, uninstall, interactions, discussion are all present and non-empty
  • (Capability entries only) interactions.loopExtensionPoints is a non-empty subset of the 12 Loop Extension Points, interactions.hookKinds ⊆ {step, contribution, gate}, and interactions.configKeys / requires / runtimeCompat / produces / consumes are present (empty arrays are fine where nothing applies)
  • (EoS entries only) protocolVersion is an integer ≥ 1, interactions.interfacePoints is a non-empty subset of the six interface points, interactions.profile is one of programmatic-cli / declarative-cli / ide, and interactions.axes has exactly the eight required axis keys plus, optionally, effortSurface (argv / none)
  • (Reviewer entries only) interactions.slug matches the lane slug grammar ^[a-z0-9][a-z0-9_-]*$, interactions.flags is a non-empty array matching ^--[a-z0-9][a-z0-9-]*$, interactions.transport is spawn or openai-http, interactions.evidenceClass is source-grounded or diff-only, interactions.reviewsSection is a non-empty string (max 200 characters), and interactions.requiresBinaries / configKeys / runtimeCompat are present (empty arrays are fine where nothing applies)

Ownership & non-endorsement

  • repo links to a repository I own or am the primary maintainer of — not a fork, mirror, or someone else's project
  • I understand that inclusion in this registry means only that a maintainer merged this PR — it is not an endorsement, and GSD has not reviewed, tested, audited, or verified my solution or its claimed GSD interactions
  • I understand this entry is removed only for illegal content, malware, spam, or a dead/non-functional link — never for quality — and a maintainer may remove it on that narrow basis without further notice

One entry, one PR

  • This PR adds or updates exactly one entry, in exactly one of capabilities.json / eos.json / reviewers.json
  • I have not bundled any other registry entry, code change, or unrelated docs change into this PR

Generated file in sync

  • I ran npm run gen:registry after editing the JSON source, and this PR includes the regenerated docs/registries/capability-registry.md, docs/registries/eos-registry.md, or docs/registries/reviewer-registry.md
  • I did not hand-edit the generated .md file directly — all edits were made to the JSON source

Documentation

CI enforces lint:docs for any changeset fragment typed Added / Changed / Deprecated / Removed — it must also touch a file under docs/. The JSON source and its regenerated markdown, both under docs/registries/, satisfy this.

  • This PR includes both the JSON source file and the regenerated markdown file under docs/registries/

Checklist

  • npm run validate:registry passes locally against my entry
  • discussion links to a GitHub Discussion in the Registry category (or notes that one will be created on merge, per docs/registries/README.md)
  • .changeset/ fragment added with an Added type describing the new listing

Example filled entry

{
  "id": "linear-issue-sync",
  "name": "Linear Issue Sync",
  "type": "capability",
  "repo": "some-org/gsd-cap-linear-sync",
  "description": "Mirrors ROADMAP.md items to Linear issues as a ship:post contribution.",
  "author": "Some Org <hello@some-org.example>",
  "license": "MIT",
  "enginesGsd": ">=1.6.0",
  "install": "gsd capability install https://github.com/some-org/gsd-cap-linear-sync.git#v1.0.0",
  "uninstall": "gsd capability remove linear-issue-sync",
  "interactions": {
    "loopExtensionPoints": ["ship:post"],
    "hookKinds": ["contribution"],
    "configKeys": ["linear-issue-sync.enabled"],
    "requires": [],
    "runtimeCompat": ["all"],
    "produces": ["linear-issue-links"],
    "consumes": ["ROADMAP.md"]
  },
  "discussion": "https://github.com/open-gsd/gsd-core/discussions/1234"
}