mirror of
https://github.com/alibaba/open-code-review.git
synced 2026-09-14 19:59:52 +08:00
a3d6cf8d0e
* chore(claude): drop duplicate local /open-code-review command The repo-local .claude/commands/open-code-review.md duplicated the canonical plugin prompt at plugins/open-code-review/claude-code/commands/review.md verbatim, adding another copy to keep in sync. Docs already point users at the plugin command file, so remove the redundant local copy. * ci: add plugin distribution contract guardrails Two blocking checks over the plugin/skill distribution surface, which had no automated verification at all. links: the docs and READMEs embed 146 in-repo path links across 53 files (raw.githubusercontent /main/ curl commands and blob|tree/main links). Nothing verified the paths still existed, so moving a referenced file turned every link into a 404 that only readers hit. A missing path now fails the build; a blob/tree kind mismatch only warns, since GitHub redirects between the two views. The scan fails closed below a corpus floor so a broken walk cannot masquerade as a pass, and reports any documentation file it had to skip instead of passing over it in silence. manifests: every path declared by a plugin or marketplace manifest must resolve to a real, non-empty target, and every SKILL.md and command prompt must carry the frontmatter its loader requires (a skill's name must equal its directory, since that is how loaders address it). Without this a rename yields a plugin that installs cleanly and exposes nothing. The Cursor manifest resolves `../skills/` from its manifest directory while its two siblings resolve from the plugin root. That asymmetry could not be verified against Cursor's published spec, so the declaration carries an explicit unverified-base warning rather than being silently certified: if Cursor resolves from the plugin root, `../skills/` points at a `plugins/skills` directory that does not exist. Unit tests run against temp fixtures, never the real work tree: pointing them at the repo would mean a stale link anywhere fails the test step with a stack trace before the annotation-producing steps run, and would also fail the unrelated Action Contract workflow, which runs the same npm script. * fix(ci): escape all regex metacharacters in repo slug CodeQL flagged js/incomplete-sanitization (high) on the slug escape in repoLinkPattern: it escaped `/` but not `\`, so an input backslash could pair with a following replacement. The slug is a hardcoded constant with no metacharacters, making it unexploitable today, but the partial escape is wrong as written and blocks the CodeQL gate. Escape the full regex metacharacter set with backslash first in the class. The generated pattern source is byte-identical for the current slug, and the links/manifests checks still resolve all 146 links.
85 lines
3.0 KiB
YAML
85 lines
3.0 KiB
YAML
name: plugin-contract
|
|
|
|
# Distribution-contract guardrails, kept out of ci.yml (which is build/test/lint)
|
|
# for the same reason translation-sync.yml is: a slow container pull here should
|
|
# never delay core CI feedback.
|
|
#
|
|
# - blocking: every in-repo path link in the docs/READMEs resolves to a real
|
|
# file (these URLs are served from the default branch, so a stale path is a
|
|
# 404 for every reader). blob/tree kind mismatches are warnings only.
|
|
# - blocking: every path a plugin or marketplace manifest declares resolves to
|
|
# a real, non-empty directory, and every SKILL.md / command prompt carries
|
|
# the frontmatter its loader requires.
|
|
#
|
|
# Scoped with paths: so it only runs when a manifest, a skill, a command prompt,
|
|
# a doc, or the checker itself changes.
|
|
#
|
|
# Both checks read the whole work tree, so they are also run on push to main:
|
|
# two PRs can each pass alone and break the invariant once merged (one renames a
|
|
# file, the other adds a reference to the old path). Without the push trigger
|
|
# nothing would ever re-evaluate the merged result. Same shape as
|
|
# action-contract.yml.
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
paths:
|
|
- ".claude-plugin/**"
|
|
- ".agents/**"
|
|
- "plugins/**"
|
|
- "skills/**"
|
|
- "pages/src/content/docs/**"
|
|
- "README*.md"
|
|
- "CONTRIBUTING*.md"
|
|
- "AGENTS.md"
|
|
- "scripts/github-actions/check-plugin-contract.*"
|
|
- ".github/workflows/plugin-contract.yml"
|
|
pull_request:
|
|
branches: [main]
|
|
paths:
|
|
- ".claude-plugin/**"
|
|
- ".agents/**"
|
|
- "plugins/**"
|
|
- "skills/**"
|
|
- "pages/src/content/docs/**"
|
|
- "README*.md"
|
|
- "CONTRIBUTING*.md"
|
|
- "AGENTS.md"
|
|
- "scripts/github-actions/check-plugin-contract.*"
|
|
- ".github/workflows/plugin-contract.yml"
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
plugin-contract:
|
|
runs-on: self-hosted
|
|
timeout-minutes: 10
|
|
container:
|
|
# Pinned to an exact 24.x for reproducible builds, matching the pin used
|
|
# by translation-sync.yml (avoids the mutable node:24 tag).
|
|
image: node:24.18.0
|
|
steps:
|
|
# The checks read the working tree only — no git history needed.
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
|
|
- name: Trust workspace
|
|
run: git config --global --replace-all safe.directory '*'
|
|
|
|
# Validate the guardrail script itself (plain Node, no deps).
|
|
- name: Test plugin-contract guardrail
|
|
run: node scripts/github-actions/check-plugin-contract.test.js
|
|
|
|
# BLOCKING: no doc or README may link to a path that no longer exists.
|
|
- name: Check in-repo path links
|
|
run: node scripts/github-actions/check-plugin-contract.js links
|
|
|
|
# BLOCKING: plugin/marketplace manifests must resolve to real targets and
|
|
# every skill/command must carry loadable frontmatter.
|
|
- name: Check plugin manifest contract
|
|
run: node scripts/github-actions/check-plugin-contract.js manifests
|