* 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)
(cherry picked from commit 90771ddf02)
5.1 KiB
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,discussionare all present and non-empty- (Capability entries only)
interactions.loopExtensionPointsis a non-empty subset of the 12 Loop Extension Points,interactions.hookKinds⊆{step, contribution, gate}, andinteractions.configKeys/requires/runtimeCompat/produces/consumesare present (empty arrays are fine where nothing applies) - (EoS entries only)
protocolVersionis an integer ≥ 1,interactions.interfacePointsis a non-empty subset of the six interface points,interactions.profileis one ofprogrammatic-cli/declarative-cli/ide, andinteractions.axeshas exactly the eight required axis keys plus, optionally,effortSurface(argv/none) - (Reviewer entries only)
interactions.slugmatches the lane slug grammar^[a-z0-9][a-z0-9_-]*$,interactions.flagsis a non-empty array matching^--[a-z0-9][a-z0-9-]*$,interactions.transportisspawnoropenai-http,interactions.evidenceClassissource-groundedordiff-only,interactions.reviewsSectionis a non-empty string (max 200 characters), andinteractions.requiresBinaries/configKeys/runtimeCompatare present (empty arrays are fine where nothing applies)
Ownership & non-endorsement
repolinks 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:registryafter editing the JSON source, and this PR includes the regenerateddocs/registries/capability-registry.md,docs/registries/eos-registry.md, ordocs/registries/reviewer-registry.md - I did not hand-edit the generated
.mdfile directly — all edits were made to the JSON source
Documentation
CI enforces
lint:docsfor any changeset fragment typedAdded/Changed/Deprecated/Removed— it must also touch a file underdocs/. The JSON source and its regenerated markdown, both underdocs/registries/, satisfy this.
- This PR includes both the JSON source file and the regenerated markdown file under
docs/registries/
Checklist
npm run validate:registrypasses locally against my entrydiscussionlinks to a GitHub Discussion in theRegistrycategory (or notes that one will be created on merge, per docs/registries/README.md).changeset/fragment added with anAddedtype 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"
}