docs(zh-CN): update

This commit is contained in:
neo
2026-03-13 17:45:44 +08:00
parent f548ca3e19
commit 4c0107a322
88 changed files with 16872 additions and 280 deletions

View File

@@ -18,7 +18,8 @@ rules/
├── typescript/ # TypeScript/JavaScript specific
├── python/ # Python specific
├── golang/ # Go specific
── swift/ # Swift specific
── swift/ # Swift specific
└── php/ # PHP specific
```
* **common/** 包含通用原则 —— 没有语言特定的代码示例。
@@ -34,6 +35,7 @@ rules/
./install.sh python
./install.sh golang
./install.sh swift
./install.sh php
# Install multiple languages at once
./install.sh typescript python
@@ -54,6 +56,7 @@ cp -r rules/typescript ~/.claude/rules/typescript
cp -r rules/python ~/.claude/rules/python
cp -r rules/golang ~/.claude/rules/golang
cp -r rules/swift ~/.claude/rules/swift
cp -r rules/php ~/.claude/rules/php
# Attention ! ! ! Configure according to your actual project requirements; the configuration here is for reference only.
```
@@ -87,7 +90,7 @@ cp -r rules/swift ~/.claude/rules/swift
当语言特定规则与通用规则冲突时,**语言特定规则优先**(具体规则覆盖通用规则)。这遵循标准的分层配置模式(类似于 CSS 特异性或 `.gitignore` 优先级)。
* `rules/common/` 定义了适用于所有项目的通用默认值。
* `rules/golang/`、`rules/python/`、`rules/typescript/` 等在语言习惯用法不同的地方会覆盖这些默认值。
* `rules/golang/`、`rules/python/`、`rules/swift/`、`rules/php/`、`rules/typescript/` 等在语言习惯不同时覆盖这些默认值。
### 示例

View File

@@ -6,32 +6,33 @@
## 功能实现工作流程
0. **研究与复用** *(任何新实现之前强制进行)*
* **先进行 GitHub 代码搜索:** 在编写任何新内容之前,运行 `gh search repos``gh search code` 以查找现有的实现、模板和模式。
* **使用 Exa MCP 进行研究:** 在规划阶段使用 `exa-web-search` MCP 进行更广泛的研究、数据摄取和发现现有技术
* **检查包注册表:** 在编写工具代码之前,搜索 npm、PyPI、crates.io 和其他注册表。优先选择经过实战检验的库,而不是自己编写的解决方案
* **搜索可适配的实现:** 寻找能够解决 80% 以上问题并且可以分叉、移植或包装的开源项目
* 当满足要求时,优先采用或移植经过验证的方法,而不是编写全新的代码
0. **研究与复用** *(任何新实现前必须执行)*
* **先进行 GitHub 代码搜索:** 在编写任何新代码之前,运行 `gh search repos``gh search code` 以查找现有的实现、模板和模式。
* **其次查阅库文档:** 在实现之前,使用 Context7 或主要供应商文档来确认 API 行为、包的使用以及版本特定的细节
* **仅在以上两者不足时使用 Exa** 在 GitHub 搜索和主要文档之后,再使用 Exa 进行更广泛的网络研究或探索
* **检查包注册中心:** 在编写工具代码之前,先搜索 npm、PyPI、crates.io 和其他注册中心。优先选择经过实战检验的库,而不是自己动手实现
* **寻找可适配的实现:** 寻找能解决 80% 以上问题的开源项目,以便进行分叉、移植或封装
* 如果经过验证的方法能满足需求,优先采用或移植该方法,而不是编写全新的代码。
1. **先规划**
* 使用 **planner** 代理创建实施计划
* 编码前生成规划文档PRD、架构、系统设计、技术文档、任务列表
* 使用 **planner** 智能体来创建实施计划
* 编码前生成规划文档PRD、架构、系统设计、技术文档、任务列表
* 识别依赖项和风险
* 分解为多个阶段
2. **TDD 方法**
* 使用 **tdd-guide** 代理
* 先写测试 (RED)
* 实现以通过测试 (GREEN)
* 重构 (IMPROVE)
* 验证 80%+ 的覆盖率
* 使用 **tdd-guide** 智能体
*写测试RED
* 实现代码以通过测试GREEN
* 重构IMPROVE
* 验证 80% 以上的覆盖率
3. **代码审查**
* 编写代码后立即使用 **code-reviewer** 代理
* 处理 CRITICAL 和 HIGH 级别的问题
* 编写代码后立即使用 **code-reviewer** 智能体
* 解决 CRITICAL 和 HIGH 级别的问题
* 尽可能修复 MEDIUM 级别的问题
4. **提交与推送**
* 详细的提交信息
* 遵循约定式提交格式
* 关于提交信息格式和 PR 流程请参阅 [git-workflow.md](git-workflow.md)
* 提交信息格式和 PR 流程请参阅 [git-workflow.md](git-workflow.md)

View File

@@ -0,0 +1,90 @@
---
paths:
- "**/*.kt"
- "**/*.kts"
---
# Kotlin 编码风格
> 本文档在 [common/coding-style.md](../common/coding-style.md) 的基础上扩展了 Kotlin 相关内容。
## 格式化
* 使用 **ktlint****Detekt** 进行风格检查
* 遵循官方 Kotlin 代码风格 (`kotlin.code.style=official``gradle.properties` 中)
## 不可变性
* 优先使用 `val` 而非 `var` — 默认使用 `val`,仅在需要可变性时使用 `var`
* 对值类型使用 `data class`;在公共 API 中使用不可变集合 (`List`, `Map`, `Set`)
* 状态更新使用写时复制:`state.copy(field = newValue)`
## 命名
遵循 Kotlin 约定:
* 函数和属性使用 `camelCase`
* 类、接口、对象和类型别名使用 `PascalCase`
* 常量 (`const val``@JvmStatic`) 使用 `SCREAMING_SNAKE_CASE`
* 接口以行为而非 `I` 为前缀:使用 `Clickable` 而非 `IClickable`
## 空安全
* 绝不使用 `!!` — 优先使用 `?.`, `?:`, `requireNotNull()``checkNotNull()`
* 使用 `?.let {}` 进行作用域内的空安全操作
* 对于确实可能没有结果的函数,返回可为空的类型
```kotlin
// BAD
val name = user!!.name
// GOOD
val name = user?.name ?: "Unknown"
val name = requireNotNull(user) { "User must be set before accessing name" }.name
```
## 密封类型
使用密封类/接口来建模封闭的状态层次结构:
```kotlin
sealed interface UiState<out T> {
data object Loading : UiState<Nothing>
data class Success<T>(val data: T) : UiState<T>
data class Error(val message: String) : UiState<Nothing>
}
```
对密封类型始终使用详尽的 `when` — 不要使用 `else` 分支。
## 扩展函数
使用扩展函数实现工具操作,但要确保其可发现性:
* 放在以接收者类型命名的文件中 (`StringExt.kt`, `FlowExt.kt`)
* 限制作用域 — 不要向 `Any` 或过于泛化的类型添加扩展
## 作用域函数
使用合适的作用域函数:
* `let` — 空检查并转换:`user?.let { greet(it) }`
* `run` — 使用接收者计算结果:`service.run { fetch(config) }`
* `apply` — 配置对象:`builder.apply { timeout = 30 }`
* `also` — 副作用:`result.also { log(it) }`
* 避免深度嵌套作用域函数(最多 2 层)
## 错误处理
* 使用 `Result<T>` 或自定义密封类型
* 使用 `runCatching {}` 包装可能抛出异常的代码
* 绝不捕获 `CancellationException` — 始终重新抛出它
* 避免使用 `try-catch` 进行控制流
```kotlin
// BAD — using exceptions for control flow
val user = try { repository.getUser(id) } catch (e: NotFoundException) { null }
// GOOD — nullable return
val user: User? = repository.findUser(id)
```

View File

@@ -0,0 +1,18 @@
---
paths:
- "**/*.kt"
- "**/*.kts"
- "**/build.gradle.kts"
---
# Kotlin Hooks
> 此文件在 [common/hooks.md](../common/hooks.md) 的基础上扩展了 Kotlin 相关内容。
## PostToolUse Hooks
`~/.claude/settings.json` 中配置:
* **ktfmt/ktlint**: 在编辑后自动格式化 `.kt``.kts` 文件
* **detekt**: 在编辑 Kotlin 文件后运行静态分析
* **./gradlew build**: 在更改后验证编译

View File

@@ -0,0 +1,147 @@
---
paths:
- "**/*.kt"
- "**/*.kts"
---
# Kotlin 模式
> 此文件扩展了 [common/patterns.md](../common/patterns.md) 的内容,增加了 Kotlin 和 Android/KMP 特定的内容。
## 依赖注入
首选构造函数注入。使用 KoinKMP或 Hilt仅限 Android
```kotlin
// Koin — declare modules
val dataModule = module {
single<ItemRepository> { ItemRepositoryImpl(get(), get()) }
factory { GetItemsUseCase(get()) }
viewModelOf(::ItemListViewModel)
}
// Hilt — annotations
@HiltViewModel
class ItemListViewModel @Inject constructor(
private val getItems: GetItemsUseCase
) : ViewModel()
```
## ViewModel 模式
单一状态对象、事件接收器、单向数据流:
```kotlin
data class ScreenState(
val items: List<Item> = emptyList(),
val isLoading: Boolean = false
)
class ScreenViewModel(private val useCase: GetItemsUseCase) : ViewModel() {
private val _state = MutableStateFlow(ScreenState())
val state = _state.asStateFlow()
fun onEvent(event: ScreenEvent) {
when (event) {
is ScreenEvent.Load -> load()
is ScreenEvent.Delete -> delete(event.id)
}
}
}
```
## 仓库模式
* `suspend` 函数返回 `Result<T>` 或自定义错误类型
* 对于响应式流使用 `Flow`
* 协调本地和远程数据源
```kotlin
interface ItemRepository {
suspend fun getById(id: String): Result<Item>
suspend fun getAll(): Result<List<Item>>
fun observeAll(): Flow<List<Item>>
}
```
## 用例模式
单一职责,`operator fun invoke`
```kotlin
class GetItemUseCase(private val repository: ItemRepository) {
suspend operator fun invoke(id: String): Result<Item> {
return repository.getById(id)
}
}
class GetItemsUseCase(private val repository: ItemRepository) {
suspend operator fun invoke(): Result<List<Item>> {
return repository.getAll()
}
}
```
## expect/actual (KMP)
用于平台特定的实现:
```kotlin
// commonMain
expect fun platformName(): String
expect class SecureStorage {
fun save(key: String, value: String)
fun get(key: String): String?
}
// androidMain
actual fun platformName(): String = "Android"
actual class SecureStorage {
actual fun save(key: String, value: String) { /* EncryptedSharedPreferences */ }
actual fun get(key: String): String? = null /* ... */
}
// iosMain
actual fun platformName(): String = "iOS"
actual class SecureStorage {
actual fun save(key: String, value: String) { /* Keychain */ }
actual fun get(key: String): String? = null /* ... */
}
```
## 协程模式
* 在 ViewModels 中使用 `viewModelScope`,对于结构化的子工作使用 `coroutineScope`
* 对于来自冷流的 StateFlow 使用 `stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), initialValue)`
* 当子任务失败应独立处理时使用 `supervisorScope`
## 使用 DSL 的构建器模式
```kotlin
class HttpClientConfig {
var baseUrl: String = ""
var timeout: Long = 30_000
private val interceptors = mutableListOf<Interceptor>()
fun interceptor(block: () -> Interceptor) {
interceptors.add(block())
}
}
fun httpClient(block: HttpClientConfig.() -> Unit): HttpClient {
val config = HttpClientConfig().apply(block)
return HttpClient(config)
}
// Usage
val client = httpClient {
baseUrl = "https://api.example.com"
timeout = 15_000
interceptor { AuthInterceptor(tokenProvider) }
}
```
## 参考
有关详细的协程模式,请参阅技能:`kotlin-coroutines-flows`
有关模块和分层模式,请参阅技能:`android-clean-architecture`

View File

@@ -0,0 +1,83 @@
---
paths:
- "**/*.kt"
- "**/*.kts"
---
# Kotlin 安全
> 本文档基于 [common/security.md](../common/security.md),补充了 Kotlin 和 Android/KMP 相关的内容。
## 密钥管理
* 切勿在源代码中硬编码 API 密钥、令牌或凭据
* 本地开发时,使用 `local.properties`(已通过 git 忽略)来管理密钥
* 发布版本中,使用由 CI 密钥生成的 `BuildConfig` 字段
* 运行时密钥存储使用 `EncryptedSharedPreferences`Android或 KeychainiOS
```kotlin
// BAD
val apiKey = "sk-abc123..."
// GOOD — from BuildConfig (generated at build time)
val apiKey = BuildConfig.API_KEY
// GOOD — from secure storage at runtime
val token = secureStorage.get("auth_token")
```
## 网络安全
* 仅使用 HTTPS —— 配置 `network_security_config.xml` 以阻止明文传输
* 使用 OkHttp 的 `CertificatePinner` 或 Ktor 的等效功能为敏感端点固定证书
* 为所有 HTTP 客户端设置超时 —— 切勿使用默认值(可能为无限长)
* 在使用所有服务器响应前,先进行验证和清理
```xml
<!-- res/xml/network_security_config.xml -->
<network-security-config>
<base-config cleartextTrafficPermitted="false" />
</network-security-config>
```
## 输入验证
* 在处理或将用户输入发送到 API 之前,验证所有用户输入
* 对 Room/SQLDelight 使用参数化查询 —— 切勿将用户输入拼接到 SQL 语句中
* 清理用户输入中的文件路径,以防止路径遍历攻击
```kotlin
// BAD — SQL injection
@Query("SELECT * FROM items WHERE name = '$input'")
// GOOD — parameterized
@Query("SELECT * FROM items WHERE name = :input")
fun findByName(input: String): List<ItemEntity>
```
## 数据保护
* 在 Android 上,使用 `EncryptedSharedPreferences` 存储敏感键值数据
* 使用 `@Serializable` 并明确指定字段名 —— 不要泄露内部属性名
* 敏感数据不再需要时,从内存中清除
* 对序列化类使用 `@Keep` 或 ProGuard 规则,以防止名称混淆
## 身份验证
* 将令牌存储在安全存储中,而非普通的 SharedPreferences
* 实现令牌刷新机制,并正确处理 401/403 状态码
* 退出登录时清除所有身份验证状态令牌、缓存的用户数据、Cookie
* 对敏感操作使用生物特征认证(`BiometricPrompt`
## ProGuard / R8
* 为所有序列化模型(`@Serializable`、Gson、Moshi保留规则
* 为基于反射的库Koin、Retrofit保留规则
* 测试发布版本 —— 混淆可能会静默地破坏序列化
## WebView 安全
* 除非明确需要,否则禁用 JavaScript`settings.javaScriptEnabled = false`
* 在 WebView 中加载 URL 前,先进行验证
* 切勿暴露访问敏感数据的 `@JavascriptInterface` 方法
* 使用 `WebViewClient.shouldOverrideUrlLoading()` 来控制导航

View File

@@ -0,0 +1,129 @@
---
paths:
- "**/*.kt"
- "**/*.kts"
---
# Kotlin 测试
> 本文档扩展了 [common/testing.md](../common/testing.md),补充了 Kotlin 和 Android/KMP 特有的内容。
## 测试框架
* **kotlin.test** 用于跨平台 (KMP) — `@Test`, `assertEquals`, `assertTrue`
* **JUnit 4/5** 用于 Android 特定测试
* **Turbine** 用于测试 Flow 和 StateFlow
* **kotlinx-coroutines-test** 用于协程测试 (`runTest`, `TestDispatcher`)
## 使用 Turbine 测试 ViewModel
```kotlin
@Test
fun `loading state emitted then data`() = runTest {
val repo = FakeItemRepository()
repo.addItem(testItem)
val viewModel = ItemListViewModel(GetItemsUseCase(repo))
viewModel.state.test {
assertEquals(ItemListState(), awaitItem()) // initial state
viewModel.onEvent(ItemListEvent.Load)
assertTrue(awaitItem().isLoading) // loading
assertEquals(listOf(testItem), awaitItem().items) // loaded
}
}
```
## 使用伪造对象而非模拟对象
优先使用手写的伪造对象,而非模拟框架:
```kotlin
class FakeItemRepository : ItemRepository {
private val items = mutableListOf<Item>()
var fetchError: Throwable? = null
override suspend fun getAll(): Result<List<Item>> {
fetchError?.let { return Result.failure(it) }
return Result.success(items.toList())
}
override fun observeAll(): Flow<List<Item>> = flowOf(items.toList())
fun addItem(item: Item) { items.add(item) }
}
```
## 协程测试
```kotlin
@Test
fun `parallel operations complete`() = runTest {
val repo = FakeRepository()
val result = loadDashboard(repo)
advanceUntilIdle()
assertNotNull(result.items)
assertNotNull(result.stats)
}
```
使用 `runTest` — 它会自动推进虚拟时间并提供 `TestScope`
## Ktor MockEngine
```kotlin
val mockEngine = MockEngine { request ->
when (request.url.encodedPath) {
"/api/items" -> respond(
content = Json.encodeToString(testItems),
headers = headersOf(HttpHeaders.ContentType, ContentType.Application.Json.toString())
)
else -> respondError(HttpStatusCode.NotFound)
}
}
val client = HttpClient(mockEngine) {
install(ContentNegotiation) { json() }
}
```
## Room/SQLDelight 测试
* Room: 使用 `Room.inMemoryDatabaseBuilder()` 进行内存测试
* SQLDelight: 在 JVM 测试中使用 `JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)`
```kotlin
@Test
fun `insert and query items`() = runTest {
val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)
Database.Schema.create(driver)
val db = Database(driver)
db.itemQueries.insert("1", "Sample Item", "description")
val items = db.itemQueries.getAll().executeAsList()
assertEquals(1, items.size)
}
```
## 测试命名
使用反引号包裹的描述性名称:
```kotlin
@Test
fun `search with empty query returns all items`() = runTest { }
@Test
fun `delete item emits updated list without deleted item`() = runTest { }
```
## 测试组织
```
src/
├── commonTest/kotlin/ # Shared tests (ViewModel, UseCase, Repository)
├── androidUnitTest/kotlin/ # Android unit tests (JUnit)
├── androidInstrumentedTest/kotlin/ # Instrumented tests (Room, UI)
└── iosTest/kotlin/ # iOS-specific tests
```
最低测试覆盖率:每个功能都需要覆盖 ViewModel + UseCase。

View File

@@ -0,0 +1,47 @@
---
paths:
- "**/*.pl"
- "**/*.pm"
- "**/*.t"
- "**/*.psgi"
- "**/*.cgi"
---
# Perl 编码风格
> 本文档在 [common/coding-style.md](../common/coding-style.md) 的基础上,补充了 Perl 相关的内容。
## 标准
* 始终 `use v5.36`(启用 `strict``warnings``say` 和子程序签名)
* 使用子程序签名 — 切勿手动解包 `@_`
* 优先使用 `say` 而非显式换行的 `print`
## 不可变性
* 对所有属性使用 **Moo**,并配合 `is => 'ro'``Types::Standard`
* 切勿直接使用被祝福的哈希引用 — 始终通过 Moo/Moose 访问器
* **面向对象覆盖说明**:对于计算得出的只读值,使用 Moo `has` 属性并配合 `builder``default` 是可以接受的
## 格式化
使用 **perltidy** 并采用以下设置:
```
-i=4 # 4-space indent
-l=100 # 100 char line length
-ce # cuddled else
-bar # opening brace always right
```
## 代码检查
使用 **perlcritic**,严重级别设为 3并启用主题`core``pbp``security`
```bash
perlcritic --severity 3 --theme 'core || pbp || security' lib/
```
## 参考
查看技能:`perl-patterns`,了解全面的现代 Perl 惯用法和最佳实践。

View File

@@ -0,0 +1,23 @@
---
paths:
- "**/*.pl"
- "**/*.pm"
- "**/*.t"
- "**/*.psgi"
- "**/*.cgi"
---
# Perl 钩子
> 本文件在 [common/hooks.md](../common/hooks.md) 的基础上扩展了 Perl 相关的内容。
## PostToolUse 钩子
`~/.claude/settings.json` 中配置:
* **perltidy**:编辑后自动格式化 `.pl``.pm` 文件
* **perlcritic**:编辑 `.pm` 文件后运行代码检查
## 警告
* 警告在非脚本 `.pm` 文件中使用 `print` — 应使用 `say` 或日志模块(例如,`Log::Any`

View File

@@ -0,0 +1,77 @@
---
paths:
- "**/*.pl"
- "**/*.pm"
- "**/*.t"
- "**/*.psgi"
- "**/*.cgi"
---
# Perl 模式
> 本文档在 [common/patterns.md](../common/patterns.md) 的基础上扩展了 Perl 特定的内容。
## 仓储模式
在接口背后使用 **DBI****DBIx::Class**
```perl
package MyApp::Repo::User;
use Moo;
has dbh => (is => 'ro', required => 1);
sub find_by_id ($self, $id) {
my $sth = $self->dbh->prepare('SELECT * FROM users WHERE id = ?');
$sth->execute($id);
return $sth->fetchrow_hashref;
}
```
## DTOs / 值对象
使用带有 **Types::Standard****Moo** 类(相当于 Python 的 dataclasses
```perl
package MyApp::DTO::User;
use Moo;
use Types::Standard qw(Str Int);
has name => (is => 'ro', isa => Str, required => 1);
has email => (is => 'ro', isa => Str, required => 1);
has age => (is => 'ro', isa => Int);
```
## 资源管理
* 始终使用 **三参数 open** 配合 `autodie`
* 使用 **Path::Tiny** 进行文件操作
```perl
use autodie;
use Path::Tiny;
my $content = path('config.json')->slurp_utf8;
```
## 模块接口
使用 `Exporter 'import'` 配合 `@EXPORT_OK` — 绝不使用 `@EXPORT`
```perl
use Exporter 'import';
our @EXPORT_OK = qw(parse_config validate_input);
```
## 依赖管理
使用 **cpanfile** + **carton** 以实现可复现的安装:
```bash
carton install
carton exec prove -lr t/
```
## 参考
查看技能:`perl-patterns` 以获取全面的现代 Perl 模式和惯用法。

View File

@@ -0,0 +1,70 @@
---
paths:
- "**/*.pl"
- "**/*.pm"
- "**/*.t"
- "**/*.psgi"
- "**/*.cgi"
---
# Perl 安全
> 本文档在 [common/security.md](../common/security.md) 的基础上扩展了 Perl 相关的内容。
## 污染模式
* 在所有 CGI/面向 Web 的脚本中使用 `-T` 标志
* 在执行任何外部命令前,清理 `%ENV` (`$ENV{PATH}``$ENV{CDPATH}` 等)
## 输入验证
* 使用允许列表正则表达式进行去污化 — 绝不要使用 `/(.*)/s`
* 使用明确的模式验证所有用户输入:
```perl
if ($input =~ /\A([a-zA-Z0-9_-]+)\z/) {
my $clean = $1;
}
```
## 文件 I/O
* **仅使用三参数 open** — 绝不要使用两参数 open
* 使用 `Cwd::realpath` 防止路径遍历:
```perl
use Cwd 'realpath';
my $safe_path = realpath($user_path);
die "Path traversal" unless $safe_path =~ m{\A/allowed/directory/};
```
## 进程执行
* 使用 **列表形式的 `system()`** — 绝不要使用单字符串形式
* 使用 **IPC::Run3** 来捕获输出
* 绝对不要在反引号中使用变量插值
```perl
system('grep', '-r', $pattern, $directory); # safe
```
## SQL 注入预防
始终使用 DBI 占位符 — 绝不要将变量插值到 SQL 中:
```perl
my $sth = $dbh->prepare('SELECT * FROM users WHERE email = ?');
$sth->execute($email);
```
## 安全扫描
运行 **perlcritic** 并使用安全主题,严重级别设为 4 或更高:
```bash
perlcritic --severity 4 --theme security lib/
```
## 参考
有关全面的 Perl 安全模式、污染模式和安全 I/O请参阅技能`perl-security`

View File

@@ -0,0 +1,55 @@
---
paths:
- "**/*.pl"
- "**/*.pm"
- "**/*.t"
- "**/*.psgi"
- "**/*.cgi"
---
# Perl 测试
> 本文档在 [common/testing.md](../common/testing.md) 的基础上扩展了针对 Perl 的内容。
## 框架
在新项目中使用 **Test2::V0**(而非 Test::More
```perl
use Test2::V0;
is($result, 42, 'answer is correct');
done_testing;
```
## 测试运行器
```bash
prove -l t/ # adds lib/ to @INC
prove -lr -j8 t/ # recursive, 8 parallel jobs
```
始终使用 `-l` 以确保 `lib/` 位于 `@INC` 上。
## 覆盖率
使用 **Devel::Cover** —— 目标覆盖率 80%+
```bash
cover -test
```
## 模拟
* **Test::MockModule** —— 模拟现有模块上的方法
* **Test::MockObject** —— 从头创建测试替身
## 常见陷阱
* 测试文件末尾始终使用 `done_testing`
* 使用 `prove` 时切勿忘记 `-l` 标志
## 参考
有关使用 Test2::V0、prove 和 Devel::Cover 的详细 Perl TDD 模式,请参阅技能:`perl-testing`

View File

@@ -0,0 +1,36 @@
---
paths:
- "**/*.php"
- "**/composer.json"
---
# PHP 编码风格
> 此文件在 [common/coding-style.md](../common/coding-style.md) 的基础上扩展了 PHP 相关内容。
## 标准
* 遵循 **PSR-12** 的格式化和命名约定。
* 在应用程序代码中优先使用 `declare(strict_types=1);`
* 在所有新代码允许的地方使用标量类型提示、返回类型和类型化属性。
## 不可变性
* 对于跨越服务边界的数据,优先使用不可变的 DTO 和值对象。
* 在可能的情况下,对请求/响应负载使用 `readonly` 属性或不可变构造函数。
* 对于简单的映射使用数组;将业务关键的结构提升为显式类。
## 格式化
* 使用 **PHP-CS-Fixer****Laravel Pint** 进行格式化。
* 使用 **PHPStan****Psalm** 进行静态分析。
* 将 Composer 脚本纳入版本控制,以便在本地和 CI 中运行相同的命令。
## 错误处理
* 对于异常状态抛出异常;避免在新代码中返回 `false`/`null` 作为隐藏的错误通道。
* 在框架/请求输入到达领域逻辑之前,将其转换为经过验证的 DTO。
## 参考
有关更广泛的服务/仓库分层指导,请参阅技能:`backend-patterns`

View File

@@ -0,0 +1,25 @@
---
paths:
- "**/*.php"
- "**/composer.json"
- "**/phpstan.neon"
- "**/phpstan.neon.dist"
- "**/psalm.xml"
---
# PHP 钩子
> 此文件在 [common/hooks.md](../common/hooks.md) 的基础上扩展了 PHP 相关的内容。
## PostToolUse 钩子
`~/.claude/settings.json` 中配置:
* **Pint / PHP-CS-Fixer**:自动格式化编辑过的 `.php` 文件。
* **PHPStan / Psalm**:在类型化代码库中对编辑过的 PHP 文件运行静态分析。
* **PHPUnit / Pest**:当编辑影响到行为时,为被修改的文件或模块运行针对性测试。
## 警告
* 当编辑过的文件中存在 `var_dump``dd``dump``die()` 时发出警告。
* 当编辑的 PHP 文件添加了原始 SQL 或禁用了 CSRF/会话保护时发出警告。

View File

@@ -0,0 +1,33 @@
---
paths:
- "**/*.php"
- "**/composer.json"
---
# PHP 设计模式
> 本文档在 [common/patterns.md](../common/patterns.md) 的基础上,补充了 PHP 相关的内容。
## 精炼控制器,明确服务
* 保持控制器专注于传输层:认证、验证、序列化、状态码。
* 将业务规则移至应用/领域服务中,这些服务无需 HTTP 引导即可轻松测试。
## DTO 与值对象
* 对于请求、命令和外部 API 负载,用 DTO 替代结构复杂的关联数组。
* 对于货币、标识符、日期范围和其他受约束的概念,使用值对象。
## 依赖注入
* 依赖于接口或精简的服务契约,而非框架全局变量。
* 通过构造函数传递协作者,这样服务就无需依赖服务定位器查找,易于测试。
## 边界
* 当模型层职责超出持久化时,应将 ORM 模型与领域决策隔离。
* 将第三方 SDK 封装在小型的适配器之后,使代码库的其余部分依赖于你的契约,而非它们的。
## 参考
关于端点约定和响应格式的指导,请参见技能:`api-design`

View File

@@ -0,0 +1,34 @@
---
paths:
- "**/*.php"
- "**/composer.lock"
- "**/composer.json"
---
# PHP 安全
> 本文档在 [common/security.md](../common/security.md) 的基础上,补充了 PHP 相关的内容。
## 输入与输出
* 在框架边界验证请求输入(`FormRequest`、Symfony Validator 或显式 DTO 验证)。
* 默认在模板中转义输出;将原始 HTML 渲染视为需要合理解释的例外情况。
* 未经验证切勿信任查询参数、Cookie、请求头或上传文件的元数据。
## 数据库安全
* 对所有动态查询使用预处理语句(`PDO`、Doctrine、Eloquent 查询构建器)。
* 避免在控制器/视图中拼接 SQL 字符串。
* 谨慎限定 ORM 批量赋值范围,并明确列出可写入字段的白名单。
## 密钥与依赖项
* 从环境变量或密钥管理器中加载密钥,切勿从已提交的配置文件中读取。
* 在 CI 中运行 `composer audit`,并在添加依赖项前审查新包维护者的可信度。
* 审慎锁定主版本号,并及时移除已废弃的包。
## 认证与会话安全
* 使用 `password_hash()` / `password_verify()` 存储密码。
* 在身份验证和权限变更后重新生成会话标识符。
* 对状态变更的 Web 请求强制实施 CSRF 保护。

View File

@@ -0,0 +1,35 @@
---
paths:
- "**/*.php"
- "**/phpunit.xml"
- "**/phpunit.xml.dist"
- "**/composer.json"
---
# PHP 测试
> 本文档在 [common/testing.md](../common/testing.md) 的基础上,补充了 PHP 相关的内容。
## 测试框架
默认使用 **PHPUnit** 作为测试框架。如果项目已在使用 **Pest**,也是可以接受的。
## 覆盖率
```bash
vendor/bin/phpunit --coverage-text
# or
vendor/bin/pest --coverage
```
在 CI 中优先使用 **pcov****Xdebug**,并将覆盖率阈值设置在 CI 中,而不是作为团队内部的隐性知识。
## 测试组织
* 将快速的单元测试与涉及框架/数据库的集成测试分开。
* 使用工厂/构建器来生成测试数据,而不是手动编写大量的数组。
* 保持 HTTP/控制器测试专注于传输和验证;将业务规则移到服务层级的测试中。
## 参考
关于整个仓库范围内的 RED -> GREEN -> REFACTOR 循环,请参见技能:`tdd-workflow`

View File

@@ -10,19 +10,128 @@ paths:
> 本文件基于 [common/coding-style.md](../common/coding-style.md) 扩展,包含 TypeScript/JavaScript 特定内容。
## 类型与接口
使用类型使公共 API、共享模型和组件属性显式化、可读且可复用。
### 公共 API
* 为导出的函数、共享工具函数和公共类方法添加参数类型和返回类型
* 让 TypeScript 推断明显的局部变量类型
* 将重复的内联对象结构提取为命名类型或接口
```typescript
// WRONG: Exported function without explicit types
export function formatUser(user) {
return `${user.firstName} ${user.lastName}`
}
// CORRECT: Explicit types on public APIs
interface User {
firstName: string
lastName: string
}
export function formatUser(user: User): string {
return `${user.firstName} ${user.lastName}`
}
```
### 接口与类型别名
* 使用 `interface` 定义可能被扩展或实现的对象结构
* 使用 `type` 定义联合类型、交叉类型、元组、映射类型和工具类型
* 优先使用字符串字面量联合类型而非 `enum`,除非需要 `enum` 以实现互操作性
```typescript
interface User {
id: string
email: string
}
type UserRole = 'admin' | 'member'
type UserWithRole = User & {
role: UserRole
}
```
### 避免使用 `any`
* 在应用程序代码中避免使用 `any`
* 对外部或不受信任的输入使用 `unknown`,然后安全地缩小其类型范围
* 当值的类型依赖于调用者时,使用泛型
```typescript
// WRONG: any removes type safety
function getErrorMessage(error: any) {
return error.message
}
// CORRECT: unknown forces safe narrowing
function getErrorMessage(error: unknown): string {
if (error instanceof Error) {
return error.message
}
return 'Unexpected error'
}
```
### React 属性
* 使用命名的 `interface``type` 定义组件属性
* 显式地定义回调属性类型
* 除非有特定原因,否则不要使用 `React.FC`
```typescript
interface User {
id: string
email: string
}
interface UserCardProps {
user: User
onSelect: (id: string) => void
}
function UserCard({ user, onSelect }: UserCardProps) {
return <button onClick={() => onSelect(user.id)}>{user.email}</button>
}
```
### JavaScript 文件
*`.js``.jsx` 文件中,当类型能提高清晰度且迁移到 TypeScript 不可行时,使用 JSDoc
* 保持 JSDoc 与运行时行为一致
```javascript
/**
* @param {{ firstName: string, lastName: string }} user
* @returns {string}
*/
export function formatUser(user) {
return `${user.firstName} ${user.lastName}`
}
```
## 不可变性
使用展开运算符进行不可变更新:
```typescript
interface User {
id: string
name: string
}
// WRONG: Mutation
function updateUser(user, name) {
user.name = name // MUTATION!
function updateUser(user: User, name: string): User {
user.name = name // MUTATION!
return user
}
// CORRECT: Immutability
function updateUser(user, name) {
function updateUser(user: Readonly<User>, name: string): User {
return {
...user,
name
@@ -32,31 +141,56 @@ function updateUser(user, name) {
## 错误处理
使用 async/await 配合 try-catch
使用 async/await 配合 try-catch 并安全地缩小未知错误类型范围
```typescript
try {
const result = await riskyOperation()
return result
} catch (error) {
console.error('Operation failed:', error)
throw new Error('Detailed user-friendly message')
interface User {
id: string
email: string
}
declare function riskyOperation(userId: string): Promise<User>
function getErrorMessage(error: unknown): string {
if (error instanceof Error) {
return error.message
}
return 'Unexpected error'
}
const logger = {
error: (message: string, error: unknown) => {
// Replace with your production logger (for example, pino or winston).
}
}
async function loadUser(userId: string): Promise<User> {
try {
const result = await riskyOperation(userId)
return result
} catch (error: unknown) {
logger.error('Operation failed', error)
throw new Error(getErrorMessage(error))
}
}
```
## 输入验证
使用 Zod 进行基于模式的验证:
使用 Zod 进行基于模式的验证,并从模式推断类型
```typescript
import { z } from 'zod'
const schema = z.object({
const userSchema = z.object({
email: z.string().email(),
age: z.number().int().min(0).max(150)
})
const validated = schema.parse(input)
type UserInput = z.infer<typeof userSchema>
const validated: UserInput = userSchema.parse(input)
```
## Console.log