Repository navigation
Proposal: teamai install — 统一编排 npm 依赖与 Claude 插件市场 #325
Description
状态:设计评审中(未实现)
范围:本提案聚焦两个生态 —— npm 与 Claude plugin 插件市场。cargo / codex 暂缓,架构保留扩展位(见 §11)。
1. 背景与目标
TeamAI CLI 目前是一个"团队 AI 资源同步工具"(通过 Git 仓库同步 skills/rules/agents/hooks/mcp 到各 AI 工具原生目录)。本提案在此之上增加一层能力,让 TeamAI 成为项目的 AI-native 开发环境编排器:
用一个命令
teamai install,依据项目里的声明,恢复整个开发环境 —— 既装齐语言依赖(npm),也装齐 Claude Code 的插件(通过官方插件市场机制)。
核心原则:TeamAI 不重造任何包管理器。
- npm 依赖由
npm自己解析,package-lock.json是依赖图真相源。 - Claude 插件由 Claude Code 原生的
claude pluginCLI 安装,TeamAI 只做编排,不自己拷目录、不碰~/.claude/plugins/内部结构。
TeamAI 只负责:统一声明(一份 manifest)、发现、安装、同步、锁定、环境体检。
TeamAI (Orchestrator)
│
┌────────────────┴────────────────┐
│ │
NpmAdapter ClaudePluginAdapter
│ │
shell out: npm shell out: claude plugin
(npm 解依赖图) (marketplace add + install)
2. 现状约束(基于真实代码,非假设)
在动手前已通读现有代码库,以下事实决定了本提案的形态:
2.1 命名冲突(关键)
产品直觉会想用 teamai init / teamai.yaml,但两者在现有代码里已被占用且语义完全不同:
| 名字 | 现有含义 | 冲突 |
|---|---|---|
teamai init |
配置团队 Git 仓库 + clone + 注册成员(4 条模式分派路径,绑定 provider 认证) | 🔴 语义完全不同 |
teamai.yaml |
团队仓库根的团队配置(team/repo/provider/toolPaths/...),由 TeamaiConfigSchema 校验 |
🔴 单仓库模式下同路径 |
teamai doctor |
诊断团队同步配置 | 🟡 可增量扩展 |
teamai install / sync / lock |
空闲 | ✅ 无冲突 |
策略:
- MVP 只新增空闲的顶级命令
teamai install(sync/lock暂缓,见 §12);不新增teamai init—— 把"检测项目 + 脚手架 manifest"折叠进install首次运行(类比npm install也会初始化node_modules)。 - 配置复用
teamai.yaml,新增一个packages:键;通过独立 loader 只读name+packages,与团队配置的加载器互不干扰 —— 团队仓库的teamai.yaml与纯包项目的teamai.yaml可共存。 doctor在现有检查后追加 Environment + Packages 段(仅当声明了packages:),现有检查项 100% 不动。
2.2 可直接复用的现有资产
| 能力 | 现有实现 | 位置 |
|---|---|---|
| 配置加载范式(YAML + Zod parse) | loadTeamConfig → YAML.parse → Schema.parse |
src/config.ts, src/types.ts |
| 子进程执行(无注入,args 数组) | runCommand(async spawn)、cnbExec(spawnSync) |
src/clone.ts:85, providers/cnb/cnb-cli.ts:32 |
| 二进制版本探测 | execVersion(bin, ['--version']),失败返 '' |
src/agent-version.ts:17 |
| ✔/✖ 输出 | Check[] + log.success/error |
src/doctor.ts |
| CLI 注册(懒加载) | commander v12 + await import() |
src/index.ts |
| 依赖已就绪 | yaml / zod / fs-extra(无需新增依赖) |
package.json |
2.3 能力缺口(需新写)
- node/npm/claude 的版本探测(现只探测 gh/gf provider 二进制)。
package.json的项目类型嗅探(现有 init 不做)。- 编排
claude pluginCLI 的逻辑。
3. Claude 插件市场机制(本机实测 + 官方文档双重确认)
3.1 本机 claude plugin CLI 实测可用
claude plugin marketplace add <source> [--scope user|project|local] [--sparse <paths...>]
claude plugin install <plugin>@<marketplace> [-s|--scope <scope>] [-y|--yes] [--config k=v]
claude plugin list [--json] [--available] # --available 需配合 --json
claude plugin marketplace list [--json]
claude plugin uninstall <plugin>@<marketplace> [--scope <scope>] [--prune] [-y]
claude plugin update <plugin>@<marketplace> [-y]
claude plugin enable|disable <plugin>@<marketplace>
claude plugin list --json 真实输出(本机):
[
{ "id": "code-review@claude-plugins-official", "version": "unknown", "scope": "user",
"enabled": true, "installPath": "/Users/.../plugins/cache/.../code-review/unknown",
"installedAt": "...", "lastUpdated": "..." },
{ "id": "context7@claude-plugins-official", "version": "unknown", "scope": "user",
"enabled": true, "mcpServers": { "context7": { "type": "http", "url": "..." } } }
]claude plugin marketplace list --json:
[ { "name": "claude-plugins-official", "source": "github",
"repo": "anthropics/claude-plugins-official",
"installLocation": "/Users/.../plugins/marketplaces/claude-plugins-official" } ]3.2 官方机制要点(docs.claude.com,已核实)
- marketplace = 带
.claude-plugin/marketplace.json的 git repo(也支持本地路径/URL)。add只注册目录,不装任何东西。 - 非交互:
claude plugin ...shell 子命令无需会话即可跑;-y在非 TTY 下对command/headersHelper源是必需的,对普通 git/npm 源可选但安全。 - enable ≠ install(v2.1.195+):外部源插件仅在 settings 的
enabledPlugins声明不会自动安装,Claude 会报未安装并提示claude plugin install。→ 编排必须真跑 install。 - 无单一 lockfile:官方以 settings 文件(
extraKnownMarketplaces+enabledPlugins)+cache/<版本>目录为事实 pin;--json是唯一可靠状态源。 - pin 面:marketplace 加时用
owner/repo@ref或git-url#ref;plugin 源支持ref(分支/标签)与sha(40 位,优先级更高)。 - install 落盘:
~/.claude/plugins/cache/<marketplace>/<plugin>/<version>/;marketplace 目录在~/.claude/plugins/marketplaces/<name>/。 - install 后需重启会话才加载(或
/reload-plugins)—— 对 TeamAI 无影响(我们只负责装好)。 - 确定性/离线:可用
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC/DISABLE_AUTOUPDATER控制刷新与自更新。
⚠️ 官方文档未明确列出各子命令的数值退出码与list --json完整字段。设计上以--json载荷为准解析,不依赖退出码文本;退出码仅作粗粒度成功/失败判断,并按本机claude --version实测校准。
4. 命令表面
MVP 只新增 1 个命令,扩展 2 个现有命令。sync / lock 暂不做(见 §12)——teamai.lock 仍由 install 生成为快照,只是没有单独命令去操作它。
teamai install 的两种形态(npm 心智)
install 借用 npm install 的双形态:
| 形态 | 作用 | 谁用 |
|---|---|---|
teamai install <plugin> / <pkg>(带参数) |
① TeamAI 调底层 claude plugin install / npm install 装上并验证 ② 自动把这条写进 teamai.yaml 的 packages: |
管理员加包 |
teamai install(无参数) |
读 teamai.yaml 的 packages:,装齐全部声明(幂等) |
队友 / 新机器恢复 |
完全类比:npm install lodash(装一个 + 记进 package.json) vs npm install(按 package.json 装全部)。全程只用 teamai install,管理员不碰裸 claude plugin 命令,也不手写 YAML。
命令总表
| 命令 | 新增/扩展 | 行为 |
|---|---|---|
teamai install [target] |
🆕 新增 | 带参数:装 target + 写声明;无参数:读 teamai.yaml 的 packages: 装齐。底层 npm → npm install;claude → marketplace add + plugin install -y。写 teamai.lock。 |
teamai push(扩展) |
♻️ 扩展 | 管理员把更新后的 teamai.yaml(含 packages: 段)推送到团队仓库(详见 §11)。 |
teamai doctor(扩展) |
♻️ 扩展 | 追加 Environment(node/npm/claude 版本)+ Packages(npm 包状态 / 已装 plugin 状态)。 |
沿用现有 program 级全局选项 --dry-run(预览,不落盘)、-y/--yes(非交互)。
无
teamai init:检测项目 + 脚手架 manifest 折叠进install首次运行(现有teamai init已被"团队仓库初始化"占用,语义冲突)。
5. 配置 Schema(teamai.yaml 的 packages: 键)
这个
packages:段通常由teamai install <plugin>自动写入/更新(见 §4 双形态),管理员一般不手写;下面的 schema 是它读写的目标结构。手写编辑也支持。
5.1 示例(marketplace 与 plugins 分开声明)
name: my-ai-project
packages:
npm:
- name: typescript
version: "^5.9.0"
- name: playwright
version: "^1.55.0"
claude:
# 先声明插件市场(name 是 @marketplace 标识,repo 是 git 源;可 pin ref)
marketplaces:
- name: claude-plugins-official
repo: anthropics/claude-plugins-official # owner/repo | git URL | 本地路径
- name: acme-tools
repo: acme-corp/claude-plugins
ref: v1.4.0 # 可选:pin 分支/标签
# 再声明要装的插件,用 name@marketplace 引用
plugins:
- name: code-review@claude-plugins-official # 缺省跟随 TeamAI 当前 scope
- name: deploy-tools@acme-tools
version: "2.1.0" # 可选:pin 版本
scope: user # 可选:显式覆盖(user | project | local)5.2 Zod 设计
// npm 复用扁平 spec(name + version)
const NpmSpecSchema = z.object({
name: z.string().min(1),
version: z.string().default('*'),
});
// claude:marketplace 声明
const ClaudeMarketplaceSchema = z.object({
name: z.string().min(1), // @marketplace 标识
repo: z.string().min(1), // owner/repo | git URL | 本地路径
ref: z.string().optional(), // pin 分支/标签(marketplace 源不支持 sha)
});
// claude:plugin 声明
const ClaudePluginSpecSchema = z.object({
name: z.string().min(1), // "plugin@marketplace"
version: z.string().optional(), // 可选 pin
scope: z.enum(['user', 'project', 'local']).optional(), // 缺省跟随 TeamAI 当前 scope(见 §13.1);仅需覆盖时才写
});
const ClaudeEcosystemSchema = z.object({
marketplaces: z.array(ClaudeMarketplaceSchema).default([]),
plugins: z.array(ClaudePluginSpecSchema).default([]),
});
const PackageManifestSchema = z.object({
name: z.string().default(''),
packages: z.object({
npm: z.array(NpmSpecSchema).optional(),
claude: ClaudeEcosystemSchema.optional(),
// cargo / codex 预留位,本阶段不定义(见 §11)
}).default({}),
});注意:npm 生态是扁平数组,claude 生态是对象(marketplaces + plugins 两个子列表)。形状不同是刻意的 —— 忠实反映各自原生心智,而非硬套统一结构。
5.3 校验规则(validate,不触网)
- 每个
plugins[].name必须是plugin@marketplace形式,且@后的 marketplace 必须在同文件marketplaces[]里声明(否则报错并给可读提示)。 - npm
name不含空格。 - marketplace
name唯一;repo非空。
6. Lockfile Schema(teamai.lock)
version: 1
packages:
npm:
- name: typescript
version: 5.9.2 # 从 package-lock.json 摘取的解析版本
source: npm
integrity: "sha512-..."
resolved: "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz"
claude:
marketplaces:
- name: claude-plugins-official
source: github
repo: anthropics/claude-plugins-official
installLocation: "~/.claude/plugins/marketplaces/claude-plugins-official"
plugins:
- id: code-review@claude-plugins-official
version: "1.2.0" # 来自 claude plugin list --json 的解析版本
scope: user
enabled: true
installPath: "~/.claude/plugins/cache/.../code-review/1.2.0"来源分工:
- npm 条目 = 从
package-lock.json摘顶层依赖状态(不重复解析依赖图)。 - claude 条目 = 直接采集
claude plugin list --json/marketplace list --json的真实输出。TeamAI 不发明状态,只快照原生 CLI 的事实。
7. Adapter 设计
统一抽象 PackageAdapter,CLI handler 不含任何生态特定逻辑;解析 manifest 后按生态取 adapter 并调用。
abstract class PackageAdapter {
abstract readonly ecosystem: 'npm' | 'claude';
abstract detect(cwd): Promise<boolean>;
abstract validate(decl): Promise<ValidationResult>;
abstract install(decl, ctx): Promise<LockSection>; // 真实安装,返回要写入 teamai.lock 的片段
abstract status(decl, ctx): Promise<PackageStatus[]>; // 供 doctor 比对
}7.1 NpmAdapter(shell out)
detect(cwd)= 存在package.json。install=npm install <name>@<ver> ...(通过通用execCommand,spawn + args 数组,无注入)。--dry-run打印 "would run" 不执行。- 生成 lock 片段(install 内部,非独立命令):装完读
package-lock.json(支持 v2/v3 的packages键与 v1 的dependencies),摘顶层包的 version/integrity/resolved,跳过node_modules/x/node_modules/y嵌套(传递依赖)。 status= 有无对应 package-lock 条目。
7.2 ClaudePluginAdapter(编排原生 CLI —— 本提案核心)
依赖探测:detect = claude 二进制在 PATH 且 claude plugin --help 可用(通过 probeBinary)。缺失时 install fail-fast 并提示安装 Claude Code。
install 流程(编排,严格幂等):
0. scope = TeamAI 当前 scope(project 或 user);plugin 条目自带 scope 则覆盖(见 §13.1)。
1. 前置:确认 claude plugin CLI 可用(否则 fail-fast)。
2. for each marketplace in packages.claude.marketplaces:
source = ref ? `${repo}@${ref}` : repo
claude plugin marketplace add <source> --scope <scope>
# 幂等:已存在同名 marketplace 由 CLI 自行处理;非致命失败记录并继续
3. for each plugin in packages.claude.plugins:
claude plugin install <plugin@marketplace> --scope <plugin.scope ?? scope> -y
# -y:非 TTY 下对 command/headersHelper 源必需,对普通源安全
4. 快照:claude plugin list --json + marketplace list --json → 生成 lock 的 claude 段
--dry-run:只打印将执行的marketplace add/plugin install命令,不跑、不写 lock。- version pin:marketplace 的
ref通过owner/repo@ref传入;plugin 的version目前原生 CLI 无 install-time--versionflag,pin 主要由 marketplace.json 的源版本决定 —— 设计上如实记录声明 version 到 lock,并在 doctor 里与list --json实际 version 比对,不一致时告警(不强制)。 - enable ≠ install:因此始终跑真实
install,不走"只写 settings"的捷径。 - 幂等:
install可重复运行(队友每次收到更新后重跑)——marketplace add与plugin install对已存在项由原生 CLI 自行去重。
status(供 doctor):解析 claude plugin list --json,对每个声明的 plugin@marketplace 判断 installed(id 匹配)+ enabled。
uninstall(后续):claude plugin uninstall <id> --scope <scope> --prune。
7.3 编排设计原则
- 只调 CLI,不碰
~/.claude/plugins/内部结构(cache 布局、版本目录是 Claude 私有实现,不稳定)。 - 不直接改 settings.json 的
enabledPlugins/extraKnownMarketplaces—— 交给claude plugin install按--scope落到正确的 settings 文件,避免 TeamAI 与 Claude 争抢写权 + trust gating 问题。 - JSON 优先:所有状态读取走
--json,不解析人类文本;退出码只做粗判。
8. teamai doctor 扩展输出
Environment
✔ Node 22.16
✔ npm 10.9
✔ Claude Code 2.1.243
Packages (npm)
✔ typescript 5.9.2
✔ playwright 1.55.0
Packages (claude plugins)
✔ code-review@claude-plugins-official 1.2.0 (enabled)
✖ deploy-tools@acme-tools declared but not installed
→ Run `teamai install`
✔ TeamAI environment is ready
- Environment 段:
probeBinary('node'|'npm'|'claude')。 - npm 段:比对
package-lock.json。 - claude 段:比对
claude plugin list --json;声明了但未装 / 未启用都标 ✖ 并给建议。
9. 数据流与文件改动
teamai install <target> (带参数:加包)
→ 解析 target 属于哪个生态(npm | claude)
→ getAdapter(eco).validate → install # npm: shell npm;claude: 编排 claude plugin
→ 把 target 写回 teamai.yaml 的 packages 段 # ← 双形态的"写声明"
→ 采集状态 → saveLockfile(teamai.lock)
teamai install (无参数:按声明装齐)
→ loadPackageManifest(teamai.yaml) # 读 name + packages
→ for eco in [npm, claude]:
getAdapter(eco).validate → install
→ 采集状态 → saveLockfile(teamai.lock)
纯新增:
src/pkg/
types.ts # NpmSpec / ClaudeMarketplace / ClaudePluginSpec / Manifest / Lock schema
manifest.ts # 读写 teamai.yaml packages 段 + teamai.lock(独立于 loadTeamConfig)
registry.ts # getAdapter(npm|claude) + detectEcosystems
env-detect.ts # probeBinary(node/npm/claude)
commands.ts # pkgInstall / pkgDoctorReport(sync/lock 暂缓)
adapters/
base.ts # PackageAdapter 抽象基类
npm.ts # shell out npm
claude-plugin.ts# 编排 claude plugin CLI
src/utils/exec.ts # 通用 execCommand + probeBinary(从 clone.ts:runCommand 提炼)
增量挂接(纯追加,0 删除):src/index.ts(注册 install [target])、src/doctor.ts(追加检查段)、src/push.ts/src/pull.ts(同步/比对团队 teamai.yaml 的 packages 段)、SessionStart hook 链(注册 pkg-hint)。
10. 测试计划
Unit
- manifest 解析:npm 扁平数组 + claude marketplaces/plugins 对象;
plugin@marketplace引用校验(引用未声明的 marketplace 要报错)。 - lock 读写往返。
- NpmAdapter:从 package-lock v3 摘顶层版本、跳过嵌套传递依赖、缺 lock 时降级。
- ClaudePluginAdapter:mock
execCommand,断言编排出的命令序列正确(marketplace add <repo@ref>、install <id> --scope --yes);dry-run 不调用 exec;list --json解析成 status。 - env-detect:mock
probeBinary。
Integration
- npm-only fixture:
install(真实npm install装一个无依赖小包如is-odd)→ 验证 lock 摘到 integrity。 - claude-only fixture:mock
claudeCLI(假claude脚本或 mock execCommand)编排 marketplace add + install,验证命令序列与 lock 快照;真实 CLI 端到端在有claude的机器上手动验证。 - mixed(npm + claude)。
全套件:零回归(现有 ~168 vitest 文件),tsc --noEmit 干净,npm run build 成功,真实 CLI 手动 E2E。
11. 团队分发与协作流程(核心场景)
目标场景:管理员装好并验证一套环境 → push → 队友自动感知 → 一键安装。这条链路复用 TeamAI 现有的团队仓库 + SessionStart 自动 pull hook 基础设施,不新建通道。
11.1 已定决策
- 声明复用团队仓库的
teamai.yaml:不新建packages.yaml。声明就是团队仓库(~/.teamai/team-repo/teamai.yaml)里的packages:键(格式见 §5),与团队配置同文件、加键即可。 - 全团队统一作用域:声明在团队仓库、对全团队生效(不按项目/角色区分),所有队友装同一套 claude 插件 + npm 工具。
teamai install <plugin>同时装 + 写声明:管理员加包只用teamai install <plugin>,它既调底层安装,又把这条写进团队teamai.yaml的packages:(类比npm install <pkg>写 package.json)。不碰裸claude plugin,不手写 YAML。- 提示不自装:队友 pull 到新声明后,SessionStart hook 只提示"团队环境有更新,运行
teamai install",不静默执行。理由见 §11.3。
11.2 完整流程
【管理员】
1. teamai install code-review@claude-plugins-official
# ① TeamAI 调 claude plugin install 装上并验证
# ② 自动把这条写进团队 teamai.yaml 的 packages.claude
(npm 同理:teamai install typescript → npm install + 写 packages.npm)
2. teamai push # 推送更新后的 teamai.yaml(含 packages 段)到团队仓库
【队友(自动)】
3. 打开 AI 工具 → SessionStart hook 自动 teamai pull(既有机制)
4. pull 检测到团队 teamai.yaml 的 packages 段有变化(比对哈希/rev)
→ 通过 hookSpecificOutput.additionalContext 提示:
"团队环境已更新(新增 2 个 claude 插件),运行 `teamai install` 安装"
5. 队友运行 teamai install # 无参数:读声明装齐 claude 插件 + npm 包,写本地 teamai.lock
管理员和队友用的是同一个命令 teamai install,只是管理员带参数(加包 + 写声明),队友无参数(按声明装齐)。
11.3 为什么"提示"而非"自动静默安装"(安全论证)
teamai install 会真实执行 npm install 与 claude plugin install —— 即在队友机器上运行第三方代码。若做成 pull 后静默自动装:
- 供应链攻击面:管理员账号被盗、或团队仓库被篡改,攻击者改一行声明就能在所有队友机器上静默执行任意插件/包代码,无人察觉。
- claude 插件尤其危险:插件可携带 hooks / MCP server / shell 脚本,能力极广。
"提示 + 队友主动确认"把这一步变成有人参与的决策:队友能看到"新增了什么",再决定装不装。自动性几乎不损失(仍是一行命令),但堵住了静默 RCE。这也和 claude plugin install -y 本身要求人确认 marketplace 声明命令的设计哲学一致。
双重防线(见 §13.3):声明本身也不能单人直推 —— 任何 member 都可提交 packages 段变更,但必须走 MR/PR review 合入。写入侧(review)+ 消费侧(队友确认)两道人工关卡,共同守住"团队声明 = 将在全员机器上跑的第三方代码"这条信任边界。
11.4 复用的现有机制
teamai push/pull:团队仓库同步通道(已有)。push 扩展为识别并推送团队teamai.yaml的 packages 段;pull 扩展为比对该段变化。- SessionStart hook +
hookSpecificOutput.additionalContext:src/mr-hint.ts已是"pull 后检测到变化 → 提示 AI/用户"的现成范式,packages 更新提示照搬此模式(新增一个pkg-hint)。 - 变化检测:复用
State.lastPullRev一类的哈希/版本比对,只在 packages 段实际变化时提示,不打扰。
11.5 新增/改动(团队分发部分)
src/pkg/
pkg-hint.ts # SessionStart:检测团队 teamai.yaml 的 packages 段更新 → 提示队友 install
install(带参数形态):安装后把 target 写回团队teamai.yaml的packages:段。push/pull(既有文件):识别并同步 / 比对 packages 段。- SessionStart hook 链:注册
pkg-hint(与现有mr-hint并列)。
12. 本阶段边界与后续
本阶段做:teamai install(两种形态:带参数装 + 写声明 / 无参数按声明装齐;npm + claude plugin 市场编排)+ doctor 扩展 + 团队分发(push/pull 团队 teamai.yaml 的 packages 段 + SessionStart 提示)。
暂缓(架构保留扩展位):
teamai sync/teamai lock命令:MVP 不做。teamai.lock仍由install生成为安装快照,但不提供单独操作它的命令。将来需要"按锁定版本精确复现"时再加,架构无依赖。- cargo / codex:
PackageManifestSchema.packages保留位置,不定义 schema;registry.ts的 adapter map 只注册 npm + claude。加回来时只需补 schema + adapter + 注册一行。 - claude uninstall / update 的 teamai 层命令:CLI 已支持,本阶段先不暴露。
- pull 后自动静默安装:刻意不做(见 §11.3 安全论证),只提示。
- plugin install-time 版本 pin:受限于原生 CLI 未暴露
--version,先"记录 + doctor 告警",待 CLI 支持后收紧。 - settings.json 直写(
extraKnownMarketplaces/enabledPlugins):刻意不做(交给claude plugin install写),避免争抢文件与 trust gating;若将来要"纯声明式恢复不跑 CLI",再评估。
13. 已定决策(原待确认项)
- claude 插件 scope:跟随 TeamAI 的 scope。install 在 project scope 项目里跑时,claude 插件用
--scope project;user scope 则用--scope user。插件的 scope 与 TeamAI 当前 scope 一致,不再由各 plugin 条目单独指定(条目scope字段作为可选覆盖保留,缺省跟随)。 - 测试双轨:接受。claude 段集成测试用 mock CLI(CI 无
claude环境),真实 E2E 依赖本机装了 Claude Code,手动执行。 - packages 声明写权:所有人可 push,但需 review。任何 member 都能 push 团队
teamai.yaml的 packages 段,但必须走 MR/PR review 合入(复用现有 reviewers 机制)。这是 §11.3 安全模型的关键一环:声明变更(=将在全团队机器上执行的第三方代码)必须有人 review,不能单人直推。
Activity