svharness 0.8.0 → 0.13.2
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/README.md +290 -61
- package/dist/adapters/claude-code.js +1 -0
- package/dist/adapters/codechat.js +1 -0
- package/dist/adapters/cursor.js +1 -0
- package/dist/adapters/generic.js +1 -0
- package/dist/adapters/index.js +2 -0
- package/dist/adapters/opencode.js +17 -0
- package/dist/adapters/qoder.js +1 -0
- package/dist/commands/apply.js +456 -71
- package/dist/commands/convert.js +371 -0
- package/dist/commands/init.js +156 -11
- package/dist/commands/references-apply-skills.js +47 -0
- package/dist/commands/wizard.js +442 -0
- package/dist/config/constants.js +7 -0
- package/dist/config/index.js +18 -0
- package/dist/config/load-config.js +54 -0
- package/dist/config/merge-options.js +115 -0
- package/dist/config/normalize.js +165 -0
- package/dist/config/save-config.js +40 -0
- package/dist/config/types.js +2 -0
- package/dist/core/agent-injector.js +58 -9
- package/dist/core/apply-project-entry.js +66 -0
- package/dist/core/build-project-entry.js +98 -0
- package/dist/core/doc-intake-paths.js +155 -0
- package/dist/core/extra-assets-intake.js +254 -0
- package/dist/core/markitdown-client.js +156 -0
- package/dist/core/next-steps.js +33 -22
- package/dist/core/project-ignore.js +53 -0
- package/dist/core/reference-apply-skills.js +35 -0
- package/dist/core/render-meta.js +2 -1
- package/dist/core/repomix-pack.js +3 -3
- package/dist/core/state.js +44 -24
- package/dist/index.js +211 -140
- package/dist/utils/harness-name.js +41 -0
- package/dist/utils/validate-args.js +147 -6
- package/dist/wiki/wikiTasksWriter.js +5 -6
- package/package.json +2 -1
- package/templates/_shared/apply-skills/harness-apply-skills-main.md +19 -78
- package/templates/_shared/build-rules/harness-build-rule-agent-agnostic.md +5 -5
- package/templates/_shared/build-rules/harness-build-rule-chinese-only.md +5 -5
- package/templates/_shared/build-rules/harness-build-rule-convert-check.md +46 -0
- package/templates/_shared/build-rules/harness-build-rule-memory-write.md +1 -1
- package/templates/_shared/build-rules/harness-build-rule-orchestrator-flow.md +36 -10
- package/templates/_shared/build-rules/harness-build-rule-skills-tasks-output.md +3 -2
- package/templates/_shared/build-rules/harness-build-rule-specs-schema.md +3 -3
- package/templates/_shared/build-rules/harness-build-rule-user-interaction.md +36 -16
- package/templates/_shared/build-skills/harness-build-skill-agent-env-merge.md +75 -0
- package/templates/_shared/build-skills/harness-build-skill-knowledge-builder.md +49 -85
- package/templates/_shared/build-skills/harness-build-skill-orchestrator.md +35 -18
- package/templates/_shared/build-skills/harness-build-skill-references-intake.md +91 -0
- package/templates/_shared/build-skills/harness-build-skill-spec-builder.md +19 -9
- package/templates/_shared/build-skills/harness-build-skill-wiki-writer.md +24 -24
- package/templates/_shared/build-skills/harness-build-skills-main.md +83 -0
- package/templates/_shared/meta/AGENTS_APPLY.md.ejs +139 -0
- package/templates/_shared/meta/{AGENTS.md.ejs → AGENTS_BUILD.md.ejs} +7 -5
- package/templates/_shared/meta/CHANGELOG.md.ejs +3 -3
- package/templates/_shared/meta/README.md.ejs +11 -9
- package/templates/_shared/meta/harness.yaml.ejs +28 -7
- package/templates/_shared/skeleton/baseline/code/.gitkeep +1 -0
- package/templates/_shared/skeleton/baseline/wiki/.gitkeep +1 -0
- package/templates/_shared/skeleton/references/apply-skills-registry.example.yaml +11 -0
- package/templates/_shared/skeleton/references/md/.gitkeep +1 -0
- package/templates/_shared/skeleton/references/raw/.gitkeep +1 -0
- package/templates/_shared/skeleton/references/yaml/.gitkeep +1 -0
- package/templates/_shared/skeleton/requirements/md/.gitkeep +1 -0
- package/templates/_shared/skeleton/requirements/raw/.gitkeep +1 -0
- package/templates/_shared/skeleton/requirements/yaml/.gitkeep +1 -0
- package/templates/android-xml/skeleton/agent-env/skills/harness-android-cli/SKILL.md +88 -0
- package/templates/android-xml/skeleton/agent-env/skills/harness-android-service-patterns/SKILL.md +205 -0
- package/templates/android-xml/skeleton/agent-env/skills/harness-android-xml-architecture/SKILL.md +138 -0
- package/templates/android-xml/skeleton/agent-env/skills/harness-lifecycle-management/SKILL.md +158 -0
- package/templates/android-xml/skeleton/agent-env/skills/harness-xml-ui/SKILL.md +112 -0
- package/templates/cpp/skeleton/agent-env/skills/harness-cmake-build/SKILL.md +163 -0
- package/templates/cpp/skeleton/agent-env/skills/harness-cpp-architecture/SKILL.md +157 -0
- package/templates/cpp/skeleton/agent-env/skills/harness-cpp-concurrency/SKILL.md +180 -0
- package/templates/cpp/skeleton/agent-env/skills/harness-memory-safety/SKILL.md +163 -0
- package/templates/cpp/skeleton/agent-env/skills/harness-modern-cpp/SKILL.md +149 -0
- package/templates/python/skeleton/agent-env/skills/harness-async-patterns/SKILL.md +162 -0
- package/templates/python/skeleton/agent-env/skills/harness-python-architecture/SKILL.md +160 -0
- package/templates/python/skeleton/agent-env/skills/harness-python-package-structure/SKILL.md +210 -0
- package/templates/python/skeleton/agent-env/skills/harness-python-performance/SKILL.md +207 -0
- package/templates/python/skeleton/agent-env/skills/harness-python-testing/SKILL.md +198 -0
- package/templates/svharness.config.example.yaml +40 -0
- package/templates/web-react/skeleton/agent-env/skills/harness-react-architecture/SKILL.md +177 -0
- package/templates/web-react/skeleton/agent-env/skills/harness-react-performance/SKILL.md +177 -0
- package/templates/web-react/skeleton/agent-env/skills/harness-react-testing/SKILL.md +193 -0
- package/templates/web-react/skeleton/agent-env/skills/harness-react-ui-patterns/SKILL.md +257 -0
- package/templates/web-react/skeleton/agent-env/skills/harness-state-management/SKILL.md +189 -0
- package/templates/_shared/skeleton/assets/baseline/code/.gitkeep +0 -1
- package/templates/_shared/skeleton/assets/baseline/wiki/.gitkeep +0 -1
- package/templates/_shared/skeleton/assets/raw/.gitkeep +0 -1
- package/templates/_shared/skeleton/assets/requirements/.gitkeep +0 -1
- /package/templates/_shared/skeleton/{assets/baseline/repomix → agent-env/_incoming/skills}/.gitkeep +0 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: harness-modern-cpp
|
|
3
|
+
description: Modern C++ (C++17/20) 最佳实践。覆盖智能指针、std::optional/span/variant、结构化绑定、constexpr 与范围算法。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Modern C++ 最佳实践
|
|
7
|
+
|
|
8
|
+
## 概述
|
|
9
|
+
|
|
10
|
+
优先使用 C++17/20 特性替代旧式 C 风格代码,提升代码安全性与可读性。
|
|
11
|
+
|
|
12
|
+
> **刚性约束已由 rules 加载:**
|
|
13
|
+
> - `seed-raii` — 禁止裸 new/delete,必须使用 RAII
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 智能指针
|
|
18
|
+
|
|
19
|
+
```cpp
|
|
20
|
+
// 唯一所有权 — std::unique_ptr
|
|
21
|
+
auto sensor = std::make_unique<TemperatureSensor>(args...);
|
|
22
|
+
|
|
23
|
+
// 共享所有权 — std::shared_ptr(谨慎使用,只在真正需要时)
|
|
24
|
+
auto config = std::make_shared<Configuration>(path);
|
|
25
|
+
|
|
26
|
+
// 观察者 — 裸指针/引用(非拥有)
|
|
27
|
+
void process(const ISensor* sensor) {
|
|
28
|
+
if (sensor != nullptr) {
|
|
29
|
+
auto data = sensor->read();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## std::optional
|
|
35
|
+
|
|
36
|
+
```cpp
|
|
37
|
+
// 可能无返回值的函数
|
|
38
|
+
std::optional<double> parseValue(const std::string& input) {
|
|
39
|
+
try {
|
|
40
|
+
return std::stod(input);
|
|
41
|
+
} catch (...) {
|
|
42
|
+
return std::nullopt; // 失败时不抛异常
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 使用
|
|
47
|
+
auto result = parseValue("3.14");
|
|
48
|
+
if (result.has_value()) {
|
|
49
|
+
use(result.value());
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 或使用 value_or 提供默认值
|
|
53
|
+
double v = parseValue(text).value_or(0.0);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## std::span — 连续内存视图
|
|
57
|
+
|
|
58
|
+
```cpp
|
|
59
|
+
// 替代 (T* data, size_t len) 参数对
|
|
60
|
+
double computeAverage(std::span<const double> values) {
|
|
61
|
+
double sum = 0.0;
|
|
62
|
+
for (double v : values) {
|
|
63
|
+
sum += v;
|
|
64
|
+
}
|
|
65
|
+
return sum / static_cast<double>(values.size());
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 可传入 vector 或数组
|
|
69
|
+
std::vector<double> vec = {1.0, 2.0, 3.0};
|
|
70
|
+
double avg = computeAverage(vec);
|
|
71
|
+
|
|
72
|
+
double arr[] = {4.0, 5.0, 6.0};
|
|
73
|
+
avg = computeAverage(arr);
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## std::variant — 类型安全联合体
|
|
77
|
+
|
|
78
|
+
```cpp
|
|
79
|
+
// 替代裸 union
|
|
80
|
+
using Command = std::variant<StartCmd, StopCmd, ConfigCmd, ErrorCmd>;
|
|
81
|
+
|
|
82
|
+
void handleCommand(const Command& cmd) {
|
|
83
|
+
std::visit([](const auto& c) {
|
|
84
|
+
// 编译器确保所有类型都被处理
|
|
85
|
+
c.execute();
|
|
86
|
+
}, cmd);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## 结构化绑定
|
|
91
|
+
|
|
92
|
+
```cpp
|
|
93
|
+
// 从 pair 解包
|
|
94
|
+
std::map<std::string, int> counts;
|
|
95
|
+
for (const auto& [key, value] : counts) {
|
|
96
|
+
// ...
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 从自定义类型解包
|
|
100
|
+
struct Point { double x, y, z; };
|
|
101
|
+
Point p{1.0, 2.0, 3.0};
|
|
102
|
+
auto [x, y, z] = p;
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## constexpr
|
|
106
|
+
|
|
107
|
+
```cpp
|
|
108
|
+
// 编译期计算
|
|
109
|
+
constexpr uint32_t crc32TableEntry(uint8_t index) {
|
|
110
|
+
uint32_t crc = index;
|
|
111
|
+
for (int i = 0; i < 8; ++i) {
|
|
112
|
+
crc = (crc & 1) ? (crc >> 1) ^ 0xEDB88320 : (crc >> 1);
|
|
113
|
+
}
|
|
114
|
+
return crc;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
constexpr auto CRC_TABLE = []() {
|
|
118
|
+
std::array<uint32_t, 256> table{};
|
|
119
|
+
for (size_t i = 0; i < 256; ++i) {
|
|
120
|
+
table[i] = crc32TableEntry(static_cast<uint8_t>(i));
|
|
121
|
+
}
|
|
122
|
+
return table;
|
|
123
|
+
}();
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## 范围算法 (C++20)
|
|
127
|
+
|
|
128
|
+
```cpp
|
|
129
|
+
#include <ranges>
|
|
130
|
+
#include <algorithm>
|
|
131
|
+
|
|
132
|
+
std::vector<int> data = {4, 2, 3, 1, 5, 2, 6};
|
|
133
|
+
|
|
134
|
+
// 链式操作
|
|
135
|
+
auto result = data
|
|
136
|
+
| std::views::filter([](int n) { return n > 2; })
|
|
137
|
+
| std::views::transform([](int n) { return n * n; })
|
|
138
|
+
| std::views::take(3);
|
|
139
|
+
|
|
140
|
+
// 输出: 16, 9, 25
|
|
141
|
+
for (int v : result) { /* ... */ }
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## 相关规则
|
|
147
|
+
|
|
148
|
+
- `seed-raii` — RAII 与智能指针
|
|
149
|
+
- `seed-header-guards` — 头文件保护符
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: harness-async-patterns
|
|
3
|
+
description: Python 异步编程模式。覆盖 asyncio 核心用法、协程管理、并发限流与错误处理。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 异步编程模式
|
|
7
|
+
|
|
8
|
+
## 概述
|
|
9
|
+
|
|
10
|
+
使用 `asyncio` 实现高并发 I/O 操作。遵循结构化并发原则,避免回调地狱。
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 基础模式
|
|
15
|
+
|
|
16
|
+
### 协程定义与调用
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
import asyncio
|
|
20
|
+
|
|
21
|
+
async def fetch_data(url: str) -> dict:
|
|
22
|
+
async with httpx.AsyncClient() as client:
|
|
23
|
+
resp = await client.get(url)
|
|
24
|
+
resp.raise_for_status()
|
|
25
|
+
return resp.json()
|
|
26
|
+
|
|
27
|
+
# 入口点
|
|
28
|
+
async def main() -> None:
|
|
29
|
+
data = await fetch_data("https://api.example.com/data")
|
|
30
|
+
print(data)
|
|
31
|
+
|
|
32
|
+
asyncio.run(main())
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 并发执行
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
async def main() -> None:
|
|
39
|
+
# 并发运行多个协程
|
|
40
|
+
async with asyncio.TaskGroup() as tg:
|
|
41
|
+
task1 = tg.create_task(fetch_data("/api/users"))
|
|
42
|
+
task2 = tg.create_task(fetch_data("/api/posts"))
|
|
43
|
+
task3 = tg.create_task(fetch_data("/api/comments"))
|
|
44
|
+
|
|
45
|
+
results = [task1.result(), task2.result(), task3.result()]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 并发限流
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import asyncio
|
|
52
|
+
from asyncio import Semaphore
|
|
53
|
+
|
|
54
|
+
class RateLimiter:
|
|
55
|
+
"""并发限流器。"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, max_concurrent: int) -> None:
|
|
58
|
+
self._semaphore = Semaphore(max_concurrent)
|
|
59
|
+
|
|
60
|
+
async def execute[T](self, coro: asyncio.Task[T]) -> T:
|
|
61
|
+
async with self._semaphore:
|
|
62
|
+
return await coro
|
|
63
|
+
|
|
64
|
+
# 使用
|
|
65
|
+
async def batch_process(items: list[str]) -> list[dict]:
|
|
66
|
+
limiter = RateLimiter(max_concurrent=10)
|
|
67
|
+
|
|
68
|
+
async with asyncio.TaskGroup() as tg:
|
|
69
|
+
tasks = [
|
|
70
|
+
tg.create_task(limiter.execute(process_item(item)))
|
|
71
|
+
for item in items
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
return [t.result() for t in tasks]
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 超时处理
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
async def fetch_with_timeout(url: str, timeout: float = 5.0) -> dict | None:
|
|
81
|
+
try:
|
|
82
|
+
async with asyncio.timeout(timeout):
|
|
83
|
+
return await fetch_data(url)
|
|
84
|
+
except asyncio.TimeoutError:
|
|
85
|
+
logger.warning("Request to %s timed out after %ss", url, timeout)
|
|
86
|
+
return None
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## 生产者-消费者模式
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
import asyncio
|
|
93
|
+
from asyncio import Queue
|
|
94
|
+
|
|
95
|
+
async def producer(queue: Queue[int]) -> None:
|
|
96
|
+
for i in range(100):
|
|
97
|
+
await queue.put(i)
|
|
98
|
+
await asyncio.sleep(0.01)
|
|
99
|
+
|
|
100
|
+
async def consumer(name: str, queue: Queue[int]) -> None:
|
|
101
|
+
while True:
|
|
102
|
+
item = await queue.get()
|
|
103
|
+
try:
|
|
104
|
+
print(f"{name} processing {item}")
|
|
105
|
+
await asyncio.sleep(0.1) # 模拟处理
|
|
106
|
+
finally:
|
|
107
|
+
queue.task_done()
|
|
108
|
+
|
|
109
|
+
async def main() -> None:
|
|
110
|
+
queue: Queue[int] = asyncio.Queue(maxsize=20)
|
|
111
|
+
|
|
112
|
+
async with asyncio.TaskGroup() as tg:
|
|
113
|
+
tg.create_task(producer(queue))
|
|
114
|
+
for i in range(3):
|
|
115
|
+
tg.create_task(consumer(f"worker-{i}", queue))
|
|
116
|
+
|
|
117
|
+
await queue.join() # 等待所有任务完成
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## 异步上下文管理器
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
import asyncio
|
|
124
|
+
from typing import Self
|
|
125
|
+
|
|
126
|
+
class AsyncDBConnection:
|
|
127
|
+
async def __aenter__(self) -> Self:
|
|
128
|
+
self.conn = await create_connection()
|
|
129
|
+
return self
|
|
130
|
+
|
|
131
|
+
async def __aexit__(
|
|
132
|
+
self,
|
|
133
|
+
exc_type: type[BaseException] | None,
|
|
134
|
+
exc_val: BaseException | None,
|
|
135
|
+
exc_tb: object,
|
|
136
|
+
) -> None:
|
|
137
|
+
await self.conn.close()
|
|
138
|
+
|
|
139
|
+
async def query(self, sql: str) -> list[dict]:
|
|
140
|
+
return await self.conn.execute(sql)
|
|
141
|
+
|
|
142
|
+
# 使用
|
|
143
|
+
async with AsyncDBConnection() as db:
|
|
144
|
+
results = await db.query("SELECT * FROM users")
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## 常见陷阱
|
|
148
|
+
|
|
149
|
+
| 陷阱 | 说明 | 解决方案 |
|
|
150
|
+
|------|------|---------|
|
|
151
|
+
| **阻塞调用阻塞事件循环** | 在协程中调用 `time.sleep()` 等同步阻塞 | 使用 `asyncio.to_thread()` 或 `loop.run_in_executor()` |
|
|
152
|
+
| **忘记 await** | 协程不会自动执行 | 始终 `await` 或 `create_task` |
|
|
153
|
+
| **没有超时** | 协程可能永远挂起 | 始终设置 `asyncio.timeout` |
|
|
154
|
+
| **TaskGroup 优于 gather** | `gather(return_exceptions=True)` 容易遗漏异常 | 优先使用 `TaskGroup` |
|
|
155
|
+
| **共享可变状态** | 协程间数据竞争 | 使用 `Queue` 或不可变数据结构 |
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## 相关规则
|
|
160
|
+
|
|
161
|
+
- `seed-type-annotations` — 类型注解
|
|
162
|
+
- `seed-context-managers` — 上下文管理器
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: harness-python-architecture
|
|
3
|
+
description: Python 项目架构与模块化设计。覆盖分层职责、依赖管理、配置分离与项目结构。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Python 架构设计指引
|
|
7
|
+
|
|
8
|
+
## 概述
|
|
9
|
+
|
|
10
|
+
采用分层模块化架构,保持低耦合高内聚。明确接口边界,避免循环依赖。
|
|
11
|
+
|
|
12
|
+
> **刚性约束已由 rules 加载:**
|
|
13
|
+
> - `seed-import-order` — 导入顺序规范
|
|
14
|
+
> - `seed-pep8-naming` — PEP 8 命名规范
|
|
15
|
+
> - `seed-type-annotations` — 类型注解
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 项目结构
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
project/
|
|
23
|
+
├── src/
|
|
24
|
+
│ ├── core/ # 核心抽象与接口
|
|
25
|
+
│ │ ├── interfaces/ # 抽象基类 / Protocol
|
|
26
|
+
│ │ ├── models/ # 数据模型
|
|
27
|
+
│ │ └── errors/ # 自定义异常
|
|
28
|
+
│ ├── services/ # 业务服务层
|
|
29
|
+
│ │ ├── service_a/
|
|
30
|
+
│ │ └── service_b/
|
|
31
|
+
│ ├── repositories/ # 数据访问层
|
|
32
|
+
│ ├── api/ # HTTP API 层
|
|
33
|
+
│ │ ├── routes/
|
|
34
|
+
│ │ ├── middleware/
|
|
35
|
+
│ │ └── dependencies/
|
|
36
|
+
│ ├── config/ # 配置管理
|
|
37
|
+
│ ├── utils/ # 工具函数
|
|
38
|
+
│ └── main.py # 应用入口
|
|
39
|
+
├── tests/
|
|
40
|
+
│ ├── unit/
|
|
41
|
+
│ ├── integration/
|
|
42
|
+
│ └── conftest.py
|
|
43
|
+
├── scripts/ # 脚本工具
|
|
44
|
+
├── pyproject.toml # 项目元数据与依赖
|
|
45
|
+
├── requirements.txt
|
|
46
|
+
└── README.md
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## 分层依赖规则
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
API Layer(路由入口)
|
|
53
|
+
│ 依赖 Service 层
|
|
54
|
+
▼
|
|
55
|
+
Service Layer(业务逻辑)
|
|
56
|
+
│ 依赖 Repository 层、Core 接口
|
|
57
|
+
▼
|
|
58
|
+
Repository Layer(数据访问)
|
|
59
|
+
│ 依赖 Core 模型
|
|
60
|
+
▼
|
|
61
|
+
Core Layer(核心抽象)
|
|
62
|
+
│ 零依赖或仅依赖标准库
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
| 层级 | 职责 | 可依赖 |
|
|
66
|
+
|------|------|--------|
|
|
67
|
+
| **API** | 请求处理、参数校验、响应序列化 | Service |
|
|
68
|
+
| **Service** | 业务编排、事务管理、事件发布 | Repository、Core |
|
|
69
|
+
| **Repository** | 数据存取(DB/API/文件) | Core |
|
|
70
|
+
| **Core** | 纯数据模型、接口协议、异常定义 | 标准库 |
|
|
71
|
+
|
|
72
|
+
## 依赖注入
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from abc import ABC, abstractmethod
|
|
76
|
+
from dataclasses import dataclass
|
|
77
|
+
|
|
78
|
+
# Core 接口
|
|
79
|
+
class UserRepository(ABC):
|
|
80
|
+
@abstractmethod
|
|
81
|
+
async def find_by_id(self, user_id: str) -> User | None:
|
|
82
|
+
...
|
|
83
|
+
|
|
84
|
+
# Service 通过构造器注入依赖
|
|
85
|
+
class UserService:
|
|
86
|
+
def __init__(self, repo: UserRepository) -> None:
|
|
87
|
+
self._repo = repo
|
|
88
|
+
|
|
89
|
+
async def get_user(self, user_id: str) -> UserDTO | None:
|
|
90
|
+
user = await self._repo.find_by_id(user_id)
|
|
91
|
+
if user is None:
|
|
92
|
+
return None
|
|
93
|
+
return UserDTO.from_domain(user)
|
|
94
|
+
|
|
95
|
+
# 组装(依赖注入入口)
|
|
96
|
+
def wire_dependencies() -> UserService:
|
|
97
|
+
repo = PostgresUserRepository(dsn=settings.DATABASE_URL)
|
|
98
|
+
return UserService(repo=repo)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## 配置管理
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from pydantic_settings import BaseSettings
|
|
105
|
+
|
|
106
|
+
class Settings(BaseSettings):
|
|
107
|
+
# 应用配置
|
|
108
|
+
APP_NAME: str = "my-service"
|
|
109
|
+
DEBUG: bool = False
|
|
110
|
+
|
|
111
|
+
# 数据库
|
|
112
|
+
DATABASE_URL: str
|
|
113
|
+
DB_POOL_SIZE: int = 10
|
|
114
|
+
|
|
115
|
+
# 外部服务
|
|
116
|
+
REDIS_URL: str = "redis://localhost:6379"
|
|
117
|
+
API_TIMEOUT_SECONDS: float = 30.0
|
|
118
|
+
|
|
119
|
+
model_config = {"env_file": ".env", "env_file_encoding": "utf-8"}
|
|
120
|
+
|
|
121
|
+
# 全局单例(线程安全,不可变)
|
|
122
|
+
settings = Settings()
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## 错误处理
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
class AppError(Exception):
|
|
129
|
+
"""应用基础异常。"""
|
|
130
|
+
def __init__(self, message: str, code: str) -> None:
|
|
131
|
+
self.message = message
|
|
132
|
+
self.code = code
|
|
133
|
+
super().__init__(message)
|
|
134
|
+
|
|
135
|
+
class NotFoundError(AppError):
|
|
136
|
+
def __init__(self, entity: str, entity_id: str) -> None:
|
|
137
|
+
super().__init__(
|
|
138
|
+
message=f"{entity} not found: {entity_id}",
|
|
139
|
+
code="NOT_FOUND",
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
class ValidationError(AppError):
|
|
143
|
+
def __init__(self, field: str, reason: str) -> None:
|
|
144
|
+
super().__init__(
|
|
145
|
+
message=f"Validation failed for {field}: {reason}",
|
|
146
|
+
code="VALIDATION_ERROR",
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# 统一错误处理
|
|
150
|
+
def handle_error(error: AppError) -> dict[str, str]:
|
|
151
|
+
return {"error": error.code, "message": error.message}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 相关规则
|
|
157
|
+
|
|
158
|
+
- `seed-import-order` — 导入顺序规范
|
|
159
|
+
- `seed-pep8-naming` — PEP 8 命名规范
|
|
160
|
+
- `seed-type-annotations` — 类型注解
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: harness-python-package-structure
|
|
3
|
+
description: Python 包与模块组织管理。覆盖包结构、__init__.py 规范、依赖管理与发布配置。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 包与模块组织
|
|
7
|
+
|
|
8
|
+
## 概述
|
|
9
|
+
|
|
10
|
+
规范的包结构是 Python 项目的基石。遵循标准约定,确保可导入性、可测试性与可分发性。
|
|
11
|
+
|
|
12
|
+
> **相关规则:**
|
|
13
|
+
> - `seed-import-order` — 导入顺序规范
|
|
14
|
+
> - `seed-pep8-naming` — PEP 8 命名规范
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 包结构
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
my_package/
|
|
22
|
+
├── __init__.py # 包入口,导出公共 API
|
|
23
|
+
├── _internal/ # 内部实现,不导出
|
|
24
|
+
│ ├── __init__.py
|
|
25
|
+
│ ├── _helpers.py
|
|
26
|
+
│ └── _constants.py
|
|
27
|
+
├── models/ # 数据模型
|
|
28
|
+
│ ├── __init__.py
|
|
29
|
+
│ ├── user.py
|
|
30
|
+
│ └── order.py
|
|
31
|
+
├── services/ # 业务服务
|
|
32
|
+
│ ├── __init__.py
|
|
33
|
+
│ ├── user_service.py
|
|
34
|
+
│ └── payment_service.py
|
|
35
|
+
├── cli.py # 命令行入口(可选)
|
|
36
|
+
└── __version__.py # 版本信息
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## __init__.py 规范
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
# my_package/__init__.py
|
|
43
|
+
"""My Package - 简短描述包的目的。
|
|
44
|
+
|
|
45
|
+
这个包提供了用户管理与支付处理功能。
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
from my_package.models.user import User, UserRole
|
|
49
|
+
from my_package.models.order import Order, OrderStatus
|
|
50
|
+
from my_package.services.user_service import UserService
|
|
51
|
+
from my_package.services.payment_service import PaymentService
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
"User",
|
|
55
|
+
"UserRole",
|
|
56
|
+
"Order",
|
|
57
|
+
"OrderStatus",
|
|
58
|
+
"UserService",
|
|
59
|
+
"PaymentService",
|
|
60
|
+
]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## pyproject.toml 配置
|
|
64
|
+
|
|
65
|
+
```toml
|
|
66
|
+
[build-system]
|
|
67
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
68
|
+
build-backend = "setuptools.backends._legacy:_Backend"
|
|
69
|
+
|
|
70
|
+
[project]
|
|
71
|
+
name = "my-package"
|
|
72
|
+
version = "0.1.0"
|
|
73
|
+
description = "A short description"
|
|
74
|
+
readme = "README.md"
|
|
75
|
+
requires-python = ">=3.11"
|
|
76
|
+
license = { text = "MIT" }
|
|
77
|
+
authors = [
|
|
78
|
+
{ name = "Developer", email = "dev@example.com" },
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
dependencies = [
|
|
82
|
+
"httpx>=0.27",
|
|
83
|
+
"pydantic>=2.0",
|
|
84
|
+
"python-dotenv>=1.0",
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
[project.optional-dependencies]
|
|
88
|
+
dev = [
|
|
89
|
+
"pytest>=8.0",
|
|
90
|
+
"pytest-asyncio>=0.23",
|
|
91
|
+
"pytest-cov>=5.0",
|
|
92
|
+
"ruff>=0.3",
|
|
93
|
+
"mypy>=1.8",
|
|
94
|
+
]
|
|
95
|
+
test = [
|
|
96
|
+
"pytest>=8.0",
|
|
97
|
+
"pytest-asyncio>=0.23",
|
|
98
|
+
"pytest-cov>=5.0",
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
[project.urls]
|
|
102
|
+
Homepage = "https://github.com/example/my-package"
|
|
103
|
+
|
|
104
|
+
[project.scripts]
|
|
105
|
+
my-cli = "my_package.cli:main"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## 模块组织原则
|
|
109
|
+
|
|
110
|
+
### 1. 单一职责
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
# 正确:职责分离
|
|
114
|
+
# services/user_service.py
|
|
115
|
+
class UserService:
|
|
116
|
+
"""仅处理用户相关的业务逻辑。"""
|
|
117
|
+
|
|
118
|
+
async def create_user(self, data: CreateUserDTO) -> User:
|
|
119
|
+
...
|
|
120
|
+
|
|
121
|
+
async def deactivate_user(self, user_id: str) -> None:
|
|
122
|
+
...
|
|
123
|
+
|
|
124
|
+
# services/notification_service.py
|
|
125
|
+
class NotificationService:
|
|
126
|
+
"""仅处理通知发送。"""
|
|
127
|
+
...
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 2. 显式 re-export
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
# services/__init__.py
|
|
134
|
+
from my_package.services.user_service import UserService
|
|
135
|
+
from my_package.services.payment_service import PaymentService
|
|
136
|
+
|
|
137
|
+
__all__ = ["UserService", "PaymentService"]
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### 3. 内部实现隐藏
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
# _internal/_helpers.py
|
|
144
|
+
# 单下划线前缀表示内部实现,不视为公共 API
|
|
145
|
+
def _format_currency(amount: float, currency: str) -> str:
|
|
146
|
+
...
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## 依赖管理
|
|
150
|
+
|
|
151
|
+
```txt
|
|
152
|
+
# requirements.txt — 锁定版本部署
|
|
153
|
+
httpx==0.27.0
|
|
154
|
+
pydantic==2.5.0
|
|
155
|
+
python-dotenv==1.0.1
|
|
156
|
+
uvicorn[standard]==0.27.0
|
|
157
|
+
|
|
158
|
+
# requirements-dev.txt — 开发依赖
|
|
159
|
+
-r requirements.txt
|
|
160
|
+
pytest==8.0.0
|
|
161
|
+
pytest-cov==5.0.0
|
|
162
|
+
ruff==0.3.0
|
|
163
|
+
mypy==1.8.0
|
|
164
|
+
pre-commit==3.6.0
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
或使用 Poetry:
|
|
168
|
+
|
|
169
|
+
```toml
|
|
170
|
+
[tool.poetry]
|
|
171
|
+
name = "my-package"
|
|
172
|
+
version = "0.1.0"
|
|
173
|
+
|
|
174
|
+
[tool.poetry.dependencies]
|
|
175
|
+
python = "^3.11"
|
|
176
|
+
httpx = "^0.27"
|
|
177
|
+
pydantic = "^2.5"
|
|
178
|
+
|
|
179
|
+
[tool.poetry.group.dev.dependencies]
|
|
180
|
+
pytest = "^8.0"
|
|
181
|
+
ruff = "^0.3"
|
|
182
|
+
mypy = "^1.8"
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## 命令行入口
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
# cli.py
|
|
189
|
+
import argparse
|
|
190
|
+
|
|
191
|
+
def main(argv: list[str] | None = None) -> None:
|
|
192
|
+
parser = argparse.ArgumentParser(description="My CLI Tool")
|
|
193
|
+
parser.add_argument("--verbose", "-v", action="store_true")
|
|
194
|
+
args = parser.parse_args(argv)
|
|
195
|
+
|
|
196
|
+
if args.verbose:
|
|
197
|
+
print("Verbose mode enabled")
|
|
198
|
+
print("Running my CLI...")
|
|
199
|
+
|
|
200
|
+
if __name__ == "__main__":
|
|
201
|
+
main()
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## 相关规则
|
|
207
|
+
|
|
208
|
+
- `seed-import-order` — 导入顺序规范
|
|
209
|
+
- `seed-pep8-naming` — PEP 8 命名规范
|
|
210
|
+
- `seed-type-annotations` — 类型注解
|