重新调整模板

This commit is contained in:
2026-06-07 11:28:45 +08:00
parent ae5466854f
commit 1bbcc65cf3
31 changed files with 448 additions and 1525 deletions

View File

@@ -30,8 +30,8 @@
从本模板复制出新技能仓库后,建议至少逐项确认:
- [ ] `python tests/run_tests.py -v` 能通过。
- [ ] `python scripts/main.py health` 能通过。
- [ ] `python tests/run_tests.py -v` 能通过(含 platform-kit 导入与无 vendored core 守护)
- [ ] `python scripts/main.py health` 能通过(输出 runtime diagnosticswarning 不导致非零退出)
- [ ] `python scripts/main.py version` 输出 JSON且 `skill` 与目录名 / `SKILL.md` / `constants.SKILL_SLUG` 一致。
- [ ] 所有 DB / 文件写入都在 `IsolatedDataRoot`(或等价隔离)下测试,不写真实数据目录。
- [ ] 外部系统默认使用 mock / `FakeAdapter`,不访问真实 API不打开真实 RPA。
@@ -74,6 +74,11 @@ python tests/run_tests.py test_cli_smoke
| ``SKILL.md`` slug 与 ``constants.SKILL_SLUG`` | ``test_skill_metadata.py`` |
| DB / ``task_logs`` 骨架冒烟 | ``test_db_smoke.py`` |
| **adapter profile / 外呼授权策略** | ``test_adapter_profile_policy.py`` + ``adapter_test_utils.py``(非 ``test_`` 前缀,不自动当用例收集) |
| **无 vendored ``jiangchang_skill_core``** | ``test_platform_import.py`` |
| **runtime diagnostics 架构守护** | ``test_runtime_diagnostics_integration.py`` |
| **文档/runtime 标准守护** | ``test_template_runtime_standard.py`` |
复制为新技能后**不得删除**上述架构守护测试,除非同步更新模板标准并理解影响。
### 1.3 数据隔离(``_support.IsolatedDataRoot``

View File

@@ -39,6 +39,16 @@ def get_skill_root() -> str:
return _SKILL_ROOT
def platform_kit_version_patch(version: str = "1.0.11"):
"""Mock installed jiangchang-platform-kit version for health/diagnostics tests."""
from unittest.mock import patch
return patch(
"jiangchang_skill_core.runtime_diagnostics._platform_kit_version",
return_value=version,
)
class IsolatedDataRoot:
"""
在独立临时目录下模拟数据根,避免污染本机默认数据目录。

View File

@@ -8,7 +8,7 @@ import os
import unittest
from contextlib import redirect_stderr, redirect_stdout
from _support import IsolatedDataRoot
from _support import IsolatedDataRoot, platform_kit_version_patch
# scripts/ 已由 _support 注入 sys.path
from cli.app import main
@@ -27,10 +27,23 @@ class TestCliSmoke(unittest.TestCase):
self.assertIn("health", out)
def test_health_zero(self) -> None:
buf = io.StringIO()
with redirect_stdout(buf), redirect_stderr(io.StringIO()):
rc = main(["health"])
self.assertEqual(rc, 0)
old_record = os.environ.get("OPENCLAW_RECORD_VIDEO")
os.environ["OPENCLAW_RECORD_VIDEO"] = "0"
try:
buf = io.StringIO()
with platform_kit_version_patch(), redirect_stdout(buf), redirect_stderr(io.StringIO()):
rc = main(["health"])
self.assertEqual(rc, 0)
out = buf.getvalue()
self.assertIn("health:", out)
self.assertIn("python_executable:", out)
self.assertIn("platform_kit_version:", out)
self.assertIn("jiangchang_skill_core_file:", out)
finally:
if old_record is None:
os.environ.pop("OPENCLAW_RECORD_VIDEO", None)
else:
os.environ["OPENCLAW_RECORD_VIDEO"] = old_record
def test_version_json_and_matches_constants_slug(self) -> None:
buf = io.StringIO()

View File

@@ -0,0 +1,117 @@
# -*- coding: utf-8 -*-
"""防回归:公共能力必须从共享 platform-kit 导入,而非技能 vendored 副本。"""
from __future__ import annotations
import importlib.metadata as metadata
import os
import re
import sys
import unittest
from _support import get_scripts_dir, get_skill_root
def _parse_platform_kit_min_version(skill_md_text: str) -> str:
parts = skill_md_text.split("---", 2)
if len(parts) < 2:
raise ValueError("missing frontmatter")
m = re.search(
r"platform_kit_min_version:\s*[\"']?([^\"'\s]+)[\"']?",
parts[1],
)
if not m:
raise ValueError("platform_kit_min_version not found")
return m.group(1).strip()
class TestPlatformImportSource(unittest.TestCase):
def test_jiangchang_skill_core_not_from_skill_scripts(self) -> None:
scripts_dir = os.path.abspath(get_scripts_dir())
skill_root = os.path.abspath(get_skill_root())
vendored = os.path.join(scripts_dir, "jiangchang_skill_core")
self.assertFalse(
os.path.isdir(vendored),
f"vendored package must not exist: {vendored}",
)
import jiangchang_skill_core
import jiangchang_skill_core.rpa.video_session as video_session
core_file = os.path.abspath(jiangchang_skill_core.__file__)
video_file = os.path.abspath(video_session.__file__)
self.assertNotIn(
scripts_dir.lower(),
core_file.lower(),
msg=f"jiangchang_skill_core loaded from skill scripts: {core_file}",
)
self.assertNotIn(
skill_root.lower(),
core_file.lower(),
msg=f"jiangchang_skill_core loaded from skill tree: {core_file}",
)
self.assertNotIn(
scripts_dir.lower(),
video_file.lower(),
msg=f"video_session loaded from skill scripts: {video_file}",
)
from jiangchang_skill_core.rpa.video_session import RpaVideoSession
self.assertTrue(callable(RpaVideoSession))
def test_skill_tree_load_issue_helper(self) -> None:
from jiangchang_skill_core import is_jiangchang_skill_core_from_skill_tree
skill_root = os.path.abspath(get_skill_root())
fake = os.path.join(skill_root, "scripts", "jiangchang_skill_core", "__init__.py")
self.assertTrue(
is_jiangchang_skill_core_from_skill_tree(skill_root=skill_root, core_file=fake)
)
self.assertFalse(
is_jiangchang_skill_core_from_skill_tree(
skill_root=skill_root, core_file=sys.executable
)
)
def test_platform_kit_min_version_is_1_0_11(self) -> None:
from jiangchang_skill_core import version_ge
from util.constants import PLATFORM_KIT_MIN_VERSION
self.assertEqual(PLATFORM_KIT_MIN_VERSION, "1.0.11")
md_path = os.path.join(get_skill_root(), "SKILL.md")
with open(md_path, encoding="utf-8") as f:
md = f.read()
self.assertEqual(_parse_platform_kit_min_version(md), "1.0.11")
req_path = os.path.join(get_skill_root(), "requirements.txt")
with open(req_path, encoding="utf-8") as f:
req = f.read()
self.assertIn("jiangchang-platform-kit>=1.0.11", req)
installed = metadata.version("jiangchang-platform-kit")
self.assertTrue(
version_ge(installed, PLATFORM_KIT_MIN_VERSION),
msg=f"installed {installed!r} < required {PLATFORM_KIT_MIN_VERSION!r}",
)
def test_collect_runtime_diagnostics_importable(self) -> None:
from jiangchang_skill_core import collect_runtime_diagnostics
self.assertTrue(callable(collect_runtime_diagnostics))
def test_main_import_smoke(self) -> None:
scripts_dir = get_scripts_dir()
if scripts_dir not in sys.path:
sys.path.insert(0, scripts_dir)
from cli.app import build_parser
parser = build_parser()
self.assertIsNotNone(parser)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
"""Architecture guard: runtime diagnostics must come from platform-kit."""
from __future__ import annotations
import os
import unittest
from _support import get_scripts_dir, get_skill_root
_FORBIDDEN_DEFINITIONS = (
"class RuntimeDiagnostics",
"class RuntimeIssue",
"def collect_runtime_diagnostics",
"def format_runtime_health_lines",
"def runtime_diagnostics_dict",
"def _is_git_lfs_pointer",
"def _is_usable_audio_file",
"def _probe_background_music",
)
class TestRuntimeDiagnosticsIntegration(unittest.TestCase):
def test_no_vendored_jiangchang_skill_core(self) -> None:
vendored = os.path.join(get_scripts_dir(), "jiangchang_skill_core")
self.assertFalse(os.path.isdir(vendored), vendored)
def test_runtime_diagnostics_module_removed(self) -> None:
legacy = os.path.join(get_scripts_dir(), "util", "runtime_diagnostics.py")
self.assertFalse(os.path.isfile(legacy), legacy)
def test_util_py_files_do_not_reimplement_runtime_diagnostics(self) -> None:
util_dir = os.path.join(get_scripts_dir(), "util")
for name in os.listdir(util_dir):
if not name.endswith(".py"):
continue
path = os.path.join(util_dir, name)
with open(path, encoding="utf-8") as f:
text = f.read()
for forbidden in _FORBIDDEN_DEFINITIONS:
self.assertNotIn(
forbidden,
text,
msg=f"{name} must not define {forbidden}",
)
def test_task_service_imports_platform_kit_diagnostics(self) -> None:
path = os.path.join(get_scripts_dir(), "service", "task_service.py")
with open(path, encoding="utf-8") as f:
text = f.read()
self.assertIn("from jiangchang_skill_core import", text)
self.assertIn("collect_runtime_diagnostics", text)
self.assertIn("format_runtime_health_lines", text)
self.assertIn("PLATFORM_KIT_MIN_VERSION", text)
self.assertNotIn("from util.runtime_diagnostics import", text)
def test_collect_runtime_diagnostics_callable_from_platform_kit(self) -> None:
from jiangchang_skill_core import collect_runtime_diagnostics
from util.constants import PLATFORM_KIT_MIN_VERSION, SKILL_SLUG
from util.runtime_paths import get_skill_root
diag = collect_runtime_diagnostics(
skill_slug=SKILL_SLUG,
platform_kit_min_version=PLATFORM_KIT_MIN_VERSION,
skill_root=get_skill_root(),
)
self.assertEqual(diag.skill_slug, SKILL_SLUG)
self.assertEqual(diag.platform_kit_min_version, PLATFORM_KIT_MIN_VERSION)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,77 @@
# -*- coding: utf-8 -*-
"""Docs guard: template must promote shared runtime, not vendored jiangchang_skill_core."""
from __future__ import annotations
import os
import unittest
from _support import get_skill_root
DOC_PATHS = (
"README.md",
"SKILL.md",
"references/RUNTIME.md",
"references/CLI.md",
"references/DEVELOPMENT.md",
"references/RPA.md",
"references/CONFIG.md",
)
FORBIDDEN_PHRASES = (
"scripts/jiangchang_skill_core/:运行时",
"运行时与统一日志副本",
"vendor 源码在 scripts/jiangchang_skill_core",
"模板通过 vendor 拷贝引用",
"vendor 自 platform-kit",
"模板 vendor 拷贝在 scripts/jiangchang_skill_core",
)
POSITIVE_MARKERS = (
"jiangchang-platform-kit",
"1.0.11",
"共享 runtime",
)
class TestTemplateRuntimeStandard(unittest.TestCase):
def _read(self, rel_path: str) -> str:
path = os.path.join(get_skill_root(), rel_path)
with open(path, encoding="utf-8") as f:
return f.read()
def test_guarded_docs_avoid_vendored_core_guidance(self) -> None:
for rel_path in DOC_PATHS:
text = self._read(rel_path)
for phrase in FORBIDDEN_PHRASES:
self.assertNotIn(
phrase,
text,
msg=f"{rel_path} must not contain {phrase!r}",
)
def test_guarded_docs_promote_shared_runtime_standard(self) -> None:
combined = "\n".join(self._read(p) for p in DOC_PATHS)
for marker in POSITIVE_MARKERS:
self.assertIn(
marker,
combined,
msg=f"docs should mention {marker!r}",
)
self.assertTrue(
"不得 vendored" in combined or "不要 vendor" in combined,
msg="docs should state skills must not vendor jiangchang_skill_core",
)
def test_runtime_md_mentions_collect_runtime_diagnostics(self) -> None:
text = self._read("references/RUNTIME.md")
self.assertIn("collect_runtime_diagnostics", text)
def test_cli_md_mentions_health_runtime_diagnostics(self) -> None:
text = self._read("references/CLI.md")
self.assertIn("python_executable", text)
self.assertIn("platform_kit_version", text)
self.assertIn("jiangchang_skill_core_file", text)
if __name__ == "__main__":
unittest.main()