@deepstorm/cli 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 (148) hide show
  1. package/README.md +72 -0
  2. package/dist/agents/reef-inspect-figma.md +77 -0
  3. package/dist/agents/reef-review-backend.md.tmpl +112 -0
  4. package/dist/agents/reef-review-frontend.md.tmpl +78 -0
  5. package/dist/agents/reef-review-infra.md +47 -0
  6. package/dist/agents/reef-review-security.md.tmpl +80 -0
  7. package/dist/agents/reef-scope-analysis.md +64 -0
  8. package/dist/build-registry.js +375 -0
  9. package/dist/cli.js +8581 -0
  10. package/dist/config-schema.json +133 -0
  11. package/dist/env-examples/context7.env-example +19 -0
  12. package/dist/env-examples/feishu-wiki.env-example +16 -0
  13. package/dist/env-examples/figma.env-example +16 -0
  14. package/dist/env-examples/github.env-example +20 -0
  15. package/dist/env-examples/jira.env-example +20 -0
  16. package/dist/hooks/mcp-hook.sh +77 -0
  17. package/dist/hooks/reef-auto-format.sh.tmpl +72 -0
  18. package/dist/hooks/reef-block-dangerous.sh +70 -0
  19. package/dist/hooks/reef-hooks.json +72 -0
  20. package/dist/hooks/reef-intent-detect.sh +129 -0
  21. package/dist/hooks/reef-protect-files.sh +55 -0
  22. package/dist/hooks/reef-run-tests.sh +84 -0
  23. package/dist/hooks/reef-scope-check.sh +386 -0
  24. package/dist/hooks/reef-scope-ci.sh +28 -0
  25. package/dist/hooks/reef-scope-gate.sh +115 -0
  26. package/dist/hooks/reef-scope-pre-commit.sh.tmpl +28 -0
  27. package/dist/hooks/reef-scope-setup.sh +204 -0
  28. package/dist/hooks/reef-scope-split.sh +203 -0
  29. package/dist/hooks/sweep-hooks.json +14 -0
  30. package/dist/hooks/sweep-mcp-hook.sh +77 -0
  31. package/dist/hooks/tide-hooks.json +14 -0
  32. package/dist/hooks/tide-session-preload.sh +17 -0
  33. package/dist/mcp/code-hosting/github.json +20 -0
  34. package/dist/mcp/design-tools/figma.json +19 -0
  35. package/dist/mcp/docs-reference/context7.json +28 -0
  36. package/dist/mcp/e2e-testing/playwright.json +13 -0
  37. package/dist/mcp/knowledge-base/feishu-wiki.json +19 -0
  38. package/dist/mcp/project-management/jira.json +27 -0
  39. package/dist/mcp-skills/deepflow-mcp-feishu-wiki-read/SKILL.md +65 -0
  40. package/dist/mcp-skills/deepflow-mcp-feishu-wiki-write/SKILL.md +63 -0
  41. package/dist/mcp-skills/deepflow-mcp-figma-read/SKILL.md +98 -0
  42. package/dist/mcp-skills/deepflow-mcp-github-read/SKILL.md +62 -0
  43. package/dist/mcp-skills/deepflow-mcp-github-write/SKILL.md +63 -0
  44. package/dist/mcp-skills/deepflow-mcp-jira-read/SKILL.md +80 -0
  45. package/dist/mcp-skills/deepflow-mcp-jira-write/SKILL.md +74 -0
  46. package/dist/mcp-skills/deepflow-mcp-playwright-read/SKILL.md +79 -0
  47. package/dist/mcp-skills/deepstorm-mcp-feishu-wiki-read/SKILL.md +65 -0
  48. package/dist/mcp-skills/deepstorm-mcp-feishu-wiki-write/SKILL.md +63 -0
  49. package/dist/mcp-skills/deepstorm-mcp-figma-read/SKILL.md +98 -0
  50. package/dist/mcp-skills/deepstorm-mcp-github-read/SKILL.md +62 -0
  51. package/dist/mcp-skills/deepstorm-mcp-github-write/SKILL.md +63 -0
  52. package/dist/mcp-skills/deepstorm-mcp-jira-read/SKILL.md +80 -0
  53. package/dist/mcp-skills/deepstorm-mcp-jira-write/SKILL.md +74 -0
  54. package/dist/mcp-skills/deepstorm-mcp-playwright-read/SKILL.md +79 -0
  55. package/dist/registry.json +818 -0
  56. package/dist/skills/atoll-ops/SKILL.md +46 -0
  57. package/dist/skills/reef-commit/SKILL.md +127 -0
  58. package/dist/skills/reef-gen-backend/SKILL.md.tmpl +87 -0
  59. package/dist/skills/reef-gen-backend/variants/java/steps.md +28 -0
  60. package/dist/skills/reef-gen-backend/variants/python/steps.md +70 -0
  61. package/dist/skills/reef-gen-frontend/SKILL.md.tmpl +83 -0
  62. package/dist/skills/reef-gen-frontend/variants/angular/steps.md +30 -0
  63. package/dist/skills/reef-harden/EXAMPLES.md +89 -0
  64. package/dist/skills/reef-harden/SKILL.md +136 -0
  65. package/dist/skills/reef-pr/SKILL.md +97 -0
  66. package/dist/skills/reef-review/SKILL.md.tmpl +107 -0
  67. package/dist/skills/reef-scope/SKILL.md +134 -0
  68. package/dist/skills/reef-start/SKILL.md.tmpl +562 -0
  69. package/dist/skills/reef-start/references/jira-start-subagent.md +60 -0
  70. package/dist/skills/reef-style-backend/SKILL.md.tmpl +134 -0
  71. package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/chat-client.md +96 -0
  72. package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/rag.md +94 -0
  73. package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/structured-output.md +62 -0
  74. package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/tool-calling.md +68 -0
  75. package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/quick-reference.md +220 -0
  76. package/dist/skills/reef-style-backend/fragments/java/api-spec/quick-reference.md +148 -0
  77. package/dist/skills/reef-style-backend/fragments/java/db-migration/liquibase/examples/database-migration.md +131 -0
  78. package/dist/skills/reef-style-backend/fragments/java/db-migration/liquibase/quick-reference.md +103 -0
  79. package/dist/skills/reef-style-backend/fragments/java/dependency-management/quick-reference.md +119 -0
  80. package/dist/skills/reef-style-backend/fragments/java/exception-handling/examples/error-code-enum.md +101 -0
  81. package/dist/skills/reef-style-backend/fragments/java/exception-handling/quick-reference.md +181 -0
  82. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/controller.md +95 -0
  83. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/dto-mapper.md +121 -0
  84. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/infrastructure.md +179 -0
  85. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/service-entity.md +202 -0
  86. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/testing.md +107 -0
  87. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/quick-reference.md +83 -0
  88. package/dist/skills/reef-style-backend/fragments/java/orm/hibernate/quick-reference.md +150 -0
  89. package/dist/skills/reef-style-backend/fragments/java/security-redlines/quick-reference.md +197 -0
  90. package/dist/skills/reef-style-backend/fragments/java/test/data-jpa-test/examples/user-repository-test.md +69 -0
  91. package/dist/skills/reef-style-backend/fragments/java/test/data-jpa-test/quick-reference.md +101 -0
  92. package/dist/skills/reef-style-backend/fragments/java/test/junit5/examples/user-service-test.md +61 -0
  93. package/dist/skills/reef-style-backend/fragments/java/test/junit5/quick-reference.md +100 -0
  94. package/dist/skills/reef-style-backend/fragments/java/test/spring-mvc-test/examples/user-controller-test.md +61 -0
  95. package/dist/skills/reef-style-backend/fragments/java/test/spring-mvc-test/quick-reference.md +85 -0
  96. package/dist/skills/reef-style-backend/fragments/java/test/spring-service-test/examples/user-service-integration-test.md +56 -0
  97. package/dist/skills/reef-style-backend/fragments/java/test/spring-service-test/quick-reference.md +83 -0
  98. package/dist/skills/reef-style-backend/fragments/python/alembic-migration/quick-reference.md +77 -0
  99. package/dist/skills/reef-style-backend/fragments/python/api-spec/quick-reference.md +164 -0
  100. package/dist/skills/reef-style-backend/fragments/python/dependency-management/quick-reference.md +139 -0
  101. package/dist/skills/reef-style-backend/fragments/python/exception-handling/quick-reference.md +177 -0
  102. package/dist/skills/reef-style-backend/fragments/python/fastapi-quick-reference/quick-reference.md +101 -0
  103. package/dist/skills/reef-style-backend/fragments/python/langchain/quick-reference.md +135 -0
  104. package/dist/skills/reef-style-backend/fragments/python/pytest-testing/quick-reference.md +111 -0
  105. package/dist/skills/reef-style-backend/fragments/python/ruff-mypy-toolchain/quick-reference.md +83 -0
  106. package/dist/skills/reef-style-backend/fragments/python/security-redlines/quick-reference.md +207 -0
  107. package/dist/skills/reef-style-backend/fragments/python/sqlalchemy-orm/quick-reference.md +91 -0
  108. package/dist/skills/reef-style-backend/variants/java/examples/code-wrapping.md +227 -0
  109. package/dist/skills/reef-style-backend/variants/java/examples/contributor-pattern.md +97 -0
  110. package/dist/skills/reef-style-backend/variants/java/quick-reference.md +117 -0
  111. package/dist/skills/reef-style-backend/variants/python/examples/crud-router.md +71 -0
  112. package/dist/skills/reef-style-backend/variants/python/examples/pydantic-schema.md +45 -0
  113. package/dist/skills/reef-style-backend/variants/python/examples/pytest-fixture.md +60 -0
  114. package/dist/skills/reef-style-backend/variants/python/examples/sqlalchemy-model.md +31 -0
  115. package/dist/skills/reef-style-backend/variants/python/quick-reference.md +111 -0
  116. package/dist/skills/reef-style-frontend/SKILL.md.tmpl +70 -0
  117. package/dist/skills/reef-style-frontend/fragments/css/tailwind/quick-reference.md +79 -0
  118. package/dist/skills/reef-style-frontend/fragments/test/vitest/examples/testing.md +150 -0
  119. package/dist/skills/reef-style-frontend/fragments/test/vitest/quick-reference.md +141 -0
  120. package/dist/skills/reef-style-frontend/fragments/ts-config/strict/quick-reference.md +112 -0
  121. package/dist/skills/reef-style-frontend/fragments/ui-lib/primeng/examples/ui-components.md +134 -0
  122. package/dist/skills/reef-style-frontend/fragments/ui-lib/primeng/quick-reference.md +66 -0
  123. package/dist/skills/reef-style-frontend/variants/angular/examples/code-wrapping.md +252 -0
  124. package/dist/skills/reef-style-frontend/variants/angular/examples/component-types-pipes.md +56 -0
  125. package/dist/skills/reef-style-frontend/variants/angular/examples/entity-types.md +100 -0
  126. package/dist/skills/reef-style-frontend/variants/angular/examples/forms-layer.md +119 -0
  127. package/dist/skills/reef-style-frontend/variants/angular/examples/service-routing.md +95 -0
  128. package/dist/skills/reef-style-frontend/variants/angular/quick-reference.md +100 -0
  129. package/dist/skills/reef-testcase/SKILL.md +138 -0
  130. package/dist/skills/reef-testcase/references/coverage-dimensions.md +99 -0
  131. package/dist/skills/reef-testcase/references/test-case-template.md +63 -0
  132. package/dist/skills/sweep-init/SKILL.md +286 -0
  133. package/dist/skills/sweep-init/scripts/flow-selector.mjs +305 -0
  134. package/dist/skills/sweep-plan/SKILL.md.tmpl +312 -0
  135. package/dist/skills/sweep-plan/references/test-flow-template.md +46 -0
  136. package/dist/skills/sweep-run/SKILL.md +437 -0
  137. package/dist/skills/sweep-run/scripts/env-manager.mjs +208 -0
  138. package/dist/skills/sweep-run/scripts/flow-parser.mjs +329 -0
  139. package/dist/skills/sweep-run/scripts/flow-selector.mjs +483 -0
  140. package/dist/skills/sweep-run/scripts/mcp-manager.mjs +208 -0
  141. package/dist/skills/sweep-run/scripts/spec-compiler.mjs +303 -0
  142. package/dist/skills/tide-discuss/SKILL.md.tmpl +449 -0
  143. package/dist/skills/tide-discuss/references/checklists.md +88 -0
  144. package/dist/skills/tide-discuss/references/data-format.md +237 -0
  145. package/dist/skills/tide-discuss/references/prd-template.md +134 -0
  146. package/dist/skills/tide-discuss/references/publish-flow.md +167 -0
  147. package/dist/skills/tide-discuss/references/role-prompts.md +105 -0
  148. package/package.json +38 -0
@@ -0,0 +1,60 @@
1
+ # 示例:pytest Fixture
2
+
3
+ ```python
4
+ # tests/conftest.py
5
+ import pytest
6
+ import pytest_asyncio
7
+ from httpx import AsyncClient, ASGITransport
8
+ from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
9
+
10
+ from app.main import app
11
+ from app.models.base import Base
12
+ from app.dependencies import get_async_session
13
+
14
+ # 测试用内存数据库
15
+ TEST_DATABASE_URL = "sqlite+aiosqlite:///./test.db"
16
+
17
+ @pytest.fixture(scope="session")
18
+ def event_loop():
19
+ """session 级别的 event loop"""
20
+ import asyncio
21
+ loop = asyncio.new_event_loop()
22
+ yield loop
23
+ loop.close()
24
+
25
+
26
+ @pytest_asyncio.fixture(scope="session")
27
+ async def engine():
28
+ """测试用引擎"""
29
+ engine = create_async_engine(TEST_DATABASE_URL, echo=False)
30
+ async with engine.begin() as conn:
31
+ await conn.run_sync(Base.metadata.create_all)
32
+ yield engine
33
+ async with engine.begin() as conn:
34
+ await conn.run_sync(Base.metadata.drop_all)
35
+ await engine.dispose()
36
+
37
+
38
+ @pytest_asyncio.fixture
39
+ async def db_session(engine):
40
+ """每个测试独立事务"""
41
+ async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
42
+ async with async_session() as session:
43
+ async with session.begin():
44
+ yield session
45
+ await session.rollback()
46
+
47
+
48
+ @pytest_asyncio.fixture
49
+ async def client(db_session: AsyncSession):
50
+ """HTTP 测试客户端"""
51
+
52
+ async def override_get_db():
53
+ yield db_session
54
+
55
+ app.dependency_overrides[get_async_session] = override_get_db
56
+ transport = ASGITransport(app=app)
57
+ async with AsyncClient(transport=transport, base_url="http://test") as ac:
58
+ yield ac
59
+ app.dependency_overrides.clear()
60
+ ```
@@ -0,0 +1,31 @@
1
+ # 示例:SQLAlchemy Model
2
+
3
+ ```python
4
+ # app/models/user.py
5
+ from datetime import datetime
6
+ from sqlalchemy import String, Integer, DateTime, Boolean, func
7
+ from sqlalchemy.orm import Mapped, mapped_column, relationship
8
+ from app.models.base import Base
9
+
10
+
11
+ class User(Base):
12
+ __tablename__ = "users"
13
+
14
+ id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
15
+ name: Mapped[str] = mapped_column(String(100), nullable=False)
16
+ email: Mapped[str] = mapped_column(String(255), unique=True, nullable=False)
17
+ hashed_password: Mapped[str] = mapped_column(String(255), nullable=False)
18
+ is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
19
+ created_at: Mapped[datetime] = mapped_column(
20
+ DateTime, server_default=func.now(), nullable=False
21
+ )
22
+ updated_at: Mapped[datetime] = mapped_column(
23
+ DateTime, server_default=func.now(), onupdate=func.now(), nullable=False
24
+ )
25
+
26
+ # 关联关系
27
+ posts: Mapped[list["Post"]] = relationship(back_populates="author", lazy="selectin")
28
+
29
+ def __repr__(self) -> str:
30
+ return f"User(id={self.id}, name={self.name}, email={self.email})"
31
+ ```
@@ -0,0 +1,111 @@
1
+ # 后端编码快速参考 — Python(FastAPI)
2
+
3
+ 按需加载。仅当你需要编写对应组件类型时阅读相关章节。
4
+
5
+ > 已安装子维度的规范,通过该维度的 `{value}.md` 文件阅读。本页只包含 Python 语言通用的核心规范。
6
+ >
7
+ > **跨维度规范(适用所有后端代码):**
8
+ > - [API 规范](api-spec.md) — RESTful 命名、统一响应体、OpenAPI、版本策略
9
+ > - [依赖管理规范](dependency-management.md) — pyproject.toml、版本声明、CVE
10
+ > - [异常处理深度规范](exception-handling.md) — AppError 层次、错误码、全局处理
11
+ > - [安全红线](security-redlines.md) — P0/P1 安全规则(必须遵守)
12
+
13
+ ## 推荐项目结构
14
+
15
+ ```
16
+ app/
17
+ ├── api/v1/ # FastAPI routers(按领域分组)
18
+ ├── core/ # 配置、中间件、全局依赖
19
+ ├── models/ # SQLAlchemy ORM models
20
+ ├── schemas/ # Pydantic request/response schemas
21
+ ├── services/ # 业务逻辑层
22
+ ├── migrations/ # Alembic migration files
23
+ ├── tasks/ # 后台任务(Celery/ARQ)
24
+ └── tests/ # pytest tests
25
+ ├── unit/ # 纯逻辑测试
26
+ └── integration/ # 集成测试(DB/API)
27
+ ```
28
+
29
+ ## 速查
30
+
31
+ | 场景 | 决策 |
32
+ | --- | --- |
33
+ | 字符串格式化 | 用 f-string(`f"Hello {name}"`),不用 `%` 或 `.format()` |
34
+ | 类型分发 | 用多态 / Protocol,不用 `isinstance()` 链 |
35
+ | 类型注释 | 都标注返回类型和参数类型(mypy 检查通过) |
36
+ | 参数顺序 | 路由可见参数 → body → query → dependency |
37
+ | 模块名 | snake_case,禁止驼峰 |
38
+ | 弃用 API | mypy / ruff 警告中的废弃 API 在同一次 PR 中替换为新 API |
39
+ | 类命名 | PascalCase(如 `UserService`、`CreateUserRequest`) |
40
+ | 函数/变量命名 | snake_case(如 `get_user()`、`user_service`) |
41
+ | 常量命名 | UPPER_SNAKE_CASE(如 `MAX_RETRY_COUNT`) |
42
+
43
+ ## 代码风格
44
+
45
+ ### 通用约定
46
+
47
+ - 变量命名有意义,禁止单字母(循环计数器 `i`/`j`/`k` 除外)
48
+ - 优先用类型别名 / Protocol 而不是裸字典或 `Any`
49
+ - f-string 替代 `%` 格式化或 `.format()`
50
+ - 用枚举(`StrEnum`)替代魔法字符串常量
51
+ - 用 `is` / `is not` 比较 `None`,不用 `== None`
52
+ - ruff 规则 `F`(Pyflakes) + `E`(pycodestyle) + `I`(isort) + `N`(pep8-naming) 全部通过
53
+ - 100 列折行(black 默认),运算符放行首
54
+
55
+ ### Capability 模式
56
+
57
+ 当实体层次需要按子类暴露能力时,在基类中用 `ABC.abstractmethod` 声明,各子类按需实现:
58
+
59
+ ```python
60
+ from abc import ABC, abstractmethod
61
+
62
+ class FormControl(ABC):
63
+ @abstractmethod
64
+ def supports_importing(self) -> bool: ...
65
+
66
+ class TextControl(FormControl):
67
+ def supports_importing(self) -> bool: return True
68
+
69
+ class NumberControl(FormControl):
70
+ def supports_importing(self) -> bool: return False
71
+ ```
72
+
73
+ **适用场景:** 基类有多个子类,且某个操作只在部分子类上有意义。
74
+ **替代方案:** 用 `isinstance()` 判断 → ✗ 不推荐,违反开闭原则。
75
+ **优势:** 开闭原则 — 新增子类只需在自己的类中覆盖方法;新增能力只需在基类加方法 + 各子类实现。
76
+
77
+ ## 类型注释规则
78
+
79
+ | 声明位置 | 需要类型注释 |
80
+ |---------|-------------|
81
+ | 函数参数 | ✓ 是 |
82
+ | 函数返回值 | ✓ 是(`-> None` 也必须标注) |
83
+ | 模块级变量 | ✓ 是 |
84
+ | 类属性 / Pydantic Field | ✓ 是 |
85
+ | 循环变量 / 列表推导 | ✗ 否 |
86
+
87
+ ## 错误处理模式
88
+
89
+ ```python
90
+ # ✅ 好:统一异常 + 全局 handler
91
+ from fastapi import HTTPException
92
+
93
+ class AppError(HTTPException):
94
+ def __init__(self, code: str, message: str, status_code: int = 400):
95
+ self.code = code
96
+ super().__init__(status_code=status_code, detail=message)
97
+
98
+ # ✅ 好:Service 层抛自定义业务异常
99
+ class UserNotFoundError(AppError):
100
+ def __init__(self, user_id: int):
101
+ super().__init__(code="USER_NOT_FOUND", message=f"User {user_id} not found", status_code=404)
102
+ ```
103
+
104
+ ## 常见坑
105
+
106
+ | 场景 | 问题 | 正确做法 |
107
+ |------|------|---------|
108
+ | `isinstance()` 分发 | Service 层用 `isinstance()` 链判断所有子类型 | 优先在基类中用抽象方法 / Protocol |
109
+ | 裸 `async` 阻塞 | `async` 函数内调了同步 IO 操作 | 用 `httpx.AsyncClient`、`asyncio.to_thread`、异步 ORM |
110
+ | N+1 查询 | 循环内访问关联对象的属性,逐条 SELECT | 用 `selectinload()` / `joinedload()` 主动预加载 |
111
+ | 类型不兼容 | mypy strict 模式通不过 | 所有函数标注类型,`None` 必须显式声明 |
@@ -0,0 +1,70 @@
1
+ ---
2
+ name: reef-style-frontend
3
+ description: 前端编码规范({{reef.frontend.framework.label}})。创建/修改前端代码时必须按本技能输出。
4
+ user-invocable: false
5
+ allowed-tools: Bash(git:*), Bash({{reef.frontend.framework.buildTool}})
6
+ model: opus
7
+ deepstorm:
8
+ tool: reef
9
+ configKey: reef.frontend.framework
10
+ ---
11
+
12
+ # 前端编码规范
13
+
14
+ ## ⚠️ 触发条件(必须遵守)
15
+
16
+ 创建或修改任何前端文件时,必须通过 Skill tool 加载本技能,并按本技能中所有规范输出代码。对应技术栈:{{reef.frontend.framework.label}}。
17
+
18
+ > **仅对本次新增/修改的代码应用规范,不格式化已有代码。** 已有的、本次未修改的代码行保持原样。
19
+
20
+ ## 维度规范
21
+
22
+ {{#if reef.frontend.framework.styleRef}}
23
+ ### 框架规范
24
+ {{reef.frontend.framework.styleRef}}
25
+ {{/if}}
26
+
27
+ {{#if reef.frontend.tsConfig.styleRef}}
28
+ ### TypeScript 配置
29
+ {{reef.frontend.tsConfig.styleRef}}
30
+ {{/if}}
31
+
32
+ {{#if reef.frontend.css.styleRef}}
33
+ ### CSS 方案
34
+ {{reef.frontend.css.styleRef}}
35
+ {{/if}}
36
+
37
+ {{#if reef.frontend.uiLibrary.styleRef}}
38
+ ### UI 组件库
39
+ {{reef.frontend.uiLibrary.styleRef}}
40
+ {{/if}}
41
+
42
+ {{#if reef.frontend.test.styleRef}}
43
+ ### 🧪 测试规范
44
+
45
+ #### 通用原则
46
+ - **测试金字塔**:Unit(单元测试)→ Integration(集成测试)→ E2E(端到端测试)。越底层测试越多,运行速度越快
47
+ - **FIRST 原则**:Fast(快速)、Independent(独立)、Repeatable(可重复)、Self-validating(自验证)、Timely(及时)
48
+ - **AAA 模式**:Arrange(准备)→ Act(执行)→ Assert(断言)
49
+
50
+ #### 框架规范
51
+ {{reef.frontend.test.styleRef}}
52
+ {{/if}}
53
+
54
+ ## 知识文件
55
+
56
+ 以下文件位于本技能目录,安装时自动复制。内容按所选维度定制。
57
+
58
+ 加载本技能时先用 `Read` 读取 `quick-reference.md` 了解核心规范,再按照上方「维度规范」中的链接选择对应文件加载(如 `primeng.md`、`vitest.md`),最后根据当前变更类型在 `examples/` 目录中选择示例文件:
59
+
60
+ | 文件 | 说明 |
61
+ |------|------|
62
+ | `quick-reference.md` | **编码规范速查**。包含核心规则和约定 |
63
+ | `{value}.md` | **维度规范**(如 `primeng.md`、`tailwind.md`)。按上方链接加载 |
64
+ | `examples/{文件}` | 按需加载示例文件(仅读与当前变更相关的) |
65
+
66
+ ## 使用方式
67
+
68
+ - **创建/修改前端代码时**:Skill tool 自动加载(见触发条件)
69
+ - `reef:reef-gen-frontend` — 编写前端代码时自动加载
70
+ - `reef:reef-review-frontend` — 审查前端代码时自动加载
@@ -0,0 +1,79 @@
1
+ # Tailwind CSS 规范
2
+
3
+ ## 概述
4
+
5
+ 使用 Tailwind CSS v4 的 utility-first 方式编写样式,不编写自定义 CSS。
6
+
7
+ ## 核心原则
8
+
9
+ - **优先使用 Tailwind utility class**,不写手写 CSS
10
+ - **组件样式写在模板的 `class` 属性中**,不在独立 CSS 文件中
11
+ - **仅在以下场景使用 `@apply`**:多个重复使用的 utility 组合
12
+ - **响应式**:使用 `sm:` / `md:` / `lg:` / `xl:` 前缀
13
+
14
+ ## 常用模式
15
+
16
+ ### 布局
17
+
18
+ ```html
19
+ <!-- Flex 布局 -->
20
+ <div class="flex items-center justify-between gap-4">
21
+ <div class="flex-1">左侧</div>
22
+ <div>右侧</div>
23
+ </div>
24
+
25
+ <!-- Grid 布局 -->
26
+ <div class="grid grid-cols-3 gap-6">
27
+ <div class="col-span-2">主区域</div>
28
+ <div>侧边栏</div>
29
+ </div>
30
+ ```
31
+
32
+ ### 间距
33
+
34
+ ```html
35
+ <!-- 内边距 -->
36
+ <div class="p-4 px-6 py-2 pt-0">
37
+
38
+ <!-- 外边距 -->
39
+ <div class="m-4 mt-2 mx-auto">
40
+ ```
41
+
42
+ ### 颜色
43
+
44
+ ```html
45
+ <!-- 使用 Tailwind 色板 -->
46
+ <div class="bg-blue-500 text-white hover:bg-blue-600">
47
+ <button class="text-gray-700 border border-gray-300 rounded">
48
+ ```
49
+
50
+ ### 圆角与阴影
51
+
52
+ ```html
53
+ <div class="rounded-lg shadow-sm">
54
+ <button class="rounded-full shadow-md">
55
+ ```
56
+
57
+ ### 暗色模式
58
+
59
+ ```html
60
+ <div class="bg-white dark:bg-gray-900 text-black dark:text-white">
61
+ ```
62
+
63
+ ## 自定义配置
64
+
65
+ ```css
66
+ /* app.css — 仅添加 Tailwind 不支持的全局样式 */
67
+ @import "tailwindcss";
68
+
69
+ @theme {
70
+ --color-primary: #3b82f6;
71
+ --color-secondary: #8b5cf6;
72
+ }
73
+ ```
74
+
75
+ ## 禁止事项
76
+
77
+ - ❌ 不写手写 CSS 覆盖 Tailwind utility
78
+ - ❌ 不使用 `!important`(Tailwind 的优先级系统已足够)
79
+ - ❌ 不创建大量 `@apply` 封装(失去 utility-first 的优势)
@@ -0,0 +1,150 @@
1
+ # 前端测试示例
2
+
3
+ ## 1. 组件测试
4
+
5
+ ```typescript
6
+ describe('FormListComponent', () => {
7
+ let fixture: ComponentFixture<FormListComponent>;
8
+
9
+ beforeEach(async () => {
10
+ TestBed.configureTestingModule({
11
+ imports: [FormListComponent],
12
+ providers: [provideHttpClient(withInterceptorsFromDi())],
13
+ });
14
+ fixture = TestBed.createComponent(FormListComponent);
15
+ });
16
+
17
+ it('should render form list', () => {
18
+ expect(fixture.nativeElement.querySelector('p-table')).toBeTruthy();
19
+ });
20
+ });
21
+ ```
22
+
23
+ 关键点:Standalone 组件直接 `imports: [Component]`;PrimeNG 子组件用 `overrideComponent` 跳过渲染;`provideHttpClientTesting()` + `HttpTestingController` 模拟请求。
24
+
25
+ ## 2. Service 测试
26
+
27
+ ```typescript
28
+ describe('FormService', () => {
29
+ let service: FormService;
30
+ let httpMock: HttpTestingController;
31
+
32
+ beforeEach(() => {
33
+ TestBed.configureTestingModule({
34
+ providers: [FormService, provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()],
35
+ });
36
+ service = TestBed.inject(FormService);
37
+ httpMock = TestBed.inject(HttpTestingController);
38
+ });
39
+
40
+ it('should GET forms', () => {
41
+ service.list(1).subscribe(res => expect(res.items.length).toBe(2));
42
+ const req = httpMock.expectOne('/api/v1/apps/1/forms');
43
+ expect(req.request.method).toBe('GET');
44
+ req.flush({ items: [{ id: 1 }, { id: 2 }], totalItems: 2 });
45
+ });
46
+ });
47
+ ```
48
+
49
+ ## 3. Unit 测试(Pipe / Signal / E2E)
50
+
51
+ ```typescript
52
+ // Pipe — 直接 new 实例,不依赖 TestBed
53
+ it('should format LocalDateTime', () => {
54
+ const pipe = new LocalDateTimePipe();
55
+ expect(pipe.transform(LocalDateTime.of(2025, 1, 15, 14, 30), 'yyyy-MM-dd')).toBe('2025-01-15');
56
+ });
57
+
58
+ // Signal
59
+ it('should update signal on submit', () => {
60
+ const comp = TestBed.createComponent(MyComponent).componentInstance;
61
+ comp.submit();
62
+ expect(comp.submitted()).toBe(true);
63
+ });
64
+
65
+ // E2E(Playwright)
66
+ test('should display form list', async ({ page }) => {
67
+ await page.goto('/apps/1/forms');
68
+ await expect(page.locator('p-table')).toBeVisible();
69
+ });
70
+ ```
71
+
72
+ ## 4. Testing Library 组件测试(推荐方式)
73
+
74
+ ```typescript
75
+ import { render, screen, fireEvent } from '@testing-library/angular';
76
+
77
+ describe('ButtonComponent', () => {
78
+ it('应使用 Testing Library render 渲染组件', async () => {
79
+ await render(ButtonComponent, { inputs: { label: '提交', disabled: false } });
80
+ expect(screen.getByRole('button', { name: '提交' })).toBeTruthy();
81
+ });
82
+
83
+ it('disabled 态应不可点击', async () => {
84
+ const onClick = vi.fn();
85
+ await render(ButtonComponent, { inputs: { label: '不可用', disabled: true, onClick } });
86
+ fireEvent.click(screen.getByRole('button'));
87
+ expect(onClick).not.toHaveBeenCalled();
88
+ });
89
+ });
90
+ ```
91
+
92
+ 关键点:`render()` 自动处理编译和变更检测;`screen.getByRole()` / `screen.getByText()` 按用户视角查找元素。
93
+
94
+ ## 5. 异步操作测试
95
+
96
+ ```typescript
97
+ describe('UserListComponent(异步加载)', () => {
98
+ it('加载完成后应显示用户列表', async () => {
99
+ const mockService = {
100
+ getUsers: vi.fn().mockResolvedValue([{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]),
101
+ };
102
+ await render(UserListComponent, {
103
+ componentProviders: [{ provide: UserService, useValue: mockService }],
104
+ });
105
+ expect(await screen.findByText('Alice')).toBeTruthy();
106
+ expect(await screen.findByText('Bob')).toBeTruthy();
107
+ });
108
+
109
+ it('加载失败时应显示错误提示', async () => {
110
+ await render(UserListComponent, {
111
+ componentProviders: [{ provide: UserService, useValue: { getUsers: vi.fn().mockRejectedValue(new Error('fail')) } }],
112
+ });
113
+ expect(await screen.findByText(/加载失败/i)).toBeTruthy();
114
+ });
115
+ });
116
+ ```
117
+
118
+ ## 6. 带依赖注入的 Service 测试
119
+
120
+ ```typescript
121
+ describe('UserService(httpResource)', () => {
122
+ let service: UserService;
123
+
124
+ beforeEach(() => {
125
+ TestBed.configureTestingModule({
126
+ providers: [UserService, provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()],
127
+ });
128
+ service = TestBed.inject(UserService);
129
+ });
130
+
131
+ it('应发送带 appId 的 GET 请求', () => {
132
+ const httpMock = TestBed.inject(HttpTestingController);
133
+ service.getUsers(1).subscribe(res => expect(res.items.length).toBe(1));
134
+ const req = httpMock.expectOne('/api/v1/apps/1/users');
135
+ expect(req.request.method).toBe('GET');
136
+ req.flush({ items: [{ id: 1, name: 'Alice' }], totalItems: 1 });
137
+ httpMock.verify();
138
+ });
139
+
140
+ it('应发送 POST 请求创建用户', () => {
141
+ const httpMock = TestBed.inject(HttpTestingController);
142
+ service.createUser(1, { name: 'Charlie' }).subscribe();
143
+ const req = httpMock.expectOne('/api/v1/apps/1/users');
144
+ expect(req.request.method).toBe('POST');
145
+ expect(req.request.body).toEqual({ name: 'Charlie' });
146
+ req.flush({ id: 3, name: 'Charlie' });
147
+ httpMock.verify();
148
+ });
149
+ });
150
+ ```
@@ -0,0 +1,141 @@
1
+ # Vitest 测试规范
2
+
3
+ 按需加载。编写测试时阅读相关章节。
4
+
5
+ > **完整示例代码见 `examples/testing.md`**。
6
+
7
+ ## 概述
8
+
9
+ 使用 Vitest 作为单元测试框架,配合 Testing Library 进行组件测试。
10
+
11
+ ## 文件与命名规范
12
+
13
+ - 测试文件放在 `__tests__/` 目录下,与被测文件同层级
14
+ - 测试文件后缀:`.test.ts` 优先于 `.spec.ts`
15
+ - describe/it 描述使用中文,说明被测功能
16
+ - 固定 import 写法:
17
+
18
+ ```typescript
19
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
20
+ import { render, screen, fireEvent } from '@testing-library/angular';
21
+ ```
22
+
23
+ ## 测试类型
24
+
25
+ | 测试类型 | 范围 | 工具 | 关键关注点 |
26
+ |---------|------|------|-----------|
27
+ | 单元测试 | 单个函数/方法 | Vitest | 逻辑正确性、边界条件 |
28
+ | 组件测试 | 单个组件 | Testing Library | 渲染、交互、事件 |
29
+
30
+ ## 基本模式
31
+
32
+ ```typescript
33
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
34
+ import { render, screen, fireEvent } from '@testing-library/angular';
35
+ import { ButtonComponent } from './button.component';
36
+
37
+ describe('ButtonComponent', () => {
38
+ // 初始化
39
+ beforeEach(async () => {
40
+ await render(ButtonComponent, {
41
+ inputs: { label: '提交' },
42
+ });
43
+ });
44
+
45
+ // 验证渲染
46
+ it('应该渲染带 label 的按钮', () => {
47
+ expect(screen.getByText('提交')).toBeTruthy();
48
+ });
49
+
50
+ // 验证交互
51
+ it('点击按钮时应触发 click 事件', async () => {
52
+ const onClick = vi.fn();
53
+ await render(ButtonComponent, {
54
+ inputs: { onClick },
55
+ });
56
+ fireEvent.click(screen.getByRole('button'));
57
+ expect(onClick).toHaveBeenCalledTimes(1);
58
+ });
59
+ });
60
+ ```
61
+
62
+ ## 异步测试
63
+
64
+ ```typescript
65
+ import { describe, it, expect, vi } from 'vitest';
66
+ import { render, screen } from '@testing-library/angular';
67
+
68
+ describe('UserProfileComponent', () => {
69
+ it('异步加载用户数据后应显示用户名', async () => {
70
+ // 使用 fake timers 控制异步行为
71
+ vi.useFakeTimers();
72
+
73
+ await render(UserProfileComponent, {
74
+ componentProviders: [
75
+ {
76
+ provide: UserService,
77
+ useValue: {
78
+ getUser: () => Promise.resolve({ name: 'Alice' }),
79
+ },
80
+ },
81
+ ],
82
+ });
83
+
84
+ // 等待异步操作完成
85
+ vi.runAllTimers();
86
+
87
+ expect(await screen.findByText('Alice')).toBeTruthy();
88
+
89
+ vi.useRealTimers();
90
+ });
91
+ });
92
+ ```
93
+
94
+ ## Signal 组件测试
95
+
96
+ ```typescript
97
+ import { describe, it, expect } from 'vitest';
98
+ import { render, screen, fireEvent } from '@testing-library/angular';
99
+ import { CounterComponent } from './counter.component';
100
+
101
+ describe('CounterComponent (Signal)', () => {
102
+ it('点击按钮后计数应增加', async () => {
103
+ await render(CounterComponent);
104
+
105
+ const button = screen.getByRole('button', { name: /增加/i });
106
+ expect(screen.getByText('计数: 0')).toBeTruthy();
107
+
108
+ fireEvent.click(button);
109
+
110
+ // Signal 是同步更新的,不需要额外等待
111
+ expect(screen.getByText('计数: 1')).toBeTruthy();
112
+ });
113
+ });
114
+ ```
115
+
116
+ ## Mock 规范
117
+
118
+ ```typescript
119
+ import { vi } from 'vitest';
120
+
121
+ // 模拟服务
122
+ const mockUserService = {
123
+ getUser: vi.fn().mockResolvedValue({ id: 1, name: 'Test' }),
124
+ };
125
+
126
+ // 模拟模块
127
+ vi.mock('./user.service', () => ({
128
+ UserService: vi.fn().mockImplementation(() => mockUserService),
129
+ }));
130
+
131
+ // 异步 mock
132
+ const mockFetch = vi.fn().mockRejectedValue(new Error('Network error'));
133
+ vi.stubGlobal('fetch', mockFetch);
134
+ ```
135
+
136
+ ## 覆盖率要求
137
+
138
+ - 语句覆盖率: ≥80%
139
+ - 分支覆盖率: ≥75%
140
+ - 函数覆盖率: ≥85%
141
+ - 行覆盖率: ≥80%