@tencent-rtc/trtc-agent-skills 0.1.0

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 (205) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +172 -0
  3. package/README.zh.md +173 -0
  4. package/bin/cli.js +434 -0
  5. package/knowledge-base/index.yaml +454 -0
  6. package/knowledge-base/platform-slice-template.md +233 -0
  7. package/knowledge-base/scenario-spec.md +350 -0
  8. package/knowledge-base/scenarios/conference/base/general-conference.md +365 -0
  9. package/knowledge-base/scenarios/conference/base/webinar-conference.md +130 -0
  10. package/knowledge-base/scenarios/conference/medical/1v1-video-consultation.md +145 -0
  11. package/knowledge-base/scenarios/conference/medical/medical-multidoctor-consultation.md +113 -0
  12. package/knowledge-base/scenarios/live/entertainment-live-room.md +118 -0
  13. package/knowledge-base/slice-spec.md +546 -0
  14. package/knowledge-base/slices/conference/web/ai-tools.md +225 -0
  15. package/knowledge-base/slices/conference/web/beauty-effects.md +188 -0
  16. package/knowledge-base/slices/conference/web/device-control.md +338 -0
  17. package/knowledge-base/slices/conference/web/login-auth.md +261 -0
  18. package/knowledge-base/slices/conference/web/network-quality.md +190 -0
  19. package/knowledge-base/slices/conference/web/official-roomkit-api.md +298 -0
  20. package/knowledge-base/slices/conference/web/official-roomkit-login-ui.md +246 -0
  21. package/knowledge-base/slices/conference/web/participant-list.md +238 -0
  22. package/knowledge-base/slices/conference/web/participant-management.md +718 -0
  23. package/knowledge-base/slices/conference/web/prejoin-check.md +293 -0
  24. package/knowledge-base/slices/conference/web/room-call.md +213 -0
  25. package/knowledge-base/slices/conference/web/room-chat.md +426 -0
  26. package/knowledge-base/slices/conference/web/room-lifecycle.md +534 -0
  27. package/knowledge-base/slices/conference/web/room-schedule.md +281 -0
  28. package/knowledge-base/slices/conference/web/screen-share.md +211 -0
  29. package/knowledge-base/slices/conference/web/video-layout.md +675 -0
  30. package/knowledge-base/slices/conference/web/virtual-background.md +197 -0
  31. package/knowledge-base/slices/conference/web/webinar-interaction.md +206 -0
  32. package/knowledge-base/slices/live/anchor-lifecycle.md +122 -0
  33. package/knowledge-base/slices/live/anchor-preview.md +90 -0
  34. package/knowledge-base/slices/live/anchor-room-config.md +104 -0
  35. package/knowledge-base/slices/live/audience-list.md +86 -0
  36. package/knowledge-base/slices/live/audience-manage.md +92 -0
  37. package/knowledge-base/slices/live/audience-watch.md +85 -0
  38. package/knowledge-base/slices/live/audio.md +116 -0
  39. package/knowledge-base/slices/live/barrage.md +88 -0
  40. package/knowledge-base/slices/live/beauty.md +99 -0
  41. package/knowledge-base/slices/live/coguest-apply.md +105 -0
  42. package/knowledge-base/slices/live/device-control.md +91 -0
  43. package/knowledge-base/slices/live/error-codes.md +167 -0
  44. package/knowledge-base/slices/live/gift.md +84 -0
  45. package/knowledge-base/slices/live/ios/.gitkeep +0 -0
  46. package/knowledge-base/slices/live/ios/anchor-lifecycle.md +313 -0
  47. package/knowledge-base/slices/live/ios/anchor-preview.md +228 -0
  48. package/knowledge-base/slices/live/ios/anchor-room-config.md +257 -0
  49. package/knowledge-base/slices/live/ios/audience-list.md +353 -0
  50. package/knowledge-base/slices/live/ios/audience-manage.md +381 -0
  51. package/knowledge-base/slices/live/ios/audience-watch.md +286 -0
  52. package/knowledge-base/slices/live/ios/audio.md +373 -0
  53. package/knowledge-base/slices/live/ios/barrage.md +285 -0
  54. package/knowledge-base/slices/live/ios/beauty.md +323 -0
  55. package/knowledge-base/slices/live/ios/coguest-apply.md +506 -0
  56. package/knowledge-base/slices/live/ios/device-control.md +286 -0
  57. package/knowledge-base/slices/live/ios/error-codes.md +270 -0
  58. package/knowledge-base/slices/live/ios/gift.md +315 -0
  59. package/knowledge-base/slices/live/ios/live-list.md +269 -0
  60. package/knowledge-base/slices/live/ios/login-auth.md +247 -0
  61. package/knowledge-base/slices/live/live-list.md +82 -0
  62. package/knowledge-base/slices/live/login-auth.md +78 -0
  63. package/package.json +34 -0
  64. package/skills/trtc/SKILL.md +326 -0
  65. package/skills/trtc/room-builder/SKILL.md +138 -0
  66. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/README.md +108 -0
  67. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/docs/backend-contract.zh-CN.md +162 -0
  68. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/docs/integration.zh-CN.md +154 -0
  69. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/docs/theme.zh-CN.md +78 -0
  70. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/index.html +12 -0
  71. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/package.json +28 -0
  72. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/postcss.config.js +5 -0
  73. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/App.vue +25 -0
  74. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/ConsultationManagePanel.vue +838 -0
  75. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/LanguageSwitch.vue +102 -0
  76. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/LoadingSpinner.vue +6 -0
  77. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalAlert.vue +34 -0
  78. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalBusinessPanel.vue +148 -0
  79. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalButton.vue +49 -0
  80. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalConfirmDialog.vue +68 -0
  81. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalDataPanel.vue +196 -0
  82. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalRecordPanel.vue +270 -0
  83. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/PrescriptionPanel.vue +363 -0
  84. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/config/basic-info-config.ts +29 -0
  85. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/config/lib-generate-test-usersig-es.min.d.ts +4 -0
  86. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/config/lib-generate-test-usersig-es.min.js +2 -0
  87. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/config/runtime-config.ts +12 -0
  88. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/env.d.ts +32 -0
  89. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/ConsultationChatPanel.vue +123 -0
  90. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/ConsultationMembersPanel.vue +230 -0
  91. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/ConsultationTranscriptionPanel.vue +135 -0
  92. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/ConsultationVideoStage.vue +113 -0
  93. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/InviteDoctorDialog.vue +132 -0
  94. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/KickMemberConfirmDialog.vue +50 -0
  95. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/types.ts +77 -0
  96. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/useConsultationChat.ts +97 -0
  97. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/useConsultationDevices.ts +48 -0
  98. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/useConsultationParticipants.ts +121 -0
  99. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/useConsultationPermissions.ts +25 -0
  100. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/utils.ts +70 -0
  101. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/en-US/index.ts +553 -0
  102. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/index.ts +25 -0
  103. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/medicalTranslate.ts +85 -0
  104. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/state.ts +49 -0
  105. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/zh-CN/index.ts +463 -0
  106. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/main.ts +12 -0
  107. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/mock/appointments.ts +96 -0
  108. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/mock/users.ts +79 -0
  109. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/router/index.ts +63 -0
  110. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/index.ts +25 -0
  111. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/integration/appointmentService.ts +77 -0
  112. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/integration/authService.ts +38 -0
  113. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/integration/launchContext.ts +31 -0
  114. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/integration/userService.ts +35 -0
  115. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/mock/appointmentService.ts +43 -0
  116. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/mock/authService.ts +33 -0
  117. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/mock/userService.ts +43 -0
  118. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/types.ts +135 -0
  119. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/shared/icons.ts +53 -0
  120. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/styles/index.css +106 -0
  121. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/styles/tailwind.css +3 -0
  122. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/styles/theme.css +209 -0
  123. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/utils/auth.ts +50 -0
  124. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/utils/format.ts +24 -0
  125. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/utils/navigation.ts +12 -0
  126. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/utils/session.ts +28 -0
  127. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/DoctorConsultationView.vue +777 -0
  128. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/DoctorDashboardView.vue +678 -0
  129. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/LoginView.vue +441 -0
  130. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/PatientConsultationFinishedView.vue +185 -0
  131. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/PatientConsultationView.vue +1003 -0
  132. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/PatientSelectDoctorView.vue +317 -0
  133. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/PatientWaitingView.vue +454 -0
  134. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/tsconfig.json +21 -0
  135. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/tsconfig.node.json +8 -0
  136. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/vite.config.ts +17 -0
  137. package/skills/trtc/room-builder/templates/scenarios/medical-consultation//346/216/245/345/205/245/350/257/264/346/230/216.md +6 -0
  138. package/skills/trtc/room-builder/tools/render_ai_instructions.py +226 -0
  139. package/skills/trtc-apply/SKILL.md +97 -0
  140. package/skills/trtc-apply/guardrails/apply_lib/__init__.py +0 -0
  141. package/skills/trtc-apply/guardrails/apply_lib/__pycache__/__init__.cpython-313.pyc +0 -0
  142. package/skills/trtc-apply/guardrails/apply_lib/__pycache__/rule_parser.cpython-313.pyc +0 -0
  143. package/skills/trtc-apply/guardrails/apply_lib/rule_parser.py +268 -0
  144. package/skills/trtc-docs/SKILL.md +207 -0
  145. package/skills/trtc-onboarding/SKILL.md +839 -0
  146. package/skills/trtc-onboarding/reference/path-a1-demo.md +103 -0
  147. package/skills/trtc-onboarding/reference/path-a2-integrate.md +693 -0
  148. package/skills/trtc-onboarding/reference/path-b-troubleshoot.md +115 -0
  149. package/skills/trtc-onboarding/reference/path-c-expand.md +43 -0
  150. package/skills/trtc-onboarding/reference/reporting-protocol.md +174 -0
  151. package/skills/trtc-onboarding/reference/supported-matrix.md +100 -0
  152. package/skills/trtc-onboarding/reference/usersig-handling.md +140 -0
  153. package/skills/trtc-search/SKILL.md +221 -0
  154. package/skills/trtc-topic/SKILL.md +638 -0
  155. package/skills/trtc-topic/guardrails/__pycache__/gate_slice_read.cpython-313.pyc +0 -0
  156. package/skills/trtc-topic/guardrails/__pycache__/gate_slice_write.cpython-313.pyc +0 -0
  157. package/skills/trtc-topic/guardrails/__pycache__/stop_require_apply_evidence.cpython-313.pyc +0 -0
  158. package/skills/trtc-topic/guardrails/gate_slice_read.py +133 -0
  159. package/skills/trtc-topic/guardrails/gate_slice_write.py +169 -0
  160. package/skills/trtc-topic/guardrails/stop_require_apply_evidence.py +97 -0
  161. package/skills/trtc-topic/references/execution-units.yaml +58 -0
  162. package/skills/trtc-topic/runtime/README.md +50 -0
  163. package/skills/trtc-topic/runtime/RUNTIME.md +128 -0
  164. package/skills/trtc-topic/runtime/lib/__init__.py +0 -0
  165. package/skills/trtc-topic/runtime/lib/platforms.py +194 -0
  166. package/skills/trtc-topic/runtime/package-lock.json +1211 -0
  167. package/skills/trtc-topic/runtime/package.json +13 -0
  168. package/skills/trtc-topic/runtime/telemetry-bridge.mjs +339 -0
  169. package/skills/trtc-topic/runtime/telemetry_collector.py +293 -0
  170. package/skills/trtc-topic/scripts/STATE-MACHINE-GUIDE.md +186 -0
  171. package/skills/trtc-topic/scripts/__pycache__/apply.cpython-313.pyc +0 -0
  172. package/skills/trtc-topic/scripts/apply.py +581 -0
  173. package/skills/trtc-topic/scripts/finalize_session.py +113 -0
  174. package/skills/trtc-topic/scripts/init_slice_queue.py +96 -0
  175. package/skills/trtc-topic/scripts/lib/__pycache__/state_machine.cpython-313.pyc +0 -0
  176. package/skills/trtc-topic/scripts/lib/state_machine.py +328 -0
  177. package/skills/trtc-topic/scripts/next_slice.py +137 -0
  178. package/skills/trtc-topic/tests/README.md +70 -0
  179. package/skills/trtc-topic/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc +0 -0
  180. package/skills/trtc-topic/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  181. package/skills/trtc-topic/tests/__pycache__/test_apply_cli.cpython-313-pytest-9.0.2.pyc +0 -0
  182. package/skills/trtc-topic/tests/__pycache__/test_apply_cli.cpython-313-pytest-9.0.3.pyc +0 -0
  183. package/skills/trtc-topic/tests/__pycache__/test_end_to_end.cpython-313-pytest-9.0.2.pyc +0 -0
  184. package/skills/trtc-topic/tests/__pycache__/test_end_to_end.cpython-313-pytest-9.0.3.pyc +0 -0
  185. package/skills/trtc-topic/tests/__pycache__/test_finalize_session.cpython-313-pytest-9.0.2.pyc +0 -0
  186. package/skills/trtc-topic/tests/__pycache__/test_finalize_session.cpython-313-pytest-9.0.3.pyc +0 -0
  187. package/skills/trtc-topic/tests/__pycache__/test_gates.cpython-313-pytest-9.0.2.pyc +0 -0
  188. package/skills/trtc-topic/tests/__pycache__/test_gates.cpython-313-pytest-9.0.3.pyc +0 -0
  189. package/skills/trtc-topic/tests/__pycache__/test_session_resolver.cpython-313-pytest-9.0.2.pyc +0 -0
  190. package/skills/trtc-topic/tests/__pycache__/test_session_resolver.cpython-313-pytest-9.0.3.pyc +0 -0
  191. package/skills/trtc-topic/tests/__pycache__/test_state_machine.cpython-313-pytest-9.0.2.pyc +0 -0
  192. package/skills/trtc-topic/tests/__pycache__/test_state_machine.cpython-313-pytest-9.0.3.pyc +0 -0
  193. package/skills/trtc-topic/tests/__pycache__/test_stop_require_apply.cpython-313-pytest-9.0.2.pyc +0 -0
  194. package/skills/trtc-topic/tests/__pycache__/test_stop_require_apply.cpython-313-pytest-9.0.3.pyc +0 -0
  195. package/skills/trtc-topic/tests/__pycache__/test_topic_skill_invariants.cpython-313-pytest-9.0.2.pyc +0 -0
  196. package/skills/trtc-topic/tests/__pycache__/test_topic_skill_invariants.cpython-313-pytest-9.0.3.pyc +0 -0
  197. package/skills/trtc-topic/tests/conftest.py +72 -0
  198. package/skills/trtc-topic/tests/test_apply_cli.py +480 -0
  199. package/skills/trtc-topic/tests/test_end_to_end.py +305 -0
  200. package/skills/trtc-topic/tests/test_finalize_session.py +51 -0
  201. package/skills/trtc-topic/tests/test_gates.py +316 -0
  202. package/skills/trtc-topic/tests/test_session_resolver.py +260 -0
  203. package/skills/trtc-topic/tests/test_state_machine.py +414 -0
  204. package/skills/trtc-topic/tests/test_stop_require_apply.py +99 -0
  205. package/skills/trtc-topic/tests/test_topic_skill_invariants.py +130 -0
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env python3
2
+ """render_ai_instructions.py — Render ai-instructions/*.md to tool-specific entry files.
3
+
4
+ Sources: ai-instructions/*.md (single source of truth, human-edited)
5
+ Targets:
6
+ - AGENTS.md (for Codex / Aider / Cline / CodeBuddy)
7
+ - CLAUDE.md (between AI-INSTRUCTIONS markers)
8
+ - .cursor/rules/{name}.mdc (one per source file; with Cursor frontmatter)
9
+
10
+ Grown TDD-style; see tests/unit/test_render_ai_instructions.py.
11
+ """
12
+ import argparse
13
+ import sys
14
+ from pathlib import Path
15
+
16
+
17
+ def _sources(project_root, *, include_base=True):
18
+ """Return sorted list of .md source files under ai-instructions/.
19
+
20
+ base.md is the fixed preamble for AGENTS.md (tells Codex to read CLAUDE.md).
21
+ When include_base=False it is excluded — used by CLAUDE.md and Cursor renders
22
+ which don't need a self-referential pointer.
23
+ """
24
+ src_dir = project_root / "ai-instructions"
25
+ if not src_dir.exists():
26
+ return []
27
+ files = sorted(src_dir.glob("*.md"))
28
+ if not include_base:
29
+ files = [f for f in files if f.name != "base.md"]
30
+ return files
31
+
32
+
33
+ # Single source-of-truth banner. Reused across all derived targets so the
34
+ # message stays identical and contributors can grep for "DO NOT EDIT" to
35
+ # find every generated file.
36
+ _BANNER = (
37
+ "<!-- DO NOT EDIT — generated from ai-instructions/ by "
38
+ "skills/trtc/room-builder/tools/render_ai_instructions.py. "
39
+ "Edit the source markdown and re-run the renderer instead. -->"
40
+ )
41
+
42
+
43
+ def _render_agents_md(project_root):
44
+ """Regenerate AGENTS.md by concatenating all sources.
45
+
46
+ base.md is rendered first as a plain preamble (no H1 header) so Codex
47
+ sees the "read CLAUDE.md" instruction at the top. Remaining sources get
48
+ an H1 section header per file.
49
+ """
50
+ base_path = project_root / "ai-instructions" / "base.md"
51
+ sources = _sources(project_root, include_base=False)
52
+ parts = [_BANNER + "\n"]
53
+ # Preamble from base.md (rendered without a # header — it has its own).
54
+ if base_path.exists():
55
+ parts.append(base_path.read_text().rstrip() + "\n")
56
+ for src in sources:
57
+ parts.append(f"# {src.stem}\n\n{src.read_text().rstrip()}\n")
58
+ (project_root / "AGENTS.md").write_text("\n".join(parts) if (base_path.exists() or sources) else _BANNER + "\n")
59
+
60
+
61
+ BEGIN_MARKER = "<!-- AI-INSTRUCTIONS:BEGIN -->"
62
+ END_MARKER = "<!-- AI-INSTRUCTIONS:END -->"
63
+
64
+
65
+ def _demote_headings(body):
66
+ """Add one '#' to every ATX heading line.
67
+
68
+ `## Foo` → `### Foo`, `### Foo` → `#### Foo`, etc. Lines that aren't
69
+ headings are returned unchanged. Used in CLAUDE.md so body sections
70
+ nest under the renderer-prepended `## {name}` parent.
71
+ """
72
+ out = []
73
+ for line in body.splitlines():
74
+ # Match an ATX heading: 1-6 '#' followed by a space.
75
+ stripped = line.lstrip()
76
+ if stripped.startswith("#"):
77
+ hashes = len(stripped) - len(stripped.lstrip("#"))
78
+ if 1 <= hashes <= 5 and stripped[hashes:hashes + 1] == " ":
79
+ # Preserve leading whitespace (rare in markdown but safe).
80
+ lead = line[:len(line) - len(stripped)]
81
+ out.append(f"{lead}#{stripped}")
82
+ continue
83
+ out.append(line)
84
+ return "\n".join(out)
85
+
86
+
87
+ def _rendered_block(project_root):
88
+ """The string content placed between markers in CLAUDE.md.
89
+
90
+ Section headers are H2 (`## {name}`). Body headings are demoted by one
91
+ level so they nest as children of the section header instead of
92
+ appearing as adjacent siblings. Banner placed first so a casual reader
93
+ sees the warning before any rendered content.
94
+
95
+ base.md is excluded — CLAUDE.md doesn't need a pointer to itself.
96
+ """
97
+ sources = _sources(project_root, include_base=False)
98
+ parts = [_BANNER + "\n"]
99
+ for src in sources:
100
+ body = _demote_headings(src.read_text().rstrip())
101
+ parts.append(f"## {src.stem}\n\n{body}\n")
102
+ return "\n".join(parts)
103
+
104
+
105
+ def _render_claude_md(project_root):
106
+ """Update CLAUDE.md in place between AI-INSTRUCTIONS markers.
107
+
108
+ If markers exist: replace content strictly between them.
109
+ If markers don't exist: append markers + rendered block at EOF.
110
+ Content outside the markers is never touched.
111
+ """
112
+ claude_path = project_root / "CLAUDE.md"
113
+ existing = claude_path.read_text() if claude_path.exists() else ""
114
+ block = _rendered_block(project_root)
115
+ marker_block = f"{BEGIN_MARKER}\n{block}\n{END_MARKER}\n"
116
+
117
+ if BEGIN_MARKER in existing and END_MARKER in existing:
118
+ begin = existing.index(BEGIN_MARKER)
119
+ end = existing.index(END_MARKER) + len(END_MARKER)
120
+ # Preserve trailing newline behavior from the original end-marker position.
121
+ trailing_nl = "\n" if end < len(existing) and existing[end] == "\n" else ""
122
+ new = existing[:begin] + marker_block.rstrip("\n") + trailing_nl + existing[end + len(trailing_nl):]
123
+ claude_path.write_text(new)
124
+ else:
125
+ # Append block at EOF (with a blank line separator).
126
+ sep = "" if existing.endswith("\n\n") or not existing else ("\n" if existing.endswith("\n") else "\n\n")
127
+ claude_path.write_text(existing + sep + marker_block)
128
+
129
+
130
+ def _render_cursor_rules(project_root):
131
+ """One .cursor/rules/{name}.mdc per source file, with Cursor frontmatter.
132
+
133
+ Banner placed AFTER the frontmatter so Cursor's YAML parser still works.
134
+ base.md is excluded — Cursor reads its own rules, no redirect needed.
135
+ """
136
+ rules_dir = project_root / ".cursor" / "rules"
137
+ rules_dir.mkdir(parents=True, exist_ok=True)
138
+ for src in _sources(project_root, include_base=False):
139
+ target = rules_dir / f"{src.stem}.mdc"
140
+ body = src.read_text().rstrip()
141
+ target.write_text(
142
+ "---\n"
143
+ "alwaysApply: true\n"
144
+ "---\n"
145
+ "\n"
146
+ f"{_BANNER}\n"
147
+ "\n"
148
+ f"{body}\n"
149
+ )
150
+
151
+
152
+ def _check_targets_up_to_date(project_root):
153
+ """Compare current target contents to what would be rendered.
154
+
155
+ Returns a list of stale target paths (relative to project_root). Empty
156
+ list = everything in sync.
157
+
158
+ Implementation: render to a temporary in-memory representation and diff
159
+ against current file contents. The cleanest way is to capture what
160
+ each render function would write before it writes; rather than
161
+ refactoring all three to return strings, we copy the current targets
162
+ out, run the renderers, diff, then restore on mismatch (so --check
163
+ has no side effect on the working tree).
164
+ """
165
+ targets = ["AGENTS.md", "CLAUDE.md"]
166
+ rules_dir = project_root / ".cursor" / "rules"
167
+ for src in _sources(project_root):
168
+ targets.append(f".cursor/rules/{src.stem}.mdc")
169
+
170
+ # Snapshot current contents.
171
+ snapshot = {}
172
+ for rel in targets:
173
+ p = project_root / rel
174
+ snapshot[rel] = p.read_bytes() if p.exists() else None
175
+
176
+ # Run render to compute new contents.
177
+ _render_agents_md(project_root)
178
+ _render_claude_md(project_root)
179
+ _render_cursor_rules(project_root)
180
+
181
+ stale = []
182
+ for rel in targets:
183
+ p = project_root / rel
184
+ new = p.read_bytes() if p.exists() else None
185
+ if new != snapshot[rel]:
186
+ stale.append(rel)
187
+
188
+ # Restore original contents so --check has no side effect.
189
+ for rel, original in snapshot.items():
190
+ p = project_root / rel
191
+ if original is None:
192
+ if p.exists():
193
+ p.unlink()
194
+ else:
195
+ p.write_bytes(original)
196
+ return stale
197
+
198
+
199
+ def main():
200
+ parser = argparse.ArgumentParser(description="Render ai-instructions/*.md to tool files.")
201
+ parser.add_argument("--project-root", default=".",
202
+ help="Repo root (defaults to CWD)")
203
+ parser.add_argument("--check", action="store_true",
204
+ help="Exit 2 if any target is stale (CI mode); no writes.")
205
+ args = parser.parse_args()
206
+ root = Path(args.project_root).resolve()
207
+
208
+ if args.check:
209
+ stale = _check_targets_up_to_date(root)
210
+ if stale:
211
+ print("render_ai_instructions: stale targets:", file=sys.stderr)
212
+ for s in stale:
213
+ print(f" {s}", file=sys.stderr)
214
+ print("Re-run `python3 skills/trtc/room-builder/tools/render_ai_instructions.py` and commit the diff.",
215
+ file=sys.stderr)
216
+ return 2
217
+ return 0
218
+
219
+ _render_agents_md(root)
220
+ _render_claude_md(root)
221
+ _render_cursor_rules(root)
222
+ return 0
223
+
224
+
225
+ if __name__ == "__main__":
226
+ sys.exit(main())
@@ -0,0 +1,97 @@
1
+ ---
2
+ name: trtc-apply
3
+ description: >
4
+ INTERNAL structural gate for TRTC code generated by the topic / onboarding
5
+ skills. Not a user-facing skill, and NOT a correctness verifier. It does one
6
+ job: stop the AI from declaring a slice "done" and ending the turn before a
7
+ deterministic check has run. The check itself is lightweight — code exists +
8
+ the slice's entry symbol is wired up (with comment/string anti-cheat) — it
9
+ does NOT verify types, compilation, or runtime behavior. Triggered only by
10
+ other skills in this repo (topic step gates, onboarding A2). Do NOT route to
11
+ this skill when a user asks "review my code" or "check this implementation".
12
+ ---
13
+
14
+ # TRTC apply — Structural Gate
15
+
16
+ apply 是 topic / onboarding 代码交付流程里的一道**结构门**,不是正确性校验器。
17
+
18
+ ```
19
+ 它保证「流程没被跳过」,不保证「代码是对的」。
20
+ ```
21
+
22
+ 正确性由两处负责,不在 apply 范围内:
23
+ - **生成阶段**:slice 的 MUST / MUST NOT 约束指导代码怎么写。
24
+ - **客户侧**:在真实项目里编译 / 运行确认。apply 跑在客户五花八门的已有项目里,无法假设一个统一可靠的 build,因此**刻意不做编译**——编译失败往往与生成代码无关(历史报错、缺依赖、私有 registry、monorepo 配置),把它当门禁只会产生噪声并拖慢每个 slice。
25
+
26
+ 本 skill 只有一种触发场景:topic / onboarding 生成代码后**内部调用**。它不是面向外部用户的"贴代码帮我检查"服务;若用户贴代码求排障,走 onboarding Path B。
27
+
28
+ ---
29
+
30
+ ## 它实际做的两件事
31
+
32
+ ### 1. 强制门(防止"自我宣布完成就停")
33
+
34
+ apply 接在状态机上。每个 slice 的状态流转:
35
+
36
+ ```
37
+ slice_read → code_written → apply_passed → user_confirmed
38
+ ```
39
+
40
+ - 写完代码后必须运行 `apply.py`,把状态从 `code_written` 推到 `apply_passed`,否则
41
+ - `guardrails/stop_require_apply_evidence.py`(Stop hook)会**拦住结束**:`code_written` 或 `apply_failed` 状态下不允许 AI 结束本轮。
42
+
43
+ 这是 apply 真正的价值:LLM 不能写一段"看起来对"的代码就自我盖章完成。
44
+
45
+ ### 2. 代码非空 + 入口符号检查(轻量,不等于正确)
46
+
47
+ `apply.py` 扫 `<project_root>/src/**/*.{vue,ts}`,做两件事:
48
+
49
+ - **代码非空**:`src/` 不存在或没有源码 → `static-only` → fail(没有可检查的内容,不放行)。
50
+ - **入口符号**:每个 slice 有一个「入口符号」(它的 composable / 组件,如 device-control 的 `useDeviceState`)。该入口符号作为**真实代码标识符**出现在某个源文件里即通过;没有出现则 fail;**该 slice 没有登记入口符号 → 跳过**(无法机械检查,绝不误判)。
51
+
52
+ 入口符号映射是单一来源:`apply_lib/rule_parser.py` 的 `COMPOSABLE_TO_SLICE`(配 `entry_symbols_for_slice()`)。
53
+
54
+ - 匹配前先用 `_strip_comments_and_strings` 剥掉注释与字符串字面量——防止把入口塞进 `// 注释` 或 `"字符串"` 骗过检查(来自 demo-test-2 真实 bug)。**入口符号是代码标识符,从不出现在字符串里,所以这步剥离不会冤枉正确代码**(这正是它取代旧「MUST 符号 grep」的原因:旧检查会把写在字符串里的符号——如错误码常量——剥掉而产生假阴性)。
55
+ - 失败信息会点名该 slice 的入口 composable(那是它公开文档里的 import,不是隐藏的 API pattern,点名安全且有助修复)。
56
+
57
+ > 明确边界:这一步**不验证**参数 / 类型 / 调用顺序 / 是否被执行 / 能否编译 / 运行时行为,也**不再**逐条核对 slice 的 MUST 符号。它只回答"代码非空,且这个 slice 的能力入口被接上了"。正确性由 slice 的 MUST / MUST NOT 约束(生成阶段)和客户侧编译运行负责。
58
+
59
+ ### 3. 重复声明检查(窄范围,真实编译错误)
60
+
61
+ `apply.py` 还有一个**高精度、窄范围**的编译安全检查:只针对"从 `use*()` 调用解构出来的名字"——同名被解构 ≥2 次,或被解构且又以 `const/function/class` 声明,则判为 `duplicate-declaration` 并 fail。两个真实案例:
62
+
63
+ - `const { getCameraList } = useDeviceState()` 之后又写 `function getCameraList()`;
64
+ - `subscribeEvent` 同时从 `useRoomParticipantState()` 和 `useRoomState()` 解构。
65
+
66
+ 它**不是**通用重复声明 linter(两个不同作用域的同名局部 `const` 不会被误报)。修法是给其中一个解构起别名:`const { subscribeEvent: subscribeParticipantEvent } = useRoomParticipantState()`。
67
+
68
+ ---
69
+
70
+ ## 调用方式
71
+
72
+ ```bash
73
+ python3 skills/trtc-topic/scripts/apply.py --slice <slice_id>
74
+ # 或按交付单元
75
+ python3 skills/trtc-topic/scripts/apply.py --unit <unit_id>
76
+ ```
77
+
78
+ - session 路径解析:`$TRTC_SESSION_PATH` → `$CLAUDE_PROJECT_DIR/.trtc-session.yaml` → `./.trtc-session.yaml`。
79
+ - 退出码:`0` 通过(状态 → `apply_passed`) / `1` 失败(状态 → `apply_failed`) / `2` 用法错误。
80
+ - 证据写入 `<session_dir>/.trtc-apply-evidence/<slug>.json`(状态、入口检查数 `entries_checked`、每个 slice 的 `entry_result`、未过项的语义文本)。
81
+ - `src/` 不存在或无源码 → 记为 `static-only`,结论为 `fail`(无可检查内容,不放行)。
82
+
83
+ `auto_advance_policy` 为 `pause_on_failure` / `pause_at_end` 时,通过后自动推进到 `user_confirmed`;否则(默认 `pause_each`)保留每步向用户确认的暂停。
84
+
85
+ ---
86
+
87
+ ## 范围与非目标
88
+
89
+ | 在范围内 | 不在范围内 |
90
+ |---------|-----------|
91
+ | web / vue 生成代码 | iOS / Android / Flutter / Electron |
92
+ | 代码非空 + 单 slice 入口符号检查 | 类型 / 编译 / 运行时正确性 |
93
+ | 注释/字符串防作弊 | 逐条核对 slice 的 MUST 符号 / 参数 / 调用顺序 |
94
+ | 重复声明(编译安全)检查 | 跨 slice 前置状态、生命周期、清理对称性 |
95
+ | 状态机强制门 + Stop hook | 集成安全(diff 范围、SDK 初始化冲突、回归测试) |
96
+
97
+ 入口符号检查只在「共用入口」(如 `conference` 对象同时承载 login-auth 与 room-lifecycle)上区分度较弱:导入一次即可让同入口的多个 slice 都过。这是「删掉逐符号 grep、改用入口门」时已接受的取舍——门的核心价值是状态机强制(不许自我盖章结束),而非判据的精度。跨 slice / 场景级的更强校验目前未实现,且不应硬编码进本门;正确性应由 slice 的 MUST / MUST NOT 约束与客户侧编译运行承接。
@@ -0,0 +1,268 @@
1
+ """rule_parser.py — Slice rule extraction + entry-symbol map.
2
+
3
+ Two responsibilities:
4
+
5
+ * ``extract_rules_from_slice`` / ``rules_for_file`` — parse the
6
+ "代码生成约束 / 生成规则" sections of slice .md files into structured
7
+ MUST/MUST NOT rules (still used for documentation/inspection).
8
+ * ``COMPOSABLE_TO_SLICE`` / ``entry_symbols_for_slice`` — the slice ↔ entry
9
+ symbol map. The structural gate (``skills/trtc-topic/scripts/apply.py``)
10
+ uses this to check that a slice's entry composable/component is wired up
11
+ in the generated code. (The gate no longer greps each rule's backtick
12
+ patterns — that produced false negatives on symbols living inside string
13
+ literals; see apply.py's module docstring.)
14
+
15
+ Each rule has:
16
+ - type: "MUST" or "MUST NOT"
17
+ - text: The full rule description
18
+ - patterns: List of grep-able code patterns extracted from backtick segments
19
+ - verify_hint: The "Verify:" instruction if present
20
+ - source_file: Path to the slice .md file
21
+ - slice_id: e.g. "conference/login-auth"
22
+ """
23
+ import re
24
+ from pathlib import Path
25
+ from typing import Optional
26
+
27
+
28
+ # Match numbered MUST/MUST NOT rules in slice files.
29
+ # Format: "N. **text** — explanation\n **Verify**: check"
30
+ _RULE_BLOCK_RE = re.compile(
31
+ r'^\d+\.\s+\*\*(.*?)\*\*\s*[—–-]\s*(.*?)$',
32
+ re.MULTILINE
33
+ )
34
+
35
+ # Match the section headers for MUST / MUST NOT
36
+ _MUST_SECTION_RE = re.compile(
37
+ r'^#{2,4}\s+MUST(?:\s+NOT)?\s*[((].*?[))]',
38
+ re.MULTILINE
39
+ )
40
+
41
+ # Extract code patterns from backtick-quoted segments
42
+ _BACKTICK_RE = re.compile(r'`([^`]+)`')
43
+
44
+ # Match "**Verify**:" lines
45
+ _VERIFY_RE = re.compile(r'\*\*Verify\*\*:\s*(.+?)$', re.MULTILINE)
46
+
47
+
48
+ # Entry composable / component / value → slice_id.
49
+ #
50
+ # This is the single source for "what is this slice's entry symbol". It is
51
+ # consumed in two places:
52
+ # * rules_for_file() — to decide which slice's rules apply to a file.
53
+ # * the apply gate (apply.py) — to check that a slice's entry symbol
54
+ # actually appears in the generated code.
55
+ #
56
+ # Entry symbols are stable code identifiers (composables / components / exported
57
+ # values), never string literals — which is what makes the entry-presence check
58
+ # immune to the comment/string-literal stripping false-negative that the old
59
+ # MUST-symbol grep suffered from.
60
+ COMPOSABLE_TO_SLICE = {
61
+ 'useLoginState': 'conference/login-auth',
62
+ 'useRoomState': 'conference/room-lifecycle',
63
+ 'useDeviceState': 'conference/device-control',
64
+ 'useRoomParticipantState': 'conference/participant-list',
65
+ 'RoomView': 'conference/video-layout',
66
+ 'RoomLayoutTemplate': 'conference/video-layout',
67
+ 'networkInfo': 'conference/network-quality',
68
+ 'NetworkQuality': 'conference/network-quality',
69
+ 'useConversationListState': 'conference/room-chat',
70
+ 'useMessageListState': 'conference/room-chat',
71
+ 'useMessageInputState': 'conference/room-chat',
72
+ 'setActiveConversation': 'conference/room-chat',
73
+ 'startScreenShare': 'conference/screen-share',
74
+ 'stopScreenShare': 'conference/screen-share',
75
+ }
76
+
77
+
78
+ def entry_symbols_for_slice(slice_id: str) -> list:
79
+ """Return the known entry symbols for a slice (inverse of COMPOSABLE_TO_SLICE).
80
+
81
+ Empty list means the slice has no registered entry symbol; callers should
82
+ treat that as "cannot check mechanically" rather than a failure.
83
+ """
84
+ return [sym for sym, sid in COMPOSABLE_TO_SLICE.items() if sid == slice_id]
85
+
86
+
87
+ def extract_rules_from_slice(md_path: Path) -> list:
88
+ """Parse a slice markdown file for MUST/MUST NOT rules.
89
+
90
+ Looks for the "代码生成约束" / "生成规则" section and extracts
91
+ structured rules from the MUST/MUST NOT subsections.
92
+
93
+ Returns list of rule dicts.
94
+ """
95
+ if not md_path.exists():
96
+ return []
97
+
98
+ content = md_path.read_text(encoding='utf-8')
99
+
100
+ # Find the code generation constraints section
101
+ constraint_start = _find_constraint_section(content)
102
+ if constraint_start is None:
103
+ return []
104
+
105
+ constraint_text = content[constraint_start:]
106
+ rules = []
107
+
108
+ # Split into MUST and MUST NOT sections
109
+ must_section = _extract_section(constraint_text, 'MUST(生成时必须包含)')
110
+ must_not_section = _extract_section(constraint_text, 'MUST NOT(生成时绝不能出现)')
111
+
112
+ if must_section:
113
+ rules.extend(_parse_rules_in_section(must_section, 'MUST', md_path))
114
+ if must_not_section:
115
+ rules.extend(_parse_rules_in_section(must_not_section, 'MUST NOT', md_path))
116
+
117
+ return rules
118
+
119
+
120
+ def _find_constraint_section(content: str) -> Optional[int]:
121
+ """Find the start of the code generation constraints section."""
122
+ markers = [
123
+ '## 代码生成约束',
124
+ '### 生成规则',
125
+ '#### MUST(生成时必须包含)',
126
+ '#### MUST(',
127
+ ]
128
+ for marker in markers:
129
+ idx = content.find(marker)
130
+ if idx != -1:
131
+ return idx
132
+ return None
133
+
134
+
135
+ def _extract_section(text: str, header_fragment: str) -> Optional[str]:
136
+ """Extract text from a section header to the next same-or-higher-level header."""
137
+ idx = text.find(header_fragment)
138
+ if idx == -1:
139
+ return None
140
+
141
+ # Find the end: next #### or ### or ## header
142
+ section_start = text.find('\n', idx) + 1
143
+ remaining = text[section_start:]
144
+
145
+ # End at next heading of same or higher level
146
+ end_match = re.search(r'^#{2,4}\s+', remaining, re.MULTILINE)
147
+ if end_match:
148
+ return remaining[:end_match.start()]
149
+ return remaining
150
+
151
+
152
+ def _parse_rules_in_section(section_text: str, rule_type: str, source_file: Path) -> list:
153
+ """Parse numbered rules from a MUST or MUST NOT section."""
154
+ rules = []
155
+
156
+ # Split by numbered items (1. ... 2. ... 3. ...)
157
+ items = re.split(r'\n(?=\d+\.\s+)', section_text)
158
+
159
+ for item in items:
160
+ item = item.strip()
161
+ if not item or not re.match(r'^\d+\.', item):
162
+ continue
163
+
164
+ # Extract the rule title (bold text after number)
165
+ title_match = re.match(r'^\d+\.\s+\*\*(.*?)\*\*', item)
166
+ if not title_match:
167
+ # Try without bold
168
+ title_match = re.match(r'^\d+\.\s+(.+?)(?:\s*[—–-]|$)', item)
169
+
170
+ title = title_match.group(1) if title_match else item[:80]
171
+
172
+ # Extract code patterns from backticks
173
+ patterns = _BACKTICK_RE.findall(item)
174
+
175
+ # Extract verify hint
176
+ verify_match = _VERIFY_RE.search(item)
177
+ verify_hint = verify_match.group(1) if verify_match else None
178
+
179
+ # Derive slice_id from file path
180
+ slice_id = _derive_slice_id(source_file)
181
+
182
+ rules.append({
183
+ 'type': rule_type,
184
+ 'text': title.strip(),
185
+ 'patterns': patterns,
186
+ 'verify_hint': verify_hint,
187
+ 'source_file': str(source_file),
188
+ 'slice_id': slice_id,
189
+ })
190
+
191
+ return rules
192
+
193
+
194
+ def _derive_slice_id(md_path: Path) -> str:
195
+ """Derive slice_id from file path.
196
+
197
+ e.g. .../slices/conference/web/login-auth.md → conference/login-auth
198
+ """
199
+ parts = md_path.parts
200
+ try:
201
+ slices_idx = parts.index('slices')
202
+ # Pattern: slices/{product}/{platform}/{ability}.md
203
+ # or: slices/{product}/{ability}.md
204
+ remaining = parts[slices_idx + 1:]
205
+ if len(remaining) >= 3:
206
+ # slices/conference/web/login-auth.md
207
+ product = remaining[0]
208
+ ability = remaining[-1].replace('.md', '')
209
+ return f"{product}/{ability}"
210
+ elif len(remaining) == 2:
211
+ # slices/conference/login-auth.md
212
+ product = remaining[0]
213
+ ability = remaining[1].replace('.md', '')
214
+ return f"{product}/{ability}"
215
+ except (ValueError, IndexError):
216
+ pass
217
+ return str(md_path.stem)
218
+
219
+
220
+ def load_rules_for_product_platform(
221
+ kb_root: Path,
222
+ product: str,
223
+ platform: str,
224
+ ) -> list:
225
+ """Load all rules for a given product and platform.
226
+
227
+ Loads both product-level and platform-specific slice files.
228
+ """
229
+ slices_dir = kb_root / 'knowledge-base' / 'slices' / product
230
+ all_rules = []
231
+
232
+ # Product-level slices
233
+ for md_file in slices_dir.glob('*.md'):
234
+ all_rules.extend(extract_rules_from_slice(md_file))
235
+
236
+ # Platform-specific slices
237
+ platform_dir = slices_dir / platform
238
+ if platform_dir.exists():
239
+ for md_file in platform_dir.glob('*.md'):
240
+ all_rules.extend(extract_rules_from_slice(md_file))
241
+
242
+ return all_rules
243
+
244
+
245
+ def rules_for_file(all_rules: list, file_content: str) -> list:
246
+ """Filter rules relevant to a specific file based on what it imports.
247
+
248
+ A rule is relevant if the file contains any composable/import that
249
+ the rule's slice defines.
250
+ """
251
+ # Build a map: slice_id → rules
252
+ by_slice = {}
253
+ for rule in all_rules:
254
+ sid = rule.get('slice_id', '')
255
+ by_slice.setdefault(sid, []).append(rule)
256
+
257
+ # Determine which slices this file touches
258
+ active_slices = set()
259
+ for composable, slice_id in COMPOSABLE_TO_SLICE.items():
260
+ if composable in file_content:
261
+ active_slices.add(slice_id)
262
+
263
+ # Return rules from active slices only
264
+ relevant = []
265
+ for sid in active_slices:
266
+ relevant.extend(by_slice.get(sid, []))
267
+
268
+ return relevant