@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.
- package/LICENSE +21 -0
- package/README.md +172 -0
- package/README.zh.md +173 -0
- package/bin/cli.js +434 -0
- package/knowledge-base/index.yaml +454 -0
- package/knowledge-base/platform-slice-template.md +233 -0
- package/knowledge-base/scenario-spec.md +350 -0
- package/knowledge-base/scenarios/conference/base/general-conference.md +365 -0
- package/knowledge-base/scenarios/conference/base/webinar-conference.md +130 -0
- package/knowledge-base/scenarios/conference/medical/1v1-video-consultation.md +145 -0
- package/knowledge-base/scenarios/conference/medical/medical-multidoctor-consultation.md +113 -0
- package/knowledge-base/scenarios/live/entertainment-live-room.md +118 -0
- package/knowledge-base/slice-spec.md +546 -0
- package/knowledge-base/slices/conference/web/ai-tools.md +225 -0
- package/knowledge-base/slices/conference/web/beauty-effects.md +188 -0
- package/knowledge-base/slices/conference/web/device-control.md +338 -0
- package/knowledge-base/slices/conference/web/login-auth.md +261 -0
- package/knowledge-base/slices/conference/web/network-quality.md +190 -0
- package/knowledge-base/slices/conference/web/official-roomkit-api.md +298 -0
- package/knowledge-base/slices/conference/web/official-roomkit-login-ui.md +246 -0
- package/knowledge-base/slices/conference/web/participant-list.md +238 -0
- package/knowledge-base/slices/conference/web/participant-management.md +718 -0
- package/knowledge-base/slices/conference/web/prejoin-check.md +293 -0
- package/knowledge-base/slices/conference/web/room-call.md +213 -0
- package/knowledge-base/slices/conference/web/room-chat.md +426 -0
- package/knowledge-base/slices/conference/web/room-lifecycle.md +534 -0
- package/knowledge-base/slices/conference/web/room-schedule.md +281 -0
- package/knowledge-base/slices/conference/web/screen-share.md +211 -0
- package/knowledge-base/slices/conference/web/video-layout.md +675 -0
- package/knowledge-base/slices/conference/web/virtual-background.md +197 -0
- package/knowledge-base/slices/conference/web/webinar-interaction.md +206 -0
- package/knowledge-base/slices/live/anchor-lifecycle.md +122 -0
- package/knowledge-base/slices/live/anchor-preview.md +90 -0
- package/knowledge-base/slices/live/anchor-room-config.md +104 -0
- package/knowledge-base/slices/live/audience-list.md +86 -0
- package/knowledge-base/slices/live/audience-manage.md +92 -0
- package/knowledge-base/slices/live/audience-watch.md +85 -0
- package/knowledge-base/slices/live/audio.md +116 -0
- package/knowledge-base/slices/live/barrage.md +88 -0
- package/knowledge-base/slices/live/beauty.md +99 -0
- package/knowledge-base/slices/live/coguest-apply.md +105 -0
- package/knowledge-base/slices/live/device-control.md +91 -0
- package/knowledge-base/slices/live/error-codes.md +167 -0
- package/knowledge-base/slices/live/gift.md +84 -0
- package/knowledge-base/slices/live/ios/.gitkeep +0 -0
- package/knowledge-base/slices/live/ios/anchor-lifecycle.md +313 -0
- package/knowledge-base/slices/live/ios/anchor-preview.md +228 -0
- package/knowledge-base/slices/live/ios/anchor-room-config.md +257 -0
- package/knowledge-base/slices/live/ios/audience-list.md +353 -0
- package/knowledge-base/slices/live/ios/audience-manage.md +381 -0
- package/knowledge-base/slices/live/ios/audience-watch.md +286 -0
- package/knowledge-base/slices/live/ios/audio.md +373 -0
- package/knowledge-base/slices/live/ios/barrage.md +285 -0
- package/knowledge-base/slices/live/ios/beauty.md +323 -0
- package/knowledge-base/slices/live/ios/coguest-apply.md +506 -0
- package/knowledge-base/slices/live/ios/device-control.md +286 -0
- package/knowledge-base/slices/live/ios/error-codes.md +270 -0
- package/knowledge-base/slices/live/ios/gift.md +315 -0
- package/knowledge-base/slices/live/ios/live-list.md +269 -0
- package/knowledge-base/slices/live/ios/login-auth.md +247 -0
- package/knowledge-base/slices/live/live-list.md +82 -0
- package/knowledge-base/slices/live/login-auth.md +78 -0
- package/package.json +34 -0
- package/skills/trtc/SKILL.md +326 -0
- package/skills/trtc/room-builder/SKILL.md +138 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/README.md +108 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/docs/backend-contract.zh-CN.md +162 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/docs/integration.zh-CN.md +154 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/docs/theme.zh-CN.md +78 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/index.html +12 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/package.json +28 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/postcss.config.js +5 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/App.vue +25 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/ConsultationManagePanel.vue +838 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/LanguageSwitch.vue +102 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/LoadingSpinner.vue +6 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalAlert.vue +34 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalBusinessPanel.vue +148 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalButton.vue +49 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalConfirmDialog.vue +68 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalDataPanel.vue +196 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalRecordPanel.vue +270 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/PrescriptionPanel.vue +363 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/config/basic-info-config.ts +29 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/config/lib-generate-test-usersig-es.min.d.ts +4 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/config/lib-generate-test-usersig-es.min.js +2 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/config/runtime-config.ts +12 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/env.d.ts +32 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/ConsultationChatPanel.vue +123 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/ConsultationMembersPanel.vue +230 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/ConsultationTranscriptionPanel.vue +135 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/ConsultationVideoStage.vue +113 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/InviteDoctorDialog.vue +132 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/KickMemberConfirmDialog.vue +50 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/types.ts +77 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/useConsultationChat.ts +97 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/useConsultationDevices.ts +48 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/useConsultationParticipants.ts +121 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/useConsultationPermissions.ts +25 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/utils.ts +70 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/en-US/index.ts +553 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/index.ts +25 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/medicalTranslate.ts +85 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/state.ts +49 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/zh-CN/index.ts +463 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/main.ts +12 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/mock/appointments.ts +96 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/mock/users.ts +79 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/router/index.ts +63 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/index.ts +25 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/integration/appointmentService.ts +77 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/integration/authService.ts +38 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/integration/launchContext.ts +31 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/integration/userService.ts +35 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/mock/appointmentService.ts +43 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/mock/authService.ts +33 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/mock/userService.ts +43 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/types.ts +135 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/shared/icons.ts +53 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/styles/index.css +106 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/styles/tailwind.css +3 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/styles/theme.css +209 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/utils/auth.ts +50 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/utils/format.ts +24 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/utils/navigation.ts +12 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/utils/session.ts +28 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/DoctorConsultationView.vue +777 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/DoctorDashboardView.vue +678 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/LoginView.vue +441 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/PatientConsultationFinishedView.vue +185 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/PatientConsultationView.vue +1003 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/PatientSelectDoctorView.vue +317 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/PatientWaitingView.vue +454 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/tsconfig.json +21 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/tsconfig.node.json +8 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/vite.config.ts +17 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation//346/216/245/345/205/245/350/257/264/346/230/216.md +6 -0
- package/skills/trtc/room-builder/tools/render_ai_instructions.py +226 -0
- package/skills/trtc-apply/SKILL.md +97 -0
- package/skills/trtc-apply/guardrails/apply_lib/__init__.py +0 -0
- package/skills/trtc-apply/guardrails/apply_lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/skills/trtc-apply/guardrails/apply_lib/__pycache__/rule_parser.cpython-313.pyc +0 -0
- package/skills/trtc-apply/guardrails/apply_lib/rule_parser.py +268 -0
- package/skills/trtc-docs/SKILL.md +207 -0
- package/skills/trtc-onboarding/SKILL.md +839 -0
- package/skills/trtc-onboarding/reference/path-a1-demo.md +103 -0
- package/skills/trtc-onboarding/reference/path-a2-integrate.md +693 -0
- package/skills/trtc-onboarding/reference/path-b-troubleshoot.md +115 -0
- package/skills/trtc-onboarding/reference/path-c-expand.md +43 -0
- package/skills/trtc-onboarding/reference/reporting-protocol.md +174 -0
- package/skills/trtc-onboarding/reference/supported-matrix.md +100 -0
- package/skills/trtc-onboarding/reference/usersig-handling.md +140 -0
- package/skills/trtc-search/SKILL.md +221 -0
- package/skills/trtc-topic/SKILL.md +638 -0
- package/skills/trtc-topic/guardrails/__pycache__/gate_slice_read.cpython-313.pyc +0 -0
- package/skills/trtc-topic/guardrails/__pycache__/gate_slice_write.cpython-313.pyc +0 -0
- package/skills/trtc-topic/guardrails/__pycache__/stop_require_apply_evidence.cpython-313.pyc +0 -0
- package/skills/trtc-topic/guardrails/gate_slice_read.py +133 -0
- package/skills/trtc-topic/guardrails/gate_slice_write.py +169 -0
- package/skills/trtc-topic/guardrails/stop_require_apply_evidence.py +97 -0
- package/skills/trtc-topic/references/execution-units.yaml +58 -0
- package/skills/trtc-topic/runtime/README.md +50 -0
- package/skills/trtc-topic/runtime/RUNTIME.md +128 -0
- package/skills/trtc-topic/runtime/lib/__init__.py +0 -0
- package/skills/trtc-topic/runtime/lib/platforms.py +194 -0
- package/skills/trtc-topic/runtime/package-lock.json +1211 -0
- package/skills/trtc-topic/runtime/package.json +13 -0
- package/skills/trtc-topic/runtime/telemetry-bridge.mjs +339 -0
- package/skills/trtc-topic/runtime/telemetry_collector.py +293 -0
- package/skills/trtc-topic/scripts/STATE-MACHINE-GUIDE.md +186 -0
- package/skills/trtc-topic/scripts/__pycache__/apply.cpython-313.pyc +0 -0
- package/skills/trtc-topic/scripts/apply.py +581 -0
- package/skills/trtc-topic/scripts/finalize_session.py +113 -0
- package/skills/trtc-topic/scripts/init_slice_queue.py +96 -0
- package/skills/trtc-topic/scripts/lib/__pycache__/state_machine.cpython-313.pyc +0 -0
- package/skills/trtc-topic/scripts/lib/state_machine.py +328 -0
- package/skills/trtc-topic/scripts/next_slice.py +137 -0
- package/skills/trtc-topic/tests/README.md +70 -0
- package/skills/trtc-topic/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_apply_cli.cpython-313-pytest-9.0.2.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_apply_cli.cpython-313-pytest-9.0.3.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_end_to_end.cpython-313-pytest-9.0.2.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_end_to_end.cpython-313-pytest-9.0.3.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_finalize_session.cpython-313-pytest-9.0.2.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_finalize_session.cpython-313-pytest-9.0.3.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_gates.cpython-313-pytest-9.0.2.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_gates.cpython-313-pytest-9.0.3.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_session_resolver.cpython-313-pytest-9.0.2.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_session_resolver.cpython-313-pytest-9.0.3.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_state_machine.cpython-313-pytest-9.0.2.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_state_machine.cpython-313-pytest-9.0.3.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_stop_require_apply.cpython-313-pytest-9.0.2.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_stop_require_apply.cpython-313-pytest-9.0.3.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_topic_skill_invariants.cpython-313-pytest-9.0.2.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_topic_skill_invariants.cpython-313-pytest-9.0.3.pyc +0 -0
- package/skills/trtc-topic/tests/conftest.py +72 -0
- package/skills/trtc-topic/tests/test_apply_cli.py +480 -0
- package/skills/trtc-topic/tests/test_end_to_end.py +305 -0
- package/skills/trtc-topic/tests/test_finalize_session.py +51 -0
- package/skills/trtc-topic/tests/test_gates.py +316 -0
- package/skills/trtc-topic/tests/test_session_resolver.py +260 -0
- package/skills/trtc-topic/tests/test_state_machine.py +414 -0
- package/skills/trtc-topic/tests/test_stop_require_apply.py +99 -0
- package/skills/trtc-topic/tests/test_topic_skill_invariants.py +130 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""pytest fixtures for the topic state-machine and gate hooks.
|
|
2
|
+
|
|
3
|
+
These fixtures only know about session files and a simulated project root.
|
|
4
|
+
They never touch the real repo session.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
import yaml
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Default confirmed_plan used by most tests — mirrors the general-conference
|
|
15
|
+
# minimal scope (P0).
|
|
16
|
+
DEFAULT_CONFIRMED_PLAN = [
|
|
17
|
+
"conference/login-auth",
|
|
18
|
+
"conference/room-lifecycle",
|
|
19
|
+
"conference/participant-list",
|
|
20
|
+
"conference/video-layout",
|
|
21
|
+
"conference/device-control",
|
|
22
|
+
"conference/network-quality",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.fixture
|
|
27
|
+
def session_factory(tmp_path: Path):
|
|
28
|
+
"""Factory that writes a `.trtc-session.yaml` into tmp_path.
|
|
29
|
+
|
|
30
|
+
Returns a callable: ``make_session(**overrides) -> Path``.
|
|
31
|
+
Defaults are picked to match a typical mid-integration general-conference/web
|
|
32
|
+
session so individual tests only override what they care about.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def _make(**overrides) -> Path:
|
|
36
|
+
base = {
|
|
37
|
+
"schema_version": 1,
|
|
38
|
+
"status": "active",
|
|
39
|
+
"product": "conference",
|
|
40
|
+
"platform": "web",
|
|
41
|
+
"intent": "integrate-scenario",
|
|
42
|
+
"scenario": "general-conference",
|
|
43
|
+
"ui_mode": None,
|
|
44
|
+
"current_step": "topic-handoff",
|
|
45
|
+
"confirmed_plan": list(DEFAULT_CONFIRMED_PLAN),
|
|
46
|
+
"completed_steps": [],
|
|
47
|
+
"project_state": {
|
|
48
|
+
"project_root": str(tmp_path / "user-project"),
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
base.update(overrides)
|
|
52
|
+
path = tmp_path / ".trtc-session.yaml"
|
|
53
|
+
path.write_text(yaml.safe_dump(base, sort_keys=False, allow_unicode=True))
|
|
54
|
+
return path
|
|
55
|
+
|
|
56
|
+
return _make
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@pytest.fixture
|
|
60
|
+
def project_factory(tmp_path: Path):
|
|
61
|
+
"""Factory that creates an empty user project skeleton at tmp_path/user-project.
|
|
62
|
+
|
|
63
|
+
Returns the project root Path. Tests can write whatever Vue/TS files they
|
|
64
|
+
need into it.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def _make() -> Path:
|
|
68
|
+
root = tmp_path / "user-project"
|
|
69
|
+
(root / "src").mkdir(parents=True, exist_ok=True)
|
|
70
|
+
return root
|
|
71
|
+
|
|
72
|
+
return _make
|
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
"""Tests for apply.py — the executable apply gate.
|
|
2
|
+
|
|
3
|
+
Contract:
|
|
4
|
+
|
|
5
|
+
apply.py --slice <slice_id> [--session PATH] [--project PATH]
|
|
6
|
+
|
|
7
|
+
Checks that the project has real source AND that each slice's entry symbol
|
|
8
|
+
(its composable/component) appears as real code in src/, writes
|
|
9
|
+
``.trtc-apply-evidence/{slice_safename}.json`` next to the session file, and
|
|
10
|
+
advances the state machine:
|
|
11
|
+
|
|
12
|
+
pass → mark_apply_passed
|
|
13
|
+
fail → mark_apply_failed
|
|
14
|
+
|
|
15
|
+
Exit codes:
|
|
16
|
+
0 — pass
|
|
17
|
+
1 — fail
|
|
18
|
+
2 — usage / config error (missing slice id, project root, etc.)
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import subprocess
|
|
24
|
+
import sys
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
import pytest
|
|
28
|
+
import yaml
|
|
29
|
+
|
|
30
|
+
APPLY_SCRIPT = (
|
|
31
|
+
Path(__file__).resolve().parents[1] / "scripts" / "apply.py"
|
|
32
|
+
)
|
|
33
|
+
STATE_MACHINE_DIR = Path(__file__).resolve().parents[1] / "scripts" / "lib"
|
|
34
|
+
sys.path.insert(0, str(STATE_MACHINE_DIR))
|
|
35
|
+
import state_machine # noqa: E402
|
|
36
|
+
sys.path.pop(0)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _run_apply(slice_id: str, session_path: Path, project_root: Path | None = None) -> subprocess.CompletedProcess:
|
|
40
|
+
args = [sys.executable, str(APPLY_SCRIPT), "--slice", slice_id, "--session", str(session_path)]
|
|
41
|
+
if project_root is not None:
|
|
42
|
+
args += ["--project", str(project_root)]
|
|
43
|
+
return subprocess.run(args, text=True, capture_output=True)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _run_apply_unit(unit_id: str, session_path: Path, project_root: Path | None = None) -> subprocess.CompletedProcess:
|
|
47
|
+
args = [sys.executable, str(APPLY_SCRIPT), "--unit", unit_id, "--session", str(session_path)]
|
|
48
|
+
if project_root is not None:
|
|
49
|
+
args += ["--project", str(project_root)]
|
|
50
|
+
return subprocess.run(args, text=True, capture_output=True)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _seed_to_code_written(session_path: Path) -> None:
|
|
54
|
+
state_machine.init_queue(session_path)
|
|
55
|
+
state_machine.advance(session_path, "mark_slice_read")
|
|
56
|
+
state_machine.advance(session_path, "mark_code_written")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
_PASSING_VUE = '''<template><div /></template>
|
|
60
|
+
<script setup lang="ts">
|
|
61
|
+
import { useLoginState, LoginEvent } from "@trtc/tuikit-atomicx-vue3";
|
|
62
|
+
const { login, setSelfInfo, subscribeEvent } = useLoginState();
|
|
63
|
+
await login({ sdkAppId: 0, userId: "u", userSig: "x", scene: 5001 });
|
|
64
|
+
setSelfInfo({ nickName: "Alice" });
|
|
65
|
+
subscribeEvent(LoginEvent.onLoginExpired, () => { /* refresh */ });
|
|
66
|
+
subscribeEvent(LoginEvent.onKickedOffline, () => { /* re-login */ });
|
|
67
|
+
</script>
|
|
68
|
+
'''
|
|
69
|
+
|
|
70
|
+
# Real source code that never references the login-auth entry (useLoginState).
|
|
71
|
+
# Under the entry-symbol gate this FAILS for conference/login-auth: there is
|
|
72
|
+
# code (so not static-only) but the slice's entry was never wired up.
|
|
73
|
+
_NO_ENTRY_VUE = '''<template><div /></template>
|
|
74
|
+
<script setup lang="ts">
|
|
75
|
+
import { ref } from "vue";
|
|
76
|
+
const ready = ref(false);
|
|
77
|
+
ready.value = true;
|
|
78
|
+
</script>
|
|
79
|
+
'''
|
|
80
|
+
|
|
81
|
+
_UNIT_PASSING_VUE = '''<template><div /></template>
|
|
82
|
+
<script setup lang="ts">
|
|
83
|
+
import { LoginEvent } from "tuikit-atomicx-vue3";
|
|
84
|
+
import { useLoginState } from "tuikit-atomicx-vue3/login";
|
|
85
|
+
import { useRoomState } from "tuikit-atomicx-vue3/room";
|
|
86
|
+
const { login, setSelfInfo, subscribeEvent, loginUserInfo } = useLoginState();
|
|
87
|
+
const { createAndJoinRoom, currentRoom, leaveRoom } = useRoomState();
|
|
88
|
+
await login({ sdkAppId: 0, userId: "u", userSig: "x", scene: 5001 });
|
|
89
|
+
setSelfInfo({ nickName: "Alice" });
|
|
90
|
+
subscribeEvent(LoginEvent.onLoginExpired, () => {});
|
|
91
|
+
subscribeEvent(LoginEvent.onKickedOffline, () => {});
|
|
92
|
+
if (loginUserInfo.value?.userId) {
|
|
93
|
+
await createAndJoinRoom();
|
|
94
|
+
}
|
|
95
|
+
if (currentRoom.value) {
|
|
96
|
+
await leaveRoom();
|
|
97
|
+
}
|
|
98
|
+
</script>
|
|
99
|
+
'''
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class TestApplyHappyPath:
|
|
103
|
+
def test_pass_advances_state_and_writes_evidence(
|
|
104
|
+
self, session_factory, project_factory
|
|
105
|
+
):
|
|
106
|
+
path = session_factory()
|
|
107
|
+
proj = project_factory()
|
|
108
|
+
(proj / "src" / "views").mkdir(parents=True, exist_ok=True)
|
|
109
|
+
(proj / "src" / "views" / "MeetingRoom.vue").write_text(_PASSING_VUE)
|
|
110
|
+
|
|
111
|
+
_seed_to_code_written(path)
|
|
112
|
+
result = _run_apply("conference/login-auth", path, proj)
|
|
113
|
+
assert result.returncode == 0, f"stdout={result.stdout}\nstderr={result.stderr}"
|
|
114
|
+
|
|
115
|
+
_, _, state = state_machine.current_slice(path)
|
|
116
|
+
assert state == "apply_passed"
|
|
117
|
+
|
|
118
|
+
ev_dir = path.parent / ".trtc-apply-evidence"
|
|
119
|
+
assert ev_dir.exists()
|
|
120
|
+
ev_files = list(ev_dir.glob("*.json"))
|
|
121
|
+
assert len(ev_files) == 1
|
|
122
|
+
ev = json.loads(ev_files[0].read_text())
|
|
123
|
+
assert ev["status"] == "pass"
|
|
124
|
+
assert ev["slice_id"] == "conference/login-auth"
|
|
125
|
+
assert ev["entries_checked"] >= 1
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class TestApplyDeliveryUnit:
|
|
129
|
+
def test_unit_apply_checks_multiple_slices_and_advances_unit_state(
|
|
130
|
+
self, session_factory, project_factory
|
|
131
|
+
):
|
|
132
|
+
path = session_factory(
|
|
133
|
+
execution_granularity="unit",
|
|
134
|
+
confirmed_plan=["conference/login-auth", "conference/room-lifecycle"],
|
|
135
|
+
)
|
|
136
|
+
proj = project_factory()
|
|
137
|
+
(proj / "src" / "views").mkdir(parents=True, exist_ok=True)
|
|
138
|
+
(proj / "src" / "views" / "MeetingRoom.vue").write_text(_UNIT_PASSING_VUE)
|
|
139
|
+
|
|
140
|
+
state_machine.init_queue(path)
|
|
141
|
+
state_machine.advance(path, "mark_slice_read")
|
|
142
|
+
state_machine.advance(path, "mark_code_written")
|
|
143
|
+
|
|
144
|
+
result = _run_apply_unit("foundation", path, proj)
|
|
145
|
+
assert result.returncode == 0, f"stdout={result.stdout}\nstderr={result.stderr}"
|
|
146
|
+
|
|
147
|
+
scope = state_machine.current_scope(path)
|
|
148
|
+
assert scope["kind"] == "unit"
|
|
149
|
+
assert scope["state"] == "apply_passed"
|
|
150
|
+
|
|
151
|
+
ev = json.loads((path.parent / ".trtc-apply-evidence" / "foundation.json").read_text())
|
|
152
|
+
assert ev["kind"] == "unit"
|
|
153
|
+
assert ev["unit_id"] == "foundation"
|
|
154
|
+
assert ev["slice_ids"] == ["conference/login-auth", "conference/room-lifecycle"]
|
|
155
|
+
checked = {entry["slice_id"]: entry for entry in ev["slices_checked"]}
|
|
156
|
+
assert checked["conference/login-auth"]["entry_result"] == "pass"
|
|
157
|
+
assert checked["conference/room-lifecycle"]["entry_result"] == "pass"
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class TestApplyFailure:
|
|
161
|
+
def test_fail_advances_to_apply_failed_with_issue_list(
|
|
162
|
+
self, session_factory, project_factory
|
|
163
|
+
):
|
|
164
|
+
path = session_factory()
|
|
165
|
+
proj = project_factory()
|
|
166
|
+
(proj / "src" / "views").mkdir(parents=True, exist_ok=True)
|
|
167
|
+
(proj / "src" / "views" / "MeetingRoom.vue").write_text(_NO_ENTRY_VUE)
|
|
168
|
+
|
|
169
|
+
_seed_to_code_written(path)
|
|
170
|
+
result = _run_apply("conference/login-auth", path, proj)
|
|
171
|
+
assert result.returncode == 1, f"stdout={result.stdout}\nstderr={result.stderr}"
|
|
172
|
+
|
|
173
|
+
_, _, state = state_machine.current_slice(path)
|
|
174
|
+
assert state == "apply_failed"
|
|
175
|
+
|
|
176
|
+
ev_files = list((path.parent / ".trtc-apply-evidence").glob("*.json"))
|
|
177
|
+
ev = json.loads(ev_files[0].read_text())
|
|
178
|
+
assert ev["status"] == "fail"
|
|
179
|
+
assert ev["issues"], "expected at least one issue listed"
|
|
180
|
+
joined = json.dumps(ev["issues"], ensure_ascii=False)
|
|
181
|
+
assert "useLoginState" in joined or "entry" in joined
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class TestApplyAntiCheatStripping:
|
|
185
|
+
"""The entry-symbol check still strips comments and string literals first.
|
|
186
|
+
|
|
187
|
+
The original demo-test-2 bug was AI stuffing a required symbol into a
|
|
188
|
+
`// comment` or `"string"` to satisfy a literal substring grep. The
|
|
189
|
+
per-API grep is gone (the gate now only checks the slice's entry
|
|
190
|
+
symbol), but the anti-cheat property must survive for the entry symbol:
|
|
191
|
+
an entry mentioned ONLY in a comment or string is not real wiring and
|
|
192
|
+
must FAIL.
|
|
193
|
+
|
|
194
|
+
Note: a correct implementation references the entry as a real code
|
|
195
|
+
identifier (e.g. `useLoginState()`), which is never inside a string —
|
|
196
|
+
so this stripping never false-negatives on correct code.
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
def test_entry_in_comment_does_not_pass(
|
|
200
|
+
self, session_factory, project_factory
|
|
201
|
+
):
|
|
202
|
+
"""A `.vue` whose only mention of the entry is in `// comments` must FAIL."""
|
|
203
|
+
path = session_factory()
|
|
204
|
+
proj = project_factory()
|
|
205
|
+
(proj / "src" / "views").mkdir(parents=True, exist_ok=True)
|
|
206
|
+
|
|
207
|
+
# useLoginState only appears in comments — no real wiring.
|
|
208
|
+
comment_stuffed = '''<template><div /></template>
|
|
209
|
+
<script setup lang="ts">
|
|
210
|
+
import { ref } from "vue";
|
|
211
|
+
// const { login } = useLoginState();
|
|
212
|
+
/*
|
|
213
|
+
const { login, setSelfInfo } = useLoginState();
|
|
214
|
+
*/
|
|
215
|
+
const ready = ref(true);
|
|
216
|
+
</script>
|
|
217
|
+
'''
|
|
218
|
+
(proj / "src" / "views" / "MeetingRoom.vue").write_text(comment_stuffed)
|
|
219
|
+
|
|
220
|
+
_seed_to_code_written(path)
|
|
221
|
+
result = _run_apply("conference/login-auth", path, proj)
|
|
222
|
+
assert result.returncode == 1, (
|
|
223
|
+
"entry mentioned only in comments must fail apply — comments are "
|
|
224
|
+
"stripped before the entry check. "
|
|
225
|
+
f"stdout={result.stdout}\nstderr={result.stderr}"
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
_, _, state = state_machine.current_slice(path)
|
|
229
|
+
assert state == "apply_failed"
|
|
230
|
+
|
|
231
|
+
def test_entry_in_string_literal_does_not_pass(
|
|
232
|
+
self, session_factory, project_factory
|
|
233
|
+
):
|
|
234
|
+
"""A `.vue` whose only mention of the entry is in string literals must FAIL."""
|
|
235
|
+
path = session_factory()
|
|
236
|
+
proj = project_factory()
|
|
237
|
+
(proj / "src" / "views").mkdir(parents=True, exist_ok=True)
|
|
238
|
+
|
|
239
|
+
string_stuffed = '''<template><div /></template>
|
|
240
|
+
<script setup lang="ts">
|
|
241
|
+
import { ref } from "vue";
|
|
242
|
+
const dummy = "const { login } = useLoginState();";
|
|
243
|
+
const dummy2 = 'useLoginState';
|
|
244
|
+
const dummy3 = `useLoginState()`;
|
|
245
|
+
const ready = ref(true);
|
|
246
|
+
</script>
|
|
247
|
+
'''
|
|
248
|
+
(proj / "src" / "views" / "MeetingRoom.vue").write_text(string_stuffed)
|
|
249
|
+
|
|
250
|
+
_seed_to_code_written(path)
|
|
251
|
+
result = _run_apply("conference/login-auth", path, proj)
|
|
252
|
+
assert result.returncode == 1, (
|
|
253
|
+
"entry mentioned only in string literals must fail apply — strings "
|
|
254
|
+
"are stripped before the entry check. "
|
|
255
|
+
f"stdout={result.stdout}\nstderr={result.stderr}"
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
def test_fail_output_does_not_leak_literal_patterns(
|
|
259
|
+
self, session_factory, project_factory
|
|
260
|
+
):
|
|
261
|
+
"""Evidence JSON `issues[]` must carry rule_text + type, never a `patterns` field.
|
|
262
|
+
|
|
263
|
+
The entry-failure issue names the slice's documented entry composable
|
|
264
|
+
(safe — it's a public import, not a hidden API pattern), but it must
|
|
265
|
+
not regrow a separate clean `patterns` array.
|
|
266
|
+
"""
|
|
267
|
+
path = session_factory()
|
|
268
|
+
proj = project_factory()
|
|
269
|
+
(proj / "src" / "views").mkdir(parents=True, exist_ok=True)
|
|
270
|
+
(proj / "src" / "views" / "MeetingRoom.vue").write_text(_NO_ENTRY_VUE)
|
|
271
|
+
|
|
272
|
+
_seed_to_code_written(path)
|
|
273
|
+
result = _run_apply("conference/login-auth", path, proj)
|
|
274
|
+
assert result.returncode == 1
|
|
275
|
+
|
|
276
|
+
ev_files = list((path.parent / ".trtc-apply-evidence").glob("*.json"))
|
|
277
|
+
ev = json.loads(ev_files[0].read_text())
|
|
278
|
+
assert ev["issues"], "regression: failure should produce at least one issue"
|
|
279
|
+
for issue in ev["issues"]:
|
|
280
|
+
assert "patterns" not in issue, (
|
|
281
|
+
f"issue leaks 'patterns' field: {issue}."
|
|
282
|
+
)
|
|
283
|
+
assert "rule_text" in issue
|
|
284
|
+
assert "type" in issue
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class TestApplyStaticOnly:
|
|
288
|
+
def test_no_project_src_falls_back_to_static_only(
|
|
289
|
+
self, session_factory, tmp_path
|
|
290
|
+
):
|
|
291
|
+
path = session_factory()
|
|
292
|
+
empty_proj = tmp_path / "user-project"
|
|
293
|
+
empty_proj.mkdir(exist_ok=True)
|
|
294
|
+
|
|
295
|
+
_seed_to_code_written(path)
|
|
296
|
+
result = _run_apply("conference/login-auth", path, empty_proj)
|
|
297
|
+
assert result.returncode == 1
|
|
298
|
+
ev = json.loads(next((path.parent / ".trtc-apply-evidence").glob("*.json")).read_text())
|
|
299
|
+
assert ev["mode"] == "static-only"
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
class TestApplyUsageErrors:
|
|
303
|
+
def test_refuses_when_not_in_code_written(self, session_factory, project_factory):
|
|
304
|
+
path = session_factory()
|
|
305
|
+
proj = project_factory()
|
|
306
|
+
state_machine.init_queue(path)
|
|
307
|
+
|
|
308
|
+
result = _run_apply("conference/login-auth", path, proj)
|
|
309
|
+
assert result.returncode == 2
|
|
310
|
+
assert "code_written" in result.stderr or "state" in result.stderr.lower()
|
|
311
|
+
|
|
312
|
+
def test_refuses_when_slice_does_not_match_cursor(
|
|
313
|
+
self, session_factory, project_factory
|
|
314
|
+
):
|
|
315
|
+
path = session_factory()
|
|
316
|
+
proj = project_factory()
|
|
317
|
+
_seed_to_code_written(path)
|
|
318
|
+
result = _run_apply("conference/room-lifecycle", path, proj)
|
|
319
|
+
assert result.returncode == 2
|
|
320
|
+
assert "current slice" in result.stderr.lower() or "login-auth" in result.stderr
|
|
321
|
+
|
|
322
|
+
def test_refuses_when_session_missing(self, tmp_path, project_factory):
|
|
323
|
+
proj = project_factory()
|
|
324
|
+
result = _run_apply("conference/login-auth", tmp_path / "missing.yaml", proj)
|
|
325
|
+
assert result.returncode == 2
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
# ---------- A+B: auto-advance policy ----------
|
|
329
|
+
#
|
|
330
|
+
# session.auto_advance_policy (root-level) controls what apply.py does
|
|
331
|
+
# AFTER recording pass:
|
|
332
|
+
#
|
|
333
|
+
# pause_each (or unset) → state stays at apply_passed; AI must wait
|
|
334
|
+
# for the user before calling mark_user_confirmed.
|
|
335
|
+
# This is the original behaviour.
|
|
336
|
+
# pause_on_failure → apply pass auto-advances mark_user_confirmed,
|
|
337
|
+
# landing on the next slice's not_started. apply
|
|
338
|
+
# fail / partial(warning) still pauses.
|
|
339
|
+
# pause_at_end → same as pause_on_failure (reserved for future).
|
|
340
|
+
#
|
|
341
|
+
# The motivation: forcing the user to type "继续" between every slice was the
|
|
342
|
+
# loud half of the protection. The quiet half — apply.py itself running real
|
|
343
|
+
# grep + advancing state — survives. AI still cannot skip apply, fake
|
|
344
|
+
# evidence, or batch-write multiple slices.
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _set_policy(session_path: Path, policy: str) -> None:
|
|
348
|
+
"""Write auto_advance_policy at session root (matches the project's flat schema)."""
|
|
349
|
+
data = yaml.safe_load(session_path.read_text())
|
|
350
|
+
data["auto_advance_policy"] = policy
|
|
351
|
+
session_path.write_text(yaml.safe_dump(data, sort_keys=False, allow_unicode=True))
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class TestAutoAdvanceOnPass:
|
|
355
|
+
def test_pause_on_failure_pass_advances_to_next_slice(
|
|
356
|
+
self, session_factory, project_factory
|
|
357
|
+
):
|
|
358
|
+
path = session_factory()
|
|
359
|
+
proj = project_factory()
|
|
360
|
+
(proj / "src" / "views").mkdir(parents=True, exist_ok=True)
|
|
361
|
+
(proj / "src" / "views" / "MeetingRoom.vue").write_text(_PASSING_VUE)
|
|
362
|
+
|
|
363
|
+
_seed_to_code_written(path)
|
|
364
|
+
_set_policy(path, "pause_on_failure")
|
|
365
|
+
|
|
366
|
+
result = _run_apply("conference/login-auth", path, proj)
|
|
367
|
+
assert result.returncode == 0, result.stderr
|
|
368
|
+
|
|
369
|
+
idx, sid, state = state_machine.current_slice(path)
|
|
370
|
+
assert idx == 1
|
|
371
|
+
assert sid == "conference/room-lifecycle"
|
|
372
|
+
assert state == "not_started"
|
|
373
|
+
|
|
374
|
+
def test_pause_each_pass_stays_at_apply_passed(
|
|
375
|
+
self, session_factory, project_factory
|
|
376
|
+
):
|
|
377
|
+
"""Original behaviour preserved when policy is pause_each."""
|
|
378
|
+
path = session_factory()
|
|
379
|
+
proj = project_factory()
|
|
380
|
+
(proj / "src" / "views").mkdir(parents=True, exist_ok=True)
|
|
381
|
+
(proj / "src" / "views" / "MeetingRoom.vue").write_text(_PASSING_VUE)
|
|
382
|
+
|
|
383
|
+
_seed_to_code_written(path)
|
|
384
|
+
_set_policy(path, "pause_each")
|
|
385
|
+
|
|
386
|
+
result = _run_apply("conference/login-auth", path, proj)
|
|
387
|
+
assert result.returncode == 0
|
|
388
|
+
_, _, state = state_machine.current_slice(path)
|
|
389
|
+
assert state == "apply_passed"
|
|
390
|
+
|
|
391
|
+
def test_unset_policy_defaults_to_pause_each(
|
|
392
|
+
self, session_factory, project_factory
|
|
393
|
+
):
|
|
394
|
+
"""Backward-compat: sessions without the field keep the old behaviour."""
|
|
395
|
+
path = session_factory()
|
|
396
|
+
proj = project_factory()
|
|
397
|
+
(proj / "src" / "views").mkdir(parents=True, exist_ok=True)
|
|
398
|
+
(proj / "src" / "views" / "MeetingRoom.vue").write_text(_PASSING_VUE)
|
|
399
|
+
|
|
400
|
+
_seed_to_code_written(path)
|
|
401
|
+
# No _set_policy call.
|
|
402
|
+
|
|
403
|
+
result = _run_apply("conference/login-auth", path, proj)
|
|
404
|
+
assert result.returncode == 0
|
|
405
|
+
_, _, state = state_machine.current_slice(path)
|
|
406
|
+
assert state == "apply_passed"
|
|
407
|
+
|
|
408
|
+
def test_pause_at_end_behaves_like_pause_on_failure(
|
|
409
|
+
self, session_factory, project_factory
|
|
410
|
+
):
|
|
411
|
+
"""Reserved value: same behaviour as pause_on_failure for now."""
|
|
412
|
+
path = session_factory()
|
|
413
|
+
proj = project_factory()
|
|
414
|
+
(proj / "src" / "views").mkdir(parents=True, exist_ok=True)
|
|
415
|
+
(proj / "src" / "views" / "MeetingRoom.vue").write_text(_PASSING_VUE)
|
|
416
|
+
|
|
417
|
+
_seed_to_code_written(path)
|
|
418
|
+
_set_policy(path, "pause_at_end")
|
|
419
|
+
|
|
420
|
+
result = _run_apply("conference/login-auth", path, proj)
|
|
421
|
+
assert result.returncode == 0
|
|
422
|
+
_, _, state = state_machine.current_slice(path)
|
|
423
|
+
assert state == "not_started"
|
|
424
|
+
|
|
425
|
+
def test_unknown_policy_is_safe_pause_each(
|
|
426
|
+
self, session_factory, project_factory
|
|
427
|
+
):
|
|
428
|
+
"""An unrecognised policy value must not auto-advance — fail closed."""
|
|
429
|
+
path = session_factory()
|
|
430
|
+
proj = project_factory()
|
|
431
|
+
(proj / "src" / "views").mkdir(parents=True, exist_ok=True)
|
|
432
|
+
(proj / "src" / "views" / "MeetingRoom.vue").write_text(_PASSING_VUE)
|
|
433
|
+
|
|
434
|
+
_seed_to_code_written(path)
|
|
435
|
+
_set_policy(path, "yolo_mode")
|
|
436
|
+
|
|
437
|
+
result = _run_apply("conference/login-auth", path, proj)
|
|
438
|
+
assert result.returncode == 0
|
|
439
|
+
_, _, state = state_machine.current_slice(path)
|
|
440
|
+
assert state == "apply_passed", "unknown policy must not auto-advance"
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
class TestAutoAdvanceOnFailure:
|
|
444
|
+
def test_pause_on_failure_apply_fail_still_pauses(
|
|
445
|
+
self, session_factory, project_factory
|
|
446
|
+
):
|
|
447
|
+
path = session_factory()
|
|
448
|
+
proj = project_factory()
|
|
449
|
+
(proj / "src" / "views").mkdir(parents=True, exist_ok=True)
|
|
450
|
+
(proj / "src" / "views" / "MeetingRoom.vue").write_text(_NO_ENTRY_VUE)
|
|
451
|
+
|
|
452
|
+
_seed_to_code_written(path)
|
|
453
|
+
_set_policy(path, "pause_on_failure")
|
|
454
|
+
|
|
455
|
+
result = _run_apply("conference/login-auth", path, proj)
|
|
456
|
+
assert result.returncode == 1
|
|
457
|
+
|
|
458
|
+
idx, sid, state = state_machine.current_slice(path)
|
|
459
|
+
# Cursor stays on this slice; state is apply_failed; AI must regenerate.
|
|
460
|
+
assert idx == 0
|
|
461
|
+
assert sid == "conference/login-auth"
|
|
462
|
+
assert state == "apply_failed"
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
class TestAutoAdvanceLastSlice:
|
|
466
|
+
def test_last_slice_auto_advance_lands_on_all_done(
|
|
467
|
+
self, session_factory, project_factory
|
|
468
|
+
):
|
|
469
|
+
path = session_factory(confirmed_plan=["conference/login-auth"])
|
|
470
|
+
proj = project_factory()
|
|
471
|
+
(proj / "src" / "views").mkdir(parents=True, exist_ok=True)
|
|
472
|
+
(proj / "src" / "views" / "MeetingRoom.vue").write_text(_PASSING_VUE)
|
|
473
|
+
|
|
474
|
+
_seed_to_code_written(path)
|
|
475
|
+
_set_policy(path, "pause_on_failure")
|
|
476
|
+
|
|
477
|
+
result = _run_apply("conference/login-auth", path, proj)
|
|
478
|
+
assert result.returncode == 0
|
|
479
|
+
idx, sid, state = state_machine.current_slice(path)
|
|
480
|
+
assert state == "all_done"
|