@tencent-rtc/trtc-agent-skills 0.1.0 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/.cursor/rules/ui-mode.mdc +99 -0
  2. package/AGENTS.md +111 -0
  3. package/CLAUDE.md +133 -0
  4. package/CODEBUDDY.md +131 -0
  5. package/README.md +28 -42
  6. package/README.zh.md +24 -42
  7. package/ai-instructions/base.md +13 -0
  8. package/ai-instructions/ui-mode.md +93 -0
  9. package/bin/cli.js +428 -13
  10. package/hooks/cursor-adapter.py +315 -0
  11. package/hooks/hooks-cursor.json +39 -0
  12. package/hooks/hooks.json +67 -0
  13. package/knowledge-base/index.yaml +8 -0
  14. package/knowledge-base/scenarios/conference/base/general-conference.md +6 -3
  15. package/knowledge-base/slices/conference/web/integration-audit.md +155 -0
  16. package/knowledge-base/slices/conference/web/login-auth.md +2 -1
  17. package/knowledge-base/slices/conference/web/official-roomkit-login-ui.md +41 -13
  18. package/knowledge-base/slices/conference/web/prejoin-check.md +8 -5
  19. package/package.json +7 -1
  20. package/skills/trtc/SKILL.md +7 -6
  21. package/skills/trtc/room-builder/SKILL.md +4 -9
  22. package/skills/trtc/room-builder/assets/local-usersig/basic-info-config.ts +39 -0
  23. package/skills/trtc/room-builder/assets/local-usersig/lib-generate-test-usersig-es.min.d.ts +4 -0
  24. package/skills/trtc/room-builder/assets/local-usersig/lib-generate-test-usersig-es.min.js +2 -0
  25. package/skills/trtc-onboarding/SKILL.md +35 -33
  26. package/skills/trtc-onboarding/reference/path-a2-integrate.md +101 -57
  27. package/skills/trtc-onboarding/reference/path-b-troubleshoot.md +139 -68
  28. package/skills/trtc-onboarding/reference/reporting-protocol.md +2 -0
  29. package/skills/trtc-onboarding/reference/usersig-handling.md +98 -104
  30. package/skills/trtc-search/SKILL.md +7 -0
  31. package/skills/trtc-topic/SKILL.md +26 -42
  32. package/skills/trtc-apply/guardrails/apply_lib/__pycache__/__init__.cpython-313.pyc +0 -0
  33. package/skills/trtc-apply/guardrails/apply_lib/__pycache__/rule_parser.cpython-313.pyc +0 -0
  34. package/skills/trtc-topic/guardrails/__pycache__/gate_slice_read.cpython-313.pyc +0 -0
  35. package/skills/trtc-topic/guardrails/__pycache__/gate_slice_write.cpython-313.pyc +0 -0
  36. package/skills/trtc-topic/guardrails/__pycache__/stop_require_apply_evidence.cpython-313.pyc +0 -0
  37. package/skills/trtc-topic/scripts/__pycache__/apply.cpython-313.pyc +0 -0
  38. package/skills/trtc-topic/scripts/lib/__pycache__/state_machine.cpython-313.pyc +0 -0
  39. package/skills/trtc-topic/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc +0 -0
  40. package/skills/trtc-topic/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  41. package/skills/trtc-topic/tests/__pycache__/test_apply_cli.cpython-313-pytest-9.0.2.pyc +0 -0
  42. package/skills/trtc-topic/tests/__pycache__/test_apply_cli.cpython-313-pytest-9.0.3.pyc +0 -0
  43. package/skills/trtc-topic/tests/__pycache__/test_end_to_end.cpython-313-pytest-9.0.2.pyc +0 -0
  44. package/skills/trtc-topic/tests/__pycache__/test_end_to_end.cpython-313-pytest-9.0.3.pyc +0 -0
  45. package/skills/trtc-topic/tests/__pycache__/test_finalize_session.cpython-313-pytest-9.0.2.pyc +0 -0
  46. package/skills/trtc-topic/tests/__pycache__/test_finalize_session.cpython-313-pytest-9.0.3.pyc +0 -0
  47. package/skills/trtc-topic/tests/__pycache__/test_gates.cpython-313-pytest-9.0.2.pyc +0 -0
  48. package/skills/trtc-topic/tests/__pycache__/test_gates.cpython-313-pytest-9.0.3.pyc +0 -0
  49. package/skills/trtc-topic/tests/__pycache__/test_session_resolver.cpython-313-pytest-9.0.2.pyc +0 -0
  50. package/skills/trtc-topic/tests/__pycache__/test_session_resolver.cpython-313-pytest-9.0.3.pyc +0 -0
  51. package/skills/trtc-topic/tests/__pycache__/test_state_machine.cpython-313-pytest-9.0.2.pyc +0 -0
  52. package/skills/trtc-topic/tests/__pycache__/test_state_machine.cpython-313-pytest-9.0.3.pyc +0 -0
  53. package/skills/trtc-topic/tests/__pycache__/test_stop_require_apply.cpython-313-pytest-9.0.2.pyc +0 -0
  54. package/skills/trtc-topic/tests/__pycache__/test_stop_require_apply.cpython-313-pytest-9.0.3.pyc +0 -0
  55. package/skills/trtc-topic/tests/__pycache__/test_topic_skill_invariants.cpython-313-pytest-9.0.2.pyc +0 -0
  56. package/skills/trtc-topic/tests/__pycache__/test_topic_skill_invariants.cpython-313-pytest-9.0.3.pyc +0 -0
@@ -0,0 +1,315 @@
1
+ #!/usr/bin/env python3
2
+ """cursor-adapter.py — Translate Cursor hook protocol to existing Claude-format guardrails.
3
+
4
+ Wired from hooks-cursor.json. Each Cursor hook event runs:
5
+
6
+ python3 ./hooks/cursor-adapter.py <dispatch-key>
7
+
8
+ This script:
9
+ 1. Reads the Cursor hook envelope from stdin (camelCase fields).
10
+ 2. Normalizes it to the {tool_name, tool_input} shape the existing
11
+ guardrail scripts in skills/*/guardrails/ expect.
12
+ 3. Sets CLAUDE_PLUGIN_ROOT and CLAUDE_PROJECT_DIR env vars so the
13
+ existing scripts find sessions and shared libs unchanged.
14
+ 4. Spawns the underlying script and propagates stdout/stderr.
15
+ 5. Translates exit codes:
16
+ Claude Code: 0=allow, 1=fail, 2=block
17
+ Cursor: 0=success, 2=deny (with JSON), other=fail-open
18
+ We map Claude exit 1 and exit 2 -> Cursor deny (exit 2 + JSON on stdout).
19
+
20
+ Fail-open principle: if anything goes wrong inside the adapter (missing
21
+ script, malformed stdin, dispatch key unknown), we exit 0 silently so a
22
+ broken adapter never blocks the user's workflow.
23
+
24
+ Cursor event mapping (see hooks-cursor.json):
25
+ sessionStart -> trtc-prepare-ui
26
+ beforeReadFile -> gate-slice-read
27
+ preToolUse -> gate-slice-write (filtered to Write/Edit inside)
28
+ afterFileEdit -> verify-ui-post-write, verify-slice-must
29
+ stop -> stop-apply-evidence, trtc-verify-ui, verify-apply-project
30
+ (Claude Code's `Stop` event fires per agent loop
31
+ iteration end. Cursor's `stop` event has the same
32
+ semantic ("Called when the agent loop ends") and
33
+ uniquely supports {"followup_message": "..."} on
34
+ stdout to auto-resubmit a corrective user turn —
35
+ which is exactly what these end-of-task guardrails
36
+ need so the agent can self-correct. Auto-resubmit
37
+ is capped at 5 iterations by Cursor's loop_limit.
38
+
39
+ Historical note: a previous revision of this
40
+ adapter mapped these to `afterAgentResponse`
41
+ because the original author observed `stop` not
42
+ firing. Empirically (Cursor 3.3.8) `stop` does
43
+ fire reliably; the original "stop never fires"
44
+ observation was caused by hooks not being loaded
45
+ at all, not by the `stop` event itself. See
46
+ install instructions in the README for the
47
+ documented user-level `~/.cursor/hooks.json`
48
+ install path — plugin-level hooks declared in
49
+ .cursor-plugin/plugin.json are NOT loaded by
50
+ current Cursor versions.)
51
+
52
+ To enable verbose tracing during development, set the env var
53
+ TRTC_HOOK_DEBUG_LOG=/path/to/file.log; see _dbg() below. The default is
54
+ silent (no log file written) so production installs don't accumulate logs.
55
+ """
56
+ from __future__ import annotations
57
+
58
+ import json
59
+ import os
60
+ import subprocess
61
+ import sys
62
+ from pathlib import Path
63
+
64
+ ADAPTER_DIR = Path(__file__).resolve().parent
65
+ PLUGIN_ROOT = ADAPTER_DIR.parent # plugin install root
66
+
67
+
68
+ # Optional debug logging — only writes when TRTC_HOOK_DEBUG_LOG is set to a
69
+ # path. Useful for diagnosing "why didn't my hook fire" without modifying
70
+ # the script.
71
+ def _dbg(msg: str) -> None:
72
+ log_path = os.environ.get("TRTC_HOOK_DEBUG_LOG")
73
+ if not log_path:
74
+ return
75
+ try:
76
+ import datetime as _dt
77
+ with open(log_path, "a") as f:
78
+ f.write(f"[{_dt.datetime.now().isoformat(timespec='milliseconds')}] {msg}\n")
79
+ except Exception:
80
+ pass
81
+
82
+
83
+ # Always-on probe log: writes one line per adapter invocation to
84
+ # /tmp/trtc-hook-probe.log so we can confirm which Cursor hook events
85
+ # actually fire end-to-end. Off by default to avoid unbounded log files
86
+ # in production installs; enable by setting env TRTC_HOOK_PROBE=1.
87
+ def _probe_log(key, payload) -> None:
88
+ if os.environ.get("TRTC_HOOK_PROBE") != "1":
89
+ return
90
+ try:
91
+ import datetime as _dt
92
+ ev = (payload or {}).get("hook_event_name", "?")
93
+ with open("/tmp/trtc-hook-probe.log", "a") as f:
94
+ f.write(
95
+ f"[{_dt.datetime.now().isoformat(timespec='milliseconds')}] "
96
+ f"event={ev!r} key={key!r} "
97
+ f"pid={os.getpid()} "
98
+ f"cwd={os.environ.get('CURSOR_PROJECT_DIR') or os.getcwd()!r}\n"
99
+ )
100
+ except Exception:
101
+ pass
102
+
103
+
104
+ # Dispatch table: dispatch_key -> relative script path under PLUGIN_ROOT.
105
+ DISPATCH = {
106
+ "trtc-prepare-ui": "skills/trtc/room-builder/guardrails/trtc_prepare_ui.py",
107
+ "gate-slice-read": "skills/trtc-topic/guardrails/gate_slice_read.py",
108
+ "gate-slice-write": "skills/trtc-topic/guardrails/gate_slice_write.py",
109
+ "verify-ui-post-write": "skills/trtc/room-builder/guardrails/verify_ui_post_write.sh",
110
+ "verify-slice-must": "skills/trtc-apply/guardrails/verify_slice_must_rules.py",
111
+ "stop-apply-evidence": "skills/trtc-topic/guardrails/stop_require_apply_evidence.py",
112
+ "trtc-verify-ui": "skills/trtc/room-builder/guardrails/trtc_verify_ui.py",
113
+ "verify-apply-project": "skills/trtc-apply/guardrails/verify_apply_project.py",
114
+ }
115
+
116
+ # Dispatch keys whose underlying scripts read JSON from stdin.
117
+ # Other keys are CLI-only and will receive empty stdin.
118
+ STDIN_KEYS = {
119
+ "gate-slice-read",
120
+ "gate-slice-write",
121
+ "verify-ui-post-write",
122
+ "verify-slice-must",
123
+ }
124
+
125
+
126
+ def _silent_allow() -> None:
127
+ """Fail-open: never block the user because the adapter itself failed."""
128
+ _dbg(" EXIT=0 (silent allow)")
129
+ sys.exit(0)
130
+
131
+
132
+ def _read_cursor_payload() -> dict:
133
+ raw = sys.stdin.read()
134
+ if not raw or not raw.strip():
135
+ return {}
136
+ try:
137
+ data = json.loads(raw)
138
+ return data if isinstance(data, dict) else {}
139
+ except (ValueError, TypeError):
140
+ return {}
141
+
142
+
143
+ def _translate_payload(key: str, cursor: dict) -> dict:
144
+ """Map Cursor's per-event payload to the {tool_name, tool_input} envelope
145
+ the existing guardrails expect.
146
+
147
+ The existing scripts read either:
148
+ - {"tool_name": "Read", "tool_input": {"file_path": "..."}}
149
+ - {"tool_name": "Write|Edit", "tool_input": {"file_path": "..."}}
150
+ """
151
+ if key == "gate-slice-read":
152
+ # Cursor's beforeReadFile payload: {file_path, content, attachments, ...}
153
+ file_path = cursor.get("file_path") or cursor.get("filePath")
154
+ return {"tool_name": "Read", "tool_input": {"file_path": file_path}}
155
+
156
+ if key == "gate-slice-write":
157
+ # Cursor's preToolUse payload: {tool_name, tool_input, tool_use_id, cwd, ...}
158
+ # Cursor only emits "Write" (no separate "Edit"); existing script
159
+ # accepts both. Pass through unchanged when tool_name is Write/Edit;
160
+ # silent allow for unrelated tools.
161
+ tool_name = cursor.get("tool_name") or cursor.get("toolName")
162
+ if tool_name not in ("Write", "Edit"):
163
+ return {} # caller will silent-allow
164
+ tool_input = cursor.get("tool_input") or cursor.get("toolInput") or {}
165
+ return {"tool_name": tool_name, "tool_input": tool_input}
166
+
167
+ if key in ("verify-ui-post-write", "verify-slice-must"):
168
+ # Cursor's afterFileEdit payload: {file_path, edits}
169
+ file_path = cursor.get("file_path") or cursor.get("filePath")
170
+ return {"tool_name": "Edit", "tool_input": {"file_path": file_path}}
171
+
172
+ # CLI-only events (sessionStart, afterAgentResponse) get empty stdin —
173
+ # they read state from session files and env vars, not stdin.
174
+ return {}
175
+
176
+
177
+ def _emit_cursor_deny(message: str) -> None:
178
+ """Cursor deny protocol for events that honor blocking (preToolUse,
179
+ beforeReadFile, afterFileEdit): JSON on stdout + exit 2.
180
+
181
+ Fields populated:
182
+ - permission: "deny" — tells Cursor this action should be blocked.
183
+ - user_message: surfaced in Cursor's UI on events that support it.
184
+ - agent_message: shown to the agent so it can self-correct on hooks
185
+ that run before the agent's loop ends (preToolUse, beforeReadFile).
186
+
187
+ NOTE: do not call this for the `stop` event. Cursor docs confirm only
188
+ `followup_message` is honored on stop; use _emit_cursor_followup()
189
+ instead so the agent gets re-prompted to fix the issue.
190
+ """
191
+ _dbg(f" EXIT=2 (deny) message={message[:160]!r}")
192
+ msg = message or "Blocked by TRTC guardrail."
193
+ payload = {
194
+ "permission": "deny",
195
+ "user_message": msg,
196
+ "agent_message": msg,
197
+ }
198
+ sys.stdout.write(json.dumps(payload))
199
+ sys.stdout.flush()
200
+ sys.exit(2)
201
+
202
+
203
+ def _emit_cursor_followup(message: str) -> None:
204
+ """Cursor stop-event protocol: emit {"followup_message": ...} on stdout
205
+ and exit 0. Cursor will automatically submit the message as the next
206
+ user turn, giving the agent a chance to self-correct.
207
+
208
+ Per Cursor docs, the stop event only honors `followup_message`; other
209
+ output fields (permission, user_message, agent_message) are accepted by
210
+ the schema but not enforced by callers. Auto-resubmission is capped by
211
+ `loop_limit` (default 5) so a guardrail that keeps failing won't loop
212
+ forever.
213
+
214
+ Exit 0 (not 2): we want Cursor to resubmit, not block. Exit 2 on stop
215
+ just halts with no chance for the agent to fix the problem.
216
+ """
217
+ msg = (message or "TRTC guardrail failed; please review and fix.").strip()
218
+ # Cursor caps message length on some events; keep this generous but bounded
219
+ # so we don't ship a 50KB stderr trace as the next user message.
220
+ if len(msg) > 4000:
221
+ msg = msg[:4000] + "\n…(truncated)"
222
+ _dbg(f" EXIT=0 (followup) message={msg[:160]!r}")
223
+ payload = {"followup_message": msg}
224
+ sys.stdout.write(json.dumps(payload))
225
+ sys.stdout.flush()
226
+ sys.exit(0)
227
+
228
+
229
+ def main(argv: list[str]) -> None:
230
+ key = argv[1] if len(argv) >= 2 else None
231
+ _dbg(
232
+ f"ENTER key={key!r} "
233
+ f"cursor_project_dir={os.environ.get('CURSOR_PROJECT_DIR', '?')!r}"
234
+ )
235
+ if not key:
236
+ _silent_allow()
237
+
238
+ # Read stdin first so debug logs see Cursor's hook_event_name even when
239
+ # the dispatch key is unknown or the script is missing.
240
+ cursor_payload = _read_cursor_payload()
241
+ _probe_log(key, cursor_payload)
242
+ _dbg(
243
+ f" hook_event_name={cursor_payload.get('hook_event_name', '?')!r} "
244
+ f"keys={sorted(cursor_payload.keys()) if cursor_payload else []}"
245
+ )
246
+
247
+ rel_script = DISPATCH.get(key)
248
+ if not rel_script:
249
+ # Unknown dispatch key — never block on adapter misconfiguration.
250
+ _silent_allow()
251
+
252
+ script = PLUGIN_ROOT / rel_script
253
+ if not script.exists():
254
+ # Script missing (e.g., skill not installed). Same convention the
255
+ # existing hooks.json uses ("[ ! -f X ] || python3 X").
256
+ _silent_allow()
257
+
258
+ claude_payload = _translate_payload(key, cursor_payload)
259
+
260
+ # gate-slice-write returns {} when tool isn't Write/Edit -> silent allow.
261
+ if key == "gate-slice-write" and not claude_payload:
262
+ _silent_allow()
263
+
264
+ # Build env: surface Cursor's project dir as CLAUDE_PROJECT_DIR so
265
+ # existing scripts that key off CLAUDE_PROJECT_DIR keep working.
266
+ env = dict(os.environ)
267
+ cursor_project_dir = env.get("CURSOR_PROJECT_DIR") or cursor_payload.get("cwd")
268
+ if cursor_project_dir and not env.get("CLAUDE_PROJECT_DIR"):
269
+ env["CLAUDE_PROJECT_DIR"] = cursor_project_dir
270
+ env["CLAUDE_PLUGIN_ROOT"] = str(PLUGIN_ROOT)
271
+
272
+ runner = ["bash", str(script)] if script.suffix == ".sh" else ["python3", str(script)]
273
+ stdin_data = json.dumps(claude_payload) if key in STDIN_KEYS else ""
274
+
275
+ try:
276
+ proc = subprocess.run(
277
+ runner,
278
+ input=stdin_data,
279
+ text=True,
280
+ env=env,
281
+ capture_output=True,
282
+ timeout=30,
283
+ )
284
+ except (subprocess.TimeoutExpired, OSError):
285
+ _silent_allow()
286
+
287
+ # Forward whatever the inner script wrote.
288
+ if proc.stdout:
289
+ sys.stderr.write(proc.stdout) # inner stdout -> stderr so we don't
290
+ # corrupt our Cursor JSON channel
291
+ if proc.stderr:
292
+ sys.stderr.write(proc.stderr)
293
+
294
+ rc = proc.returncode
295
+ _dbg(f" inner_rc={rc}")
296
+ if rc == 0:
297
+ sys.exit(0)
298
+ if rc in (1, 2):
299
+ # Translate fail/block into Cursor's response. Use stderr content as
300
+ # the agent-facing message so the existing scripts' human-readable
301
+ # explanations reach the user.
302
+ msg = (proc.stderr or proc.stdout or "").strip()
303
+ # On the `stop` event Cursor honors only `followup_message` (which
304
+ # auto-resubmits a corrective user turn). On preToolUse /
305
+ # beforeReadFile / afterFileEdit, the deny envelope is enforced.
306
+ if cursor_payload.get("hook_event_name") == "stop":
307
+ _emit_cursor_followup(msg)
308
+ _emit_cursor_deny(msg)
309
+
310
+ # Unknown exit code — fail open to mimic Claude Code's permissive default.
311
+ _silent_allow()
312
+
313
+
314
+ if __name__ == "__main__":
315
+ main(sys.argv)
@@ -0,0 +1,39 @@
1
+ {
2
+ "version": 1,
3
+ "hooks": {
4
+ "sessionStart": [
5
+ {
6
+ "command": "python3 $HOME/.cursor/plugins/local/trtc-agent-skills/hooks/cursor-adapter.py trtc-prepare-ui"
7
+ }
8
+ ],
9
+ "beforeReadFile": [
10
+ {
11
+ "command": "python3 $HOME/.cursor/plugins/local/trtc-agent-skills/hooks/cursor-adapter.py gate-slice-read"
12
+ }
13
+ ],
14
+ "preToolUse": [
15
+ {
16
+ "command": "python3 $HOME/.cursor/plugins/local/trtc-agent-skills/hooks/cursor-adapter.py gate-slice-write"
17
+ }
18
+ ],
19
+ "afterFileEdit": [
20
+ {
21
+ "command": "python3 $HOME/.cursor/plugins/local/trtc-agent-skills/hooks/cursor-adapter.py verify-ui-post-write"
22
+ },
23
+ {
24
+ "command": "python3 $HOME/.cursor/plugins/local/trtc-agent-skills/hooks/cursor-adapter.py verify-slice-must"
25
+ }
26
+ ],
27
+ "stop": [
28
+ {
29
+ "command": "python3 $HOME/.cursor/plugins/local/trtc-agent-skills/hooks/cursor-adapter.py stop-apply-evidence"
30
+ },
31
+ {
32
+ "command": "python3 $HOME/.cursor/plugins/local/trtc-agent-skills/hooks/cursor-adapter.py trtc-verify-ui"
33
+ },
34
+ {
35
+ "command": "python3 $HOME/.cursor/plugins/local/trtc-agent-skills/hooks/cursor-adapter.py verify-apply-project"
36
+ }
37
+ ]
38
+ }
39
+ }
@@ -0,0 +1,67 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [
4
+ {
5
+ "hooks": [
6
+ {
7
+ "type": "command",
8
+ "command": "[ ! -f \"${CLAUDE_PLUGIN_ROOT:-${CODEBUDDY_PLUGIN_ROOT}}/skills/trtc/room-builder/guardrails/trtc_prepare_ui.py\" ] || python3 \"${CLAUDE_PLUGIN_ROOT:-${CODEBUDDY_PLUGIN_ROOT}}/skills/trtc/room-builder/guardrails/trtc_prepare_ui.py\""
9
+ }
10
+ ]
11
+ }
12
+ ],
13
+ "PreToolUse": [
14
+ {
15
+ "matcher": "Read",
16
+ "hooks": [
17
+ {
18
+ "type": "command",
19
+ "command": "[ ! -f \"${CLAUDE_PLUGIN_ROOT:-${CODEBUDDY_PLUGIN_ROOT}}/skills/trtc-topic/guardrails/gate_slice_read.py\" ] || python3 \"${CLAUDE_PLUGIN_ROOT:-${CODEBUDDY_PLUGIN_ROOT}}/skills/trtc-topic/guardrails/gate_slice_read.py\""
20
+ }
21
+ ]
22
+ },
23
+ {
24
+ "matcher": "Write|Edit",
25
+ "hooks": [
26
+ {
27
+ "type": "command",
28
+ "command": "[ ! -f \"${CLAUDE_PLUGIN_ROOT:-${CODEBUDDY_PLUGIN_ROOT}}/skills/trtc-topic/guardrails/gate_slice_write.py\" ] || python3 \"${CLAUDE_PLUGIN_ROOT:-${CODEBUDDY_PLUGIN_ROOT}}/skills/trtc-topic/guardrails/gate_slice_write.py\""
29
+ }
30
+ ]
31
+ }
32
+ ],
33
+ "PostToolUse": [
34
+ {
35
+ "matcher": "Write|Edit",
36
+ "hooks": [
37
+ {
38
+ "type": "command",
39
+ "command": "[ ! -f \"${CLAUDE_PLUGIN_ROOT:-${CODEBUDDY_PLUGIN_ROOT}}/skills/trtc/room-builder/guardrails/verify_ui_post_write.sh\" ] || bash \"${CLAUDE_PLUGIN_ROOT:-${CODEBUDDY_PLUGIN_ROOT}}/skills/trtc/room-builder/guardrails/verify_ui_post_write.sh\""
40
+ },
41
+ {
42
+ "type": "command",
43
+ "command": "[ ! -f \"${CLAUDE_PLUGIN_ROOT:-${CODEBUDDY_PLUGIN_ROOT}}/skills/trtc-apply/guardrails/verify_slice_must_rules.py\" ] || python3 \"${CLAUDE_PLUGIN_ROOT:-${CODEBUDDY_PLUGIN_ROOT}}/skills/trtc-apply/guardrails/verify_slice_must_rules.py\""
44
+ }
45
+ ]
46
+ }
47
+ ],
48
+ "Stop": [
49
+ {
50
+ "hooks": [
51
+ {
52
+ "type": "command",
53
+ "command": "[ ! -f \"${CLAUDE_PLUGIN_ROOT:-${CODEBUDDY_PLUGIN_ROOT}}/skills/trtc-topic/guardrails/stop_require_apply_evidence.py\" ] || python3 \"${CLAUDE_PLUGIN_ROOT:-${CODEBUDDY_PLUGIN_ROOT}}/skills/trtc-topic/guardrails/stop_require_apply_evidence.py\""
54
+ },
55
+ {
56
+ "type": "command",
57
+ "command": "[ ! -f \"${CLAUDE_PLUGIN_ROOT:-${CODEBUDDY_PLUGIN_ROOT}}/skills/trtc/room-builder/guardrails/trtc_verify_ui.py\" ] || python3 \"${CLAUDE_PLUGIN_ROOT:-${CODEBUDDY_PLUGIN_ROOT}}/skills/trtc/room-builder/guardrails/trtc_verify_ui.py\""
58
+ },
59
+ {
60
+ "type": "command",
61
+ "command": "[ ! -f \"${CLAUDE_PLUGIN_ROOT:-${CODEBUDDY_PLUGIN_ROOT}}/skills/trtc-apply/guardrails/verify_apply_project.py\" ] || python3 \"${CLAUDE_PLUGIN_ROOT:-${CODEBUDDY_PLUGIN_ROOT}}/skills/trtc-apply/guardrails/verify_apply_project.py\""
62
+ }
63
+ ]
64
+ }
65
+ ]
66
+ }
67
+ }
@@ -331,6 +331,14 @@ slices:
331
331
  description: 官方 RoomKit 集成模式下 conference 对象的完整 API 签名、枚举定义、调用时序和代码示例(ui_mode=official-roomkit 专用)
332
332
  status: active
333
333
 
334
+ - id: conference/integration-audit
335
+ name: Conference Web 集成审计
336
+ tags: [audit, checklist, meta, integration-review, roomkit, online-classroom]
337
+ platforms: [web]
338
+ file: slices/conference/web/integration-audit.md
339
+ description: 横切对照官方 Conference Web 接入流程的审计清单;Path B intent F 使用,不进 scenario 集成队列
340
+ status: active
341
+
334
342
  # ============================================================
335
343
  # Scenarios — 场景组合索引
336
344
  # ============================================================
@@ -236,12 +236,15 @@ slices:
236
236
  ```
237
237
  我帮你定位到「通用会议」场景。会议骨架(登录、会前预览、房间生命周期、成员列表、画面布局、设备控制、网络质量)会默认集成。
238
238
 
239
- 你的需求里我识别到要这些增强能力(已勾选):{命中的可选模块中文名}
239
+ 你的需求里我识别到要这些增强能力:{命中的可选模块中文名}
240
240
 
241
- 还要加别的吗?(可多选;如果都不需要,请选择「以上都不需要」)
241
+ 以上无误吗?
242
242
  ```
243
243
 
244
- 3. **AskUserQuestion**:对「可选模块」做一次多选(`multiSelect: true`),选项 label 用上面的中文名,**预勾选第 1 步命中的项**。一次超过 4 项时按"会中协作 / 视频效果 / 预约邀请"分组拆成多次问(遵守 4 选项上限)。**每组多选必须包含一个「以上都不需要」选项(value=`none`),放在选项列表末尾,作为用户显式拒绝该组所有能力的出口。选中`none`时,该组其他选项视为未勾选;同时选中`none`和其他选项时,以`none`为准(清空其他选择)。**
244
+ 3a. **确认优先(`target_features` 已明确时)**:若第 1 步命中的可选模块非空且映射清晰,用 `AskUserQuestion` 单次确认:(1) 确认,按此集成 直接写 `confirmed_plan`;(2) 需要调整 再走下方多选。不要把用户已在 prompt 里点名的能力再让用户选一遍。
245
+
246
+ 3b. **多选(仅当 `target_features` 为空/模糊,或用户选「需要调整」)**:对「可选模块」做 `multiSelect: true` 多选,预勾选第 1 步命中的项。一次超过 4 项时分组。每组末尾含「以上都不需要」(`none`)。
247
+
245
248
  4. **写 `confirmed_plan`**:`confirmed_plan = 必装骨架 + 用户最终选中的可选模块`(去重,保持上面声明的顺序)。这是 `init_slice_queue.py` 的唯一输入,后续 A2-Q1.5 业务决策、slice 循环、apply 都以它为准。
246
249
  5. **不要**把未选中的可选模块写进 `confirmed_plan`,也不要在生成代码时"顺手"加它们的按钮 / composable。
247
250
  6. `enhancement_level` 仅为兼容旧字段:全部可选模块都选 → `complete`,否则 → `minimal`。真正的范围以 `confirmed_plan` 为准。
@@ -0,0 +1,155 @@
1
+ ---
2
+ id: conference/integration-audit
3
+ name: Conference Web 集成审计
4
+ product: conference
5
+ platform: web
6
+ tags: [audit, checklist, meta, integration-review, roomkit, online-classroom]
7
+ platforms: [web]
8
+ related:
9
+ - conference/login-auth
10
+ - conference/prejoin-check
11
+ - conference/room-lifecycle
12
+ - conference/room-schedule
13
+ - conference/device-control
14
+ - conference/official-roomkit-api
15
+ related_scenario: general-conference
16
+ ---
17
+
18
+ # Conference Web 集成审计
19
+
20
+ > **Meta slice** — 横切对照清单,供 Path B intent F(集成审计)使用。
21
+ > **不要**加入 scenario 的 `slices` / `confirmed_plan`,**不要**走 topic 集成队列或 apply 门禁。
22
+
23
+ > 当用户问「检查业务流程遗漏」「对照官方接入流程」「在线课堂/会议流程是否正常」时使用本 slice。
24
+ > 输出形态:**固定 checklist + slice 引用**;**不要**输出 code review、优缺点列表或「改进建议」段落。
25
+
26
+ ## 如何使用
27
+
28
+ 1. 从 session 读取 `product` / `platform` / `ui_mode` / `scenario`(若有)。
29
+ 2. 仅 `(product, platform) == (conference, web)` 时走完整清单;其他组合说明当前 skill 集成 fix 仅覆盖 Conference Web,清单仍可作为对照参考。
30
+ 3. 按角色(学生 / 老师 / 通用)裁剪章节;用户未说明角色时输出「通用骨架 + 在线课堂附录」。
31
+ 4. 每项标注:**应有** / **常见遗漏** / **对应 slice**。
32
+ 5. 用户贴了代码时,只核对清单项是否 wired up,不做风格点评。
33
+
34
+ ---
35
+
36
+ ## 1. 通用骨架(所有 Conference Web 项目)
37
+
38
+ | # | 检查项 | 常见遗漏 | 参考 slice |
39
+ |---|--------|----------|------------|
40
+ | 1.1 | `conference.login()` / `useLoginState().login()` 先于任何 room/device/chat 调用 | 未登录直接 join | `conference/login-auth` |
41
+ | 1.2 | 登录参数含 `scene: 5001`(skill 生成代码) | 缺 scene | `conference/login-auth` |
42
+ | 1.3 | 登录成功后 `setSelfInfo({ userName, avatarUrl })` | 列表只显示 userId | `conference/login-auth` |
43
+ | 1.4 | UserSig:生产由后端签发;本地可用 config 自动签名(`local-dev`)或控制台粘贴(`console`) | userId 与 userSig 不匹配 | `conference/login-auth` |
44
+ | 1.5 | 监听 `LoginEvent.onLoginExpired` / `onKickedOffline` 并收口 | 只打日志 | `conference/login-auth` |
45
+ | 1.6 | 监听 `RoomEvent.USER_SIG_EXPIRED` / `KICKED_OUT` / `ROOM_DISMISS` | 被动退出 UI 假在线 | `conference/room-lifecycle` |
46
+ | 1.7 | 页面在 **HTTPS** 或 **localhost** 下调试 | 生产 HTTP 无媒体权限 | `conference/device-control` |
47
+ | 1.8 | iframe 嵌入时宿主页 `allow="camera; microphone; display-capture; fullscreen"` | iframe 内黑屏/无权限 | `conference/device-control` |
48
+ | 1.9 | 刷新/关页时 `leaveRoom()` 或等价清理 | 幽灵参会者 | `conference/room-lifecycle` |
49
+ | 1.10 | 房主 `endRoom()` vs 成员 `leaveRoom()` 分支正确 | 老师误用 leave 导致房间未结束 | `conference/room-lifecycle` |
50
+
51
+ ---
52
+
53
+ ## 2. 会前设备检测
54
+
55
+ | # | 检查项 | 常见遗漏 | 参考 slice |
56
+ |---|--------|----------|------------|
57
+ | 2.1 | 会前用 `startCameraTest` / `stopCameraTest`,**不用** `openLocalCamera` 做预检 | getUserMedia 与 SDK 双轨冲突 | `conference/prejoin-check` |
58
+ | 2.2 | 预检页设备开关用**本地 ref**,不读 `cameraStatus` / `microphoneStatus` | 预检与会中状态串线 | `conference/prejoin-check` |
59
+ | 2.3 | 入会前 `stopCameraTest()` 释放采集 | 进房后设备占用 | `conference/prejoin-check` |
60
+ | 2.4 | 轻量 `getUserMedia` 仅作**权限门禁**;完整预览走 Atomicx 测试 API | 两套检测职责重叠 | `conference/prejoin-check` |
61
+ | 2.5 | 进房后麦克风:先 `openLocalMicrophone()` + 默认 `muteMicrophone()`;摄像头:`open/closeLocalCamera` | 开麦延迟、关摄语义错误 | `conference/device-control` |
62
+
63
+ ---
64
+
65
+ ## 3. 房间生命周期
66
+
67
+ | # | 检查项 | 常见遗漏 | 参考 slice |
68
+ |---|--------|----------|------------|
69
+ | 3.1 | 即时会议:发起人 `createAndJoinRoom`,他人 `joinRoom` | roomId 来源混乱 | `conference/room-lifecycle` |
70
+ | 3.2 | 预约会议:`scheduleRoom` 成功后,到点仍须 `joinRoom`(预约≠已入会) | 预约后直接认为在会中 | `conference/room-schedule` |
71
+ | 3.3 | `joinRoom` 失败区分:未登录 / 房间不存在 / 密码错误 / 已结束 | 统一提示「进房失败」 | `conference/room-lifecycle` |
72
+ | 3.4 | 被动退出后清理 `currentRoom`、聊天、布局、widget 注册 | 再次入会残留上一场状态 | `conference/room-lifecycle` |
73
+ | 3.5 | `passive_exit_target`:被踢/解散后跳转大厅或登录(业务决策) | 停留在空白会中页 | `conference/room-lifecycle` |
74
+
75
+ ---
76
+
77
+ ## 4. 官方 RoomKit 模式(`ui_mode = official-roomkit`)
78
+
79
+ | # | 检查项 | 常见遗漏 | 参考 slice |
80
+ |---|--------|----------|------------|
81
+ | 4.1 | 根节点 `UIKitProvider` 包裹 `PreConferenceView` / `ConferenceMainView(H5)` | Provider 缺失 | `conference/official-roomkit-api` |
82
+ | 4.2 | `setWidgetVisible` / `registerWidget` / `onWill` 在 **login 之后、join 之前** | 按钮闪烁、拦截失效 | `conference/official-roomkit-api` |
83
+ | 4.3 | `createAndJoinRoom` / `joinRoom` 成功后立即 `setFeatureConfig({ shareLink })` | 分享链接 roomId 不对 | `conference/official-roomkit-api` |
84
+ | 4.4 | 收集 `registerWidget` / `onWill` 返回的 cleanup;在 `ROOM_LEAVE` 与 `ROOM_DISMISS` 都执行 | 重复注册、泄漏 | `conference/official-roomkit-api` |
85
+ | 4.5 | UI 显隐用 `setWidgetVisible`,**不用** CSS/DOM hack | 升级 SDK 即碎 | `conference/official-roomkit-api` |
86
+ | 4.6 | lockfile 中 `@tencentcloud/roomkit-web-vue3 >= 5.4.3` | 定制 API 不可用 | `conference/official-roomkit-api` |
87
+
88
+ ---
89
+
90
+ ## 5. 在线课堂角色附录
91
+
92
+ ### 学生链路
93
+
94
+ ```text
95
+ 登录 → setSelfInfo → 预约列表(room-schedule) → 会前检测(prejoin-check)
96
+ → joinRoom → 会中(默认禁麦策略)→ 主动 leaveRoom / 被动 ROOM_DISMISS
97
+ ```
98
+
99
+ | 常见遗漏 | 说明 |
100
+ |----------|------|
101
+ | 预约后直接进房未做设备检测 | 应用 prejoin-check 页或等价流程 |
102
+ | 被房主结束会议无提示 | 监听 `RoomEvent.ROOM_DISMISS` |
103
+ | 与「只 join 不 create」的 roomid_origin 不一致 | session 业务决策应对齐 |
104
+
105
+ ### 老师链路
106
+
107
+ ```text
108
+ 登录 → setSelfInfo → createAndJoinRoom → 会中开启 AI 字幕(ai-tools) / 录制(控制台+套餐)
109
+ → endRoom(非 leaveRoom)
110
+ ```
111
+
112
+ | 常见遗漏 | 说明 |
113
+ |----------|------|
114
+ | AI 字幕非房主也调 `startRealtimeTranscriber` | 仅房主应启动 |
115
+ | 结束会议用 `leaveRoom` 而非 `endRoom` | 其他成员仍留在已结束房间上下文 |
116
+ | 录制能力未在控制台开通 | 预制 UI 按钮灰显或启动失败 |
117
+
118
+ ---
119
+
120
+ ## 6. 高频运行时问题速查(审计时顺带提及)
121
+
122
+ | 现象 / 错误码 | 结论 | 下一步 |
123
+ |---------------|------|--------|
124
+ | **6206** / UserSig expired | 鉴权失败 | 后端刷新 UserSig;监听 `USER_SIG_EXPIRED` |
125
+ | **100211** take-seat not enabled | 非麦位房间内部 seat 同步 | Standard 会议通常**可忽略**;确认未调用上麦/连麦 API |
126
+ | **100006** only open to live room | 在 Standard 房间调了 Live 接口 | 检查 roomType;隐藏 Live 工具栏 widget |
127
+ | 双设备检测冲突 | 职责未分层 | 见 §2 |
128
+ | iframe 无画面 | 缺 allow 权限 | 见 §1.8 |
129
+ | 刷新后幽灵在线 | 未 beforeunload leave | 见 §1.9 |
130
+
131
+ ---
132
+
133
+ ## 7. 推荐输出模板(给 AI)
134
+
135
+ ```markdown
136
+ ## Conference Web 集成对照结果
137
+
138
+ **场景**:{scenario 或「通用会议」} · **UI 模式**:{official-roomkit | headless | 未知}
139
+
140
+ ### 已覆盖(对照官方流程)
141
+ - …
142
+
143
+ ### 可能遗漏(按优先级)
144
+ 1. … → 参考 `conference/xxx`
145
+ 2. …
146
+
147
+ ### 环境/权限门禁
148
+ - HTTPS / iframe / UserSig:…
149
+
150
+ ### 官方文档
151
+ - [Web RoomKit 快速接入](https://cloud.tencent.com/document/product/647/81962)
152
+ - [设备检测](https://cloud.tencent.com/document/product/647/126939)
153
+ ```
154
+
155
+ **禁止**使用的标题:Critical Review Checklist、改进建议、✅正确 vs ❌错误 对比表(作为主结构)。
@@ -14,7 +14,8 @@ business_decisions:
14
14
  tier: blocking
15
15
  question: "您的项目当前处于哪个阶段?这决定鉴权凭证(UserSig)的生成方式。"
16
16
  options:
17
- - { label: "本地开发 / 调试 —— 控制台临时签发,几分钟即可跑通;有有效期、不能用于生产", value: "console" }
17
+ - { label: "本地开发 —— config 配置 SDKAppID+SecretKey,登录时按 UserID 自动签名(推荐,最快)", value: "local-dev" }
18
+ - { label: "本地开发 / 调试 —— 到控制台生成 UserSig,复制粘贴到登录页(不配置 SecretKey)", value: "console" }
18
19
  - { label: "生产环境(上线)—— 由您的后端接口签发,安全合规(推荐)", value: "backend" }
19
20
  - key: userid_strategy
20
21
  tier: blocking