feat: 市场支持团队条目类型并上架合同审查团队 (#110)

## 变更 / What

市场此前只有 `agents` 与 `skills` 两类条目。本 PR 加入**团队(teams)**条目类型,并上架第一条真实团队
listing。

The market supported only `agents` and `skills`. This PR adds a
**teams** entry type and lists the first real team.

## 一、支持团队条目类型

「支持一种新条目类型」实际涉及 4 组共 11 个文件,比表面看到的多:

**客户端契约快照**
- 新增 `schemas/market-team-entry.client.schema.json`,用 esbuild 打包客户端
`packages/schemas/src/market.ts` 后导出生成。用同样方法重新生成
`market-agent-entry.client.schema.json` 验证过管线——字节完全一致(含属性顺序),确认不是手工誊抄。

**Sidecar schema**
- `identity.kind` 枚举加 `team`;新增 `$defs.teamSpec`;接入 `spec.oneOf` 与
kind→spec 派发

**校验器(工作量主要在这里)**
- `scripts/catalog/validate_catalog_metadata.py`:`load_legacy`
原先硬编码只认两个根目录。抽出 `CATALOG_ROOTS` 常量同时驱动允许的父目录集合与错误文案;按 kind 分派客户端 schema
校验;`teams` 进 stats 与 `--require-complete` 覆盖统计;把**严格 provenance
比对**与「可安装 pointer 必须自带不可变 ref」两道门禁扩展到团队
- `scripts/i18n/validate-i18n.py`:**它独立重算计数并逐个校验 `entry.json`**,不接团队会漏校
- `.github/workflows/i18n-validate.yml`:变更检测的 grep 不含 `teams/`——**一个只改
teams 的 PR 会报「无 i18n 相关变更,跳过校验」然后零校验通过**
- 测试:`test_validate_catalog_metadata.py` 29→47,`test_validate_i18n.py`
9→17

**顺带修正一条本就不对的规则**:`icon` 此前被要求「每个 entry.json 都必须有非空内联 SVG」,但运行时 schema 里
`marketAgentSchema` 与 `marketTeamSchema` **都没有 `icon` 字段**(只有 skill
有)。也就是说这条规则对 Agent pointer 同样在强加死重量,只因本仓库暂无 agent pointer 条目而未暴露。改为
`ICON_RENDERED_KINDS = {"skill"}`,agent/team 声明 icon
时给**警告**而非错误,文案说明「下一个维护者会以为改它能改变卡片」。

## 二、上架合同审查团队

`teams/contract-review-team/`(`entry.json` + sidecar)。

**团队条目是 fork 指针卡,不分发正文**:市场只存展示元数据 + git-only `source`,真实定义(`team.json`
/ `members.json` / `shared/`)在 `source.repoUrl` 指向的仓库里。安装即
`forkTeam`,更新即 `git pull`——组合固定,因此**没有** `installPolicy` /
`updatePolicy`。

| 字段 | 值 | 依据 |
|---|---|---|
| `source.ref` | `73cd87a9901cc548871927e9d5dbec8e4cc6c2b1` | v0.1.1
的**完整 SHA**。tag 不是可复现 pin,validator 有测试专门拒绝 |
| `latestVersion` | `0.1.1` | 上游真实 tag,与 `release.version` 交叉校验 |
| `license` | `MIT` | 上游仓库真有 LICENSE,已在 pinned ref 的快照中复验 |
| `redistribution` | `source-pointer-only` |
市场从不打包团队正文,只给指针——这是交付形态,与许可证宽松与否无关 |
| `requiredClientVersion` | `10.0.137` | 六个成员都声明了 `FileDigest`
内置工具,它随该版本发布 |
| `memberCount` / `memberNames` | 6 / 5 名 | schema 规定前者**含**组长、后者**不含**
|
| `availability` | `listing-only` | 见下 |

**`availability` 为什么不是 `installable`**:四项证据满足两项(不可变 pin ✓、已知 license
✓),缺的 `reviewedAt` 与 `governance.compliance`
本质是**一次尚未发生的治理审查**——需要具名方在具体日期针对这个确切 ref 审过许可合规、第三方内容与商标使用。没发生的事不能写进目录。

补充一个事实:本仓库**零个 sidecar 有 `compliance` 块,29 个 pointer 条目全是
listing-only**,`installable` 路径从未在任何真实条目上走过。这不阻止安装——fork 由 `source` 驱动。

**`license.evidencePath` 的基准此前是未定义的**:schema 只说
`safeRelativePath`,没规定相对谁。仓库里仅有的两个先例(`guizang-ppt`、`presentation-forge`)都是
vendored 技能,LICENSE 物理上在条目目录里。按那个读法,pointer 条目写 `evidencePath`
断言的是市场目录下有该文件——对 pointer 永远不成立。新增 `license-evidence` 规则按条目形态分派:vendored
要求文件存在(error),pointer 要求条目已 pin(warning),两种读法写进 README。

## 校验 / Validation

```
test_validate_catalog_metadata.py    47 tests  OK
test_validate_i18n.py                17 tests  OK
test_collection_generator.py         exit 0
validate_catalog_metadata.py --require-complete
  0 error, 116 warning   (agents=1, teams=1, publishableSkills=62, sidecars=64)
validate-i18n.py / --online          0 error, 116 warning
translate.py --check                 exit 0
gen-collection-children.py --check   exit 0
```

116 warnings 即加入团队之前的基线——**本条 listing 贡献 0 个警告**。

真实条目上的反向控制(跑在 rsync 副本上,仓库保持干净):

```
source.kind=zip                 → team-entry-schema (error)
install/updatePolicy 出现       → team-entry-schema (error)
requiredClientVersion 漂移      → legacy-consistency (error)
memberCount 漂移                → legacy-consistency (error)
provenance ref 漂移             → legacy-consistency (error)
可安装但无不可变 ref            → installable-evidence (error)
evidencePath 在未 pin 的 pointer → license-evidence (warning)
```

另用**客户端真实校验器**(`parseMarketTeamEntry`,不是快照)验证条目通过,且多写一个字段会被拒。

## 公开信息边界 / Public information boundary

全树扫描无新增命中。团队内容使用「某某科技(北京)有限公司」这类标准中文占位。

---------

Co-authored-by: yi-ge <mizan57533@gmail.com>
This commit is contained in:
2026-09-01 04:15:45 -04:00
committed by GitHub
parent df5959eebf
commit e77c658958
15 changed files with 1277 additions and 64 deletions

View File

@@ -0,0 +1,219 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": [
"id",
"name",
"stewardship",
"license",
"redistribution",
"source"
],
"properties": {
"id": {
"type": "string",
"description": "团队条目唯一标识符,采用与 team.json#id 相同的团队 slug 规则(不允许首尾连字符),保证市场 ID 天然是一个合法团队目录名。",
"pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$"
},
"name": {
"type": "string",
"description": "默认显示名称(源 locale本地化变体见 i18n"
},
"category": {
"type": "string",
"minLength": 1,
"pattern": "^[a-z0-9-]+$",
"description": "市场项目分类slug 格式),由仓库 categories.json 定义"
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"description": "标签列表,用于搜索和分类"
},
"icon": {
"type": "string",
"description": "市场列表展示的内联 SVG 图标。**只有 Skill 卡片渲染它**——marketAgentSchema 与 marketTeamSchema 都没有 icon 字段Agent 与团队卡片一律走 avatar条目声明了 icon 也不会显示。Pointer 技能将其随离线元数据返回。"
},
"latestVersion": {
"type": "string",
"description": "市场登记的最新版本semver",
"pattern": "^\\d+\\.\\d+\\.\\d+$"
},
"i18n": {
"type": "object",
"description": "按 locale 提供的本地化展示元数据(仅 name/shortDesc轻量",
"additionalProperties": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "该 locale 下的显示名称"
},
"shortDesc": {
"type": "string",
"description": "该 locale 下的简短描述"
}
},
"additionalProperties": false
}
},
"maintainer": {
"type": "object",
"required": [
"name",
"verified"
],
"properties": {
"name": {
"type": "string",
"description": "维护者名称,如 \"DesireCore Official\""
},
"verified": {
"type": "boolean",
"description": "是否官方认证维护者,认证后显示蓝色勾选标记"
},
"account": {
"type": "string",
"description": "维护方账号标识,如 GitHub 组织/用户名 \"desirecore\""
},
"url": {
"type": "string",
"description": "维护方主页或仓库 URL"
}
},
"additionalProperties": false
},
"stewardship": {
"type": "string",
"enum": [
"official",
"partner",
"community",
"pointer"
],
"description": "维护方分类official官方/ partner合作伙伴官方/ community社区已收录/ pointer仅指针不分发内容"
},
"license": {
"type": "string",
"minLength": 1,
"description": "许可证SPDX idMIT/Apache-2.0/MIT-0…或特殊值 not-declared / source-available / packaged-distribution"
},
"redistribution": {
"type": "string",
"enum": [
"allowed",
"source-pointer-only",
"verify-package-terms"
],
"description": "再分发策略allowed可分发/ source-pointer-only仅指针安装时拉取/ verify-package-terms需核验条款"
},
"source": {
"type": "object",
"required": [
"kind",
"repoUrl"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"git"
],
"description": "团队只支持 git 源:安装是 fork 团队仓库、更新是 git pullzip / web 无法表达这两个动作。"
},
"repoUrl": {
"type": "string",
"minLength": 1,
"description": "团队 git 仓库的 HTTPS 克隆地址,如 https://github.com/desirecore/team-contract-review.git"
},
"repoBranch": {
"type": "string",
"description": "团队仓库分支名称,默认 main。安装时会真的按这个分支克隆必须是合法分支名不带 refs/ 前缀、不以 - 开头),否则安装失败关闭而不是静默改装默认分支。",
"default": "main"
},
"ref": {
"type": "string",
"description": "锁定的 commit SHA / tag用于可复现安装为空表示取分支 HEAD。只接受**完整 40 位 SHA-1** 或标签名:缩写 oid 会随仓库增长变歧义、也远比完整 SHA 容易被构造碰撞,而降级 git 后端根本不解析它。安装时会按该 ref 克隆并读回真实 HEAD 逐字复核,不一致就中止整个安装——一个不被兑现的锁比没有锁更危险,因为它会被信任。"
}
},
"additionalProperties": false,
"description": "团队 fork 源。**这是团队条目的核心**:安装端点用 repoUrl + repoBranch 调用 forkTeam更新用同一仓库 git pull。"
},
"requiredClientVersion": {
"type": "string",
"description": "此条目要求的最低客户端版本semver低于此版本时提示升级",
"pattern": "^\\d+\\.\\d+\\.\\d+$"
},
"avatar": {
"type": "object",
"required": [
"t",
"bg"
],
"properties": {
"t": {
"type": "string",
"description": "头像显示的文字(通常为一个汉字)",
"maxLength": 2
},
"bg": {
"type": "string",
"description": "头像背景 CSS 渐变值,如 linear-gradient(135deg, #007AFF, #005ECB)"
},
"image": {
"type": "object",
"description": "图片头像:市场仓库中该条目目录内的位图(如 \"assets/avatar.webp\")。存在且文件可读时优先于 t + bg 渲染,否则自动回落——所以 t 与 bg 仍为必填,它们是确定的回落形态。仅支持 PNG / JPEG / WebP。注意这是**市场展示**元数据,与安装后 agent.json 的 avatar.image 是两套,安装时不迁移。",
"required": [
"path"
],
"properties": {
"path": {
"type": "string",
"description": "相对于该市场条目目录的图片路径。禁止绝对路径和 .. 路径穿越",
"minLength": 5,
"maxLength": 240,
"pattern": "^(?![\\\\/])(?![A-Za-z]:)(?!.*(?:^|[\\\\/])\\.\\.(?:[\\\\/]|$)).+\\.(?:png|jpe?g|webp)$"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"supervisorName": {
"type": "string",
"description": "组长supervisorAgent 的显示名称,仅用于卡片与详情页展示;真实组长以团队仓库 team.json#supervisor 为准。"
},
"supervisorAgentId": {
"type": "string",
"description": "组长 Agent ID展示与去重用。只是发布者声明不作为安装期白名单依据。",
"pattern": "^[A-Za-z0-9_-]+$"
},
"memberCount": {
"type": "integer",
"minimum": 1,
"description": "团队成员总数(**含组长**),用于卡片上\"N 名成员\"的展示;不参与任何安装校验。"
},
"memberNames": {
"type": "array",
"items": {
"type": "string"
},
"maxItems": 32,
"description": "成员显示名预览列表(不含组长),用于卡片上的成员缩略展示;不保证与仓库成员完全一致。"
},
"requiredSkills": {
"type": "array",
"items": {
"type": "string"
},
"maxItems": 128,
"uniqueItems": true,
"description": "团队声明的必需 Skill ID用于安装前向用户提示前置条件真实校验由安装后的团队 capability audit 执行。"
}
},
"additionalProperties": false,
"$comment": "Generated from desirecore/desirecore packages/schemas/src/market.ts#marketTeamEntrySchema at commit 947d9f32c6c81ebe5ece78f6295155814c888a22, source blob d6b361d149773e7384babe3011d86d25ded4e493. Keep the complete client contract; regenerate from that export instead of editing fields manually."
}