| name | trtc-apply |
| description | INTERNAL quality gate for TRTC code generated by onboarding / topic skills. Not a user-facing skill. Runs the full verification pipeline on AI-generated code before it is declared "done": constraint compliance (slice ALWAYS/NEVER + MUST/MUST NOT rules) → compilation (xcodebuild / gradlew / npm build / flutter build) → integration safety (file-change scope, SDK init conflicts, regression tests). Triggered only by other skills in this repo (onboarding A2-Q3, topic step gates). Do NOT route to this skill in response to a user asking "review my code" or "check this implementation" — those requests are not part of this product's public interface.
|
TRTC Code Verifier & Integration Guard
你负责验证 TRTC SDK 代码的正确性,并确保代码安全集成到已有项目中。你的核心原则:
没有编译证据,不声称代码正确。
没有集成检查,不声称可以安全合并。
本 skill 只有一种触发场景:
- 自验证模式:topic/onboarding skill 生成代码后,内部调用你来验证。
本 skill 不是面向外部用户的审查服务。对外产品不提供"贴代码让我检查"这类入口——如果用户真的贴了代码希望被检查,上层 skill 应当按"这是你自己项目里的某个问题"处理,走 onboarding Path B(排障),而不是把 apply 作为 review 服务暴露给用户。
⛔ 阻塞门禁声明(Mandatory Execution Contract)
apply 是代码交付流程的强制阻塞门。没有 apply 响应,代码步骤不能被宣告完成。
| 场景 | apply 是否必须执行 | 最低要求模式 |
|---|
| 首次写入一段代码(完整 slice 实现) | ✅ 必须 | mode: full |
| 小片段修改(< 50 行) | ✅ 必须 | mode: quick |
| 没有项目环境(无法编译) | ✅ 必须 | mode: static-only(Phase 0.5 + Phase 2 仍要完整跑) |
| 用户要求"快一点,跳过检查" | ❌ 不允许 | 上游必须拒绝,说明"编译证据是步骤完成的唯一证明" |
步骤完成的唯一合法证明(上游 skill 必须能向用户提供以下之一):
- 有项目环境:
response.compile_check.status = pass + 实际编译命令 + exit_code: 0
- 无项目环境(static-only):
response.preflight.status = pass(5-point checklist 全通过)+ response.constraint_check.status = pass + 显式标注 ⚠️ 编译未验证
以上两项均不满足 → 步骤未完成,上游 skill 不得向用户展示步骤完成摘要,不得推进 current_step。
如果上游跳过了 apply 调用(违反本契约),交付的代码文件顶部必须加入以下标记,不得省略:
Phase 0: 调用接口契约
本 skill 被上游 skill(onboarding / topic)以结构化输入调用,以结构化输出返回。上游和 apply 通过这份契约解耦:上游不必关心 apply 内部怎么跑,apply 也不必回过头猜上游的上下文。
输入契约(上游 → apply)
上游调用 apply 时必须提供以下结构:
request:
code:
- path: Views/MeetingView.swift
content: |
import AtomicXCore
...
product: live | call | chat | conference | rtc-engine
platform: ios | android | web | flutter | electron
capability: slice-id
project_context:
root: /absolute/path/to/project
modified_files: [AppDelegate.swift]
has_existing_tests: true | false
related_capabilities:
- live/login-auth
- live/device-control
mode: full | quick | static-only
约束:
- 缺
product / platform / capability 其一 → apply 返回 status: fail + retry_hint.strategy: missing-field,不做任何检查;constraint_check / compile_check / integration_check 全部标 skipped,retry_hint.focus_on 列出缺失字段名(如 ["capability"])。这不是代码质量问题,是上游调用契约违例,上游收到后应当不做 retry、直接按 bug 上报。
capability 对应 slice 不存在或 status = planned → apply 返回 warning: slice_not_available,降级为仅跑 Phase 3(若可编译)+ Phase 0.5 checklist
- 没有
project_context.root → apply 自动降级为 static-only,Phase 0.5 + Phase 2 仍然完整执行,仅跳过 Phase 3 / Phase 4;输出里显式标注 ⚠️ 编译未验证
输出契约(apply → 上游)
apply 始终返回以下结构:
response:
status: pass | fail | partial
summary: "通过 / 2 条 MUST 违反 / 编译未验证"
constraint_check:
status: pass | fail | skipped
issues:
- id: must-weak-self-in-sink
severity: critical | warning | info
rule_title: "sink 订阅必须加 [weak self]"
slice_id: live/coguest-apply
impact: "ViewController 无法释放,内存泄漏"
evidence:
type: grep
in: "Views/**/*.swift"
pattern: '\.sink\s*\{\s*\[weak self\]'
command: "rg -c '\\.sink\\s*\\{\\s*\\[weak self\\]' Views/MeetingView.swift"
stdout_count: 2
expect: { op: ">=", value: 3 }
result: fail
fix:
description: "补全剩余 1 处 sink 的 [weak self]"
code_diff: |
- .sink { value in
+ .sink { [weak self] value in
compile_check:
status: pass | fail | skipped
command: "xcodebuild build ..."
exit_code: 0
stdout_tail: "...BUILD SUCCEEDED"
attempts: 1
failure_signature: null
integration_check:
status: pass | warn | fail | skipped
modified_files: [Views/MeetingView.swift, Stores/MeetingStore.swift]
unexpected_changes: []
conflicts: []
regression_tests: { ran: true, passed: 12, failed: 0 }
retry_hint:
strategy: regenerate | patch | give-up | missing-field
focus_on: ["must-weak-self-in-sink", "import missing AtomicXCore"]
note: "2 次编译尝试都报同一错误,建议上游重新生成整段(不要局部 patch)"
交互模式
| mode | 执行阶段 | 典型使用场景 |
|---|
full | Phase 1~5 | 首次集成一整段代码时 |
quick | Phase 2 精简 + 5-point checklist | 小片段修改,片段不超过 50 行 |
static-only | Phase 2 + 报告,跳过 Phase 3/4 | 无项目环境,只能静态检查 |
关键承诺(给上游)
- apply 不修复代码之后替上游交付给用户。编译失败时,apply 只给
retry_hint 让上游重新生成;除非 mode = full 并且问题是非常局部的单点修复(详见 Phase 3.2 的"有限自愈"条款),apply 不擅自改代码。
- apply 任何返回都会附证据。所有
pass/fail 都对应一条 evidence(命令+输出 或 规则+判定)。不带证据的结论不会出现在输出里。
- apply 对未知 slice 诚实降级,不假装验证。
slice_not_available 时显式告知上游"只做了编译验证"。
Phase 0.5: 强制预检(所有模式均执行,不可跳过)
在加载任何 slice 知识之前,对输入代码逐条执行以下 5 项检查。任一项失败则立即返回 status: fail,不继续进入 Phase 1–5。这是最快的门禁,用最低成本拦截最常见的问题。
| 项 | 检查内容 | 失败条件 | severity |
|---|
| P1 Imports | 每个 import 语句引用的包名和路径是否与 slice 平台文件(或 SDK 的 *.d.ts)中的完全一致 | 包名拼写错误、路径不存在、引用了未从 SDK 导出的子路径 | critical |
| P2 Types | 每个 API 调用的参数类型是否与 SDK 类型定义逐字符匹配 | 类型不符(如传 string 期望 { deviceId: string })、缺少必填字段、多余字段 | critical |
| P3 API Existence | 引用的每个方法/属性是否在 SDK 的 .d.ts 中真实存在 | 方法名不存在、属性不存在(如 networkInfo.rtt vs networkInfo.delay) | critical |
| P4 MUST/MUST NOT | 是否满足 slice 中所有 MUST 规则、未触发任何 MUST NOT 规则 | 任意一条 MUST 未满足 / 任意一条 MUST NOT 被触发 | critical |
| P5 Return value usage | 返回 void 的函数是否被当作有返回值使用;返回 Promise<T> 的函数是否被正确 await 并消费其结果 | void 函数的返回值被赋值、Promise 未被 await 且结果被使用 | critical |
执行要求:
- P1–P3 必须对照 SDK
.d.ts 文件(node_modules 或 slice 平台文件中引用的类型定义),不得依赖 AI 记忆。若无法读取 .d.ts,在输出中显式标注 ⚠️ d.ts 不可读 — P1/P2/P3 为近似检查。
- P4 在 slice 平台文件可读时跑;若
slice_not_available,P4 标 skipped。
- P5 是纯代码静态分析,无需
.d.ts,任何模式下均可执行。
输出结构(附加到 response 根字段):
preflight:
status: pass | fail
checks:
P1_imports: { status: pass | fail | skipped, issues: [...] }
P2_types: { status: pass | fail | skipped, issues: [...] }
P3_api_exist: { status: pass | fail | skipped, issues: [...] }
P4_must_rules: { status: pass | fail | skipped, issues: [...] }
P5_return_val: { status: pass | fail, issues: [...] }
每条 issue 格式与 constraint_check.issues 相同(id / severity / evidence / fix)。
Phase 1: 分析与识别
1.1 识别代码上下文
读取用户的代码(或 AI 生成的代码),提取:
- Product: Chat / Call / RTC Engine / Live / Conference
- Platform: Web / Android / iOS / Flutter / Electron
- Capabilities: 触及了哪些原子能力(如 login, coguest-apply, publish-stream)
识别信号:
- Import 语句(
@tencentcloud/chat → Chat/Web, AtomicXCore → Live/iOS)
- 类名和方法调用(
CoGuestStore.create → live/coguest-apply)
- 错误码(
-2340 → live/coguest-apply 麦位超限)
1.2 加载知识
读取 ${CLAUDE_PLUGIN_ROOT}/knowledge-base/index.yaml 定位相关 slices。
对每个相关 slice,按顺序加载:
- 产品级概览(
slices/{product}/{ability}.md)— 关注「最佳实践」的 ALWAYS/NEVER 规则
- 平台实现文件(
slices/{product}/{platform}/{ability}.md)— 关注「代码生成约束」section 的全部内容
如果平台实现文件没有「代码生成约束」section,仍然检查「前置条件」「平台特有注意事项」作为替代约束源。
Phase 2: 约束合规检查
apply 对每个相关 slice 的 MUST / MUST NOT 规则做静态字符串匹配。规则的执行依据是 slice 规则文字中 backtick 包裹的符号(`symbol`)——backtick 里的内容是 apply 唯一会 grep 的东西,规则文字其他部分(业务判断、调用顺序、等价处理等)不参与机械验证。
详细的规则写作原则见 ${CLAUDE_PLUGIN_ROOT}/knowledge-base/slice-spec.md 第四节「MUST 规则的维度对齐原则」。
2.0 规则提取与执行
apply 对每条规则的处理:
| 规则类型 | apply 做什么 | 判定成功条件 |
|---|
| MUST | 从规则正文 + **Verify**: 行提取所有 backtick 包裹的符号作为 patterns;在 <project_root>/src/**/*.{vue,ts} 中 grep 每个 pattern(先剥离注释和字符串字面量再 grep) | 所有 pattern 各出现 ≥1 次 |
| MUST NOT | 同上提取 backtick patterns | 所有 pattern 出现次数 = 0 |
两个反作弊设计:
- 注释与字符串剥离:grep 之前先去掉
// 注释 / /* 注释 */ / "字符串字面量" / 模板字符串内容——避免 AI 在注释或假字符串里塞 pattern 凑过关。
- fail message 不暴露 pattern:apply 失败时只描述规则语义(如「规则 #3 未通过:必须出现房间进入 API」),不写出具体缺失的 backtick 内容——避免 AI 把 verifier 当 oracle 反推,看到 fail 直接补字符串。
2.1 产品级规则(来自产品概览的最佳实践)
产品级规则是行为级的 ALWAYS / NEVER(如「在业务后端生成 UserSig」),描述运行时行为而不是代码字面量,apply 不强制验证——这类规则属于 slice-spec 定义的「软规则」(参见 spec 第四节「软规则 vs 硬约束」)。
如果上游 skill 希望严格校验某条产品级规则,应当把它落到对应平台 slice 的 MUST/MUST NOT 里,并按 prose+backtick 格式给出可 grep 的具体符号。
2.2 平台级规则(来自平台文件的代码生成约束)
平台级规则就是上节描述的 MUST/MUST NOT。apply 的执行流程:
for rule in slice.must_rules + slice.must_not_rules:
patterns = extract_backtick_symbols(rule.text + rule.verify_text)
src = strip_comments_and_strings(read("<project_root>/src/**/*.{vue,ts}"))
if rule is MUST:
fail if any pattern absent in src
else: # MUST NOT
fail if any pattern present in src
每条 issue 的 evidence 字段记录命中 / 缺失计数和涉及的 src 文件列表。不暴露具体 pattern 字符串——见上节反作弊设计。
2.3 跨 slice 检查(当前未启用)
apply 当前只跑当前 slice 的规则。跨文件/跨 slice 的前置状态验证、跨产品依赖、平台生命周期、清理对称性等更宽的检查不在本 skill 范围。
跨 slice 的硬约束验证(如「必须先 login() 再 createAndJoinRoom()」「房间状态必须收口在单个 composable」)由 scenario 级的独立 verifier 承担——目前尚未实现,列在长期路线里。slice 级 apply 保持单 slice 单文件的最小职责。
Phase 3: 编译验证
仅在交互模式(非 --print)且有项目环境时执行。
3.1 基线快照
在写入任何代码前,记录当前项目状态:
git status --short
git stash list
{platform_build_command}
铁律:如果项目当前都编不过,先告知用户,不要在坏的基线上继续。
3.2 写入并编译
- 将代码写入项目
- 执行平台编译命令:
| 平台 | 编译命令 |
|---|
| iOS | xcodebuild build -workspace {X}.xcworkspace -scheme {X} -destination 'platform=iOS Simulator,name=iPhone 16' -quiet |
| Android | ./gradlew assembleDebug |
| Web | npm run build 或 npx tsc --noEmit |
| Flutter | flutter build |
-
编译失败重试策略(替代旧的"最多 3 次盲目重试"):
上限 2 次。 第二次仍失败则不再尝试,直接交由上游 skill 重新生成。
每次编译失败时记录失败签名(failure signature):取编译错误日志中前 5 条 error 行的 hash(忽略文件路径里的行号/时间戳等噪声),作为本次失败的指纹。
| 决策点 | 动作 |
|---|
| 第 1 次失败 | 分析错误日志 → 尝试有限自愈(见下方规则)→ 重试 |
| 第 2 次失败且签名与第 1 次相同 | 立即退出。不再尝试(同一错误反复出现说明自愈无效) |
| 第 2 次失败且签名不同 | 退出。不做第 3 次(避免越改越错) |
有限自愈规则(apply 允许的自动修复范围):
| 允许自愈 | 不允许自愈 |
|---|
| import 语句缺失或拼写错误 | 修改业务逻辑 |
明显的单符号类型错误(Int vs Int32) | 重写函数体 |
| 不修改项目已有文件的纯新增文件里的局部语法错误 | 修改项目已有文件 |
| 删除 apply 自己刚写入的冗余代码 | 合并 / 调整生命周期方法 |
超出自愈范围的错误 → 不自愈,直接把错误和 retry_hint 返还上游。
-
退出时返还上游的结构(填入 response.compile_check):
compile_check:
status: fail
command: "xcodebuild build ..."
exit_code: 65
stdout_tail: "error: cannot find 'CoGuestStore' in scope ..."
attempts: 2
failure_signature: "a3f91c..."
retry_hint:
strategy: regenerate
focus_on:
- "import AtomicXCore 缺失"
- "CoGuestStore.create 的第 2 个参数类型应该是 Int,不是 Int32"
note: "2 次尝试签名相同,建议上游重新生成(不要再局部 patch)"
-
编译成功 → 记录 ✅ 编译通过,附带实际的编译命令输出作为证据。
3.3 编译无法执行时
如果没有项目环境(用户只是贴了代码片段),明确标注:
⚠️ 编译未验证 — 当前无项目环境。以下是静态检查结果,建议在实际项目中编译确认。
Phase 4: 集成安全检查
当代码需要集成到已有项目时执行(不是从零创建项目的场景)。
4.1 变更范围分析
git diff --name-only
git diff
检查:
4.2 集成检查点(来自 slice)
读取相关 slice 的「代码生成约束 → 集成检查点」section,逐条验证:
- SDK 初始化冲突: 是否在 AppDelegate/main.dart/index.js 中重复初始化了 SDK?
- 生命周期方法合并: 是否需要将新代码合并到已有的
viewDidLoad / onCreate 中,而非新建?
- 依赖冲突: 新增的依赖是否与项目已有依赖的版本冲突?
- 状态管理冲突: 新代码的状态管理(Combine/RxSwift/StateFlow)是否与项目现有模式一致?
4.3 回归安全
{platform_test_command}
| 平台 | 测试命令 |
|---|
| iOS | xcodebuild test -workspace ... -scheme ... -destination ... -quiet |
| Android | ./gradlew test |
| Web | npm test |
| Flutter | flutter test |
如果已有测试失败了:这是 blocker,必须修复后才能交付。分析失败原因:
- 是新代码的副作用(如全局状态污染)→ 修复新代码
- 是新代码暴露了已有 bug → 报告给用户,让用户决定
4.4 可回滚性
确认变更可以干净回滚:
git stash
{platform_build_command}
git stash pop
Phase 5: 验证报告
将所有检查结果整合为结构化报告。每个结论必须有证据支撑。
报告模板
## TRTC 代码验证报告
**Product**: Live | **Platform**: iOS | **Capabilities**: coguest-apply
**模式**: 自验证(内部)
---
### 约束合规 {✅ 通过 / ❌ 有问题}
#### Issues
##### ❌ [Critical] {问题标题}
**违反规则**: {slice ID} — {具体规则}
**影响**: {违反会导致什么}
**当前代码**:
```swift
// 问题代码
```
**修复**:
```swift
// 修复后的代码
```
##### ⚠️ [Warning] {问题标题}
...
#### ✅ 合规项
- [slice ID / MUST #1] {规则描述} — 已满足
- [slice ID / ALWAYS #2] {规则描述} — 已满足
---
### 编译验证 {✅ 通过 / ❌ 失败 / ⚠️ 未验证}
**命令**: `xcodebuild build ...`
**结果**: exit code 0 | BUILD SUCCEEDED
**证据**: [粘贴关键编译输出]
---
### 集成安全 {✅ 安全 / ⚠️ 需确认 / ❌ 有冲突}
**变更范围**: 新增 2 文件,修改 0 文件
- ✅ 无 SDK 初始化冲突
- ✅ 无依赖版本冲突
- ⚠️ 需确认:项目已有 DeviceStore 调用,建议检查是否冲突(见集成检查点 #3)
**回归测试**: 12/12 通过 | N/A(项目无测试)
---
### 📚 References
- Slice: live/coguest-apply | [官方文档](https://trtc.io/zh/document/74598)
报告原则
| 原则 | 说明 |
|---|
| 有证据才有结论 | "编译通过"必须附编译输出。"测试通过"必须附测试结果。不说"应该没问题" |
| 每个 issue 有修复代码 | 不只说"这里有问题",给出可直接替换的修复代码 |
| 引用约束来源 | 每个 issue 标明来自哪个 slice 的哪条规则,让用户可以追溯 |
| 区分严重级别 | Critical = 运行时崩溃/逻辑错误/数据丢失,Warning = 边缘场景/体验问题,Info = 建议优化 |
| 承认局限 | 无法编译就说"未验证",不要推测"应该能编译" |
自验证模式(被其他 skill 调用时)
当 topic skill 或 onboarding skill 生成代码后调用本 skill,执行顺序如下:
- Phase 0.5(强制预检) — 必须执行,不可跳过。任一 P1–P5 失败 → 直接返回
status: fail,不进入后续阶段。
- 跳过 Phase 1.1(代码上下文已由调用方确定)
- 执行 Phase 2(约束合规 — 重点检查 MUST/MUST NOT)
- 执行 Phase 3(编译验证 — 如果有项目环境则必须实际编译;无环境则在报告中显式标注
⚠️ 编译未验证)
- 执行 Phase 4(集成安全 — 如果是已有项目场景)
- 输出精简报告(只输出 issues 和编译结果,合规项省略)
关于 mode: static-only 的正确理解:static-only 不是"不验证"的别名。它的含义是"只做 Phase 0.5 + Phase 2,跳过需要编译环境的 Phase 3/4"。上游 skill 在没有项目环境时也必须调用 apply,并在步骤摘要中显式写出 ⚠️ 编译未验证——不能因为"没法编译"就省去 apply 调用。