create-dwy 0.5.0 → 0.6.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/bin/index.js +1 -1
- package/package.json +2 -2
- package/src/create.js +61 -37
- package/src/sync.js +213 -53
- package/templates/claude-global/CLAUDE.md +92 -0
- package/templates/claude-global/rules/dwy-android-core.md +610 -0
- package/templates/claude-global/rules/dwy-api-design.md +110 -0
- package/templates/claude-global/rules/dwy-db-migration.md +46 -0
- package/templates/claude-global/rules/dwy-flutter-core.md +564 -0
- package/templates/claude-global/rules/dwy-flutter-network.md +222 -0
- package/templates/claude-global/rules/dwy-flutter-router.md +97 -0
- package/templates/claude-global/rules/dwy-flutter-testing.md +94 -0
- package/templates/claude-global/rules/dwy-git-commit-convention.md +82 -0
- package/templates/claude-global/rules/dwy-git-security.md +53 -0
- package/templates/claude-global/rules/dwy-payload-limits.md +178 -0
- package/templates/claude-global/rules/{prefer-open-source.md → dwy-prefer-open-source.md} +4 -0
- package/templates/claude-global/rules/dwy-project-structure.md +181 -0
- package/templates/claude-global/rules/{python-code-style.md → dwy-python-core.md} +314 -357
- package/templates/claude-global/rules/dwy-python-fastapi.md +272 -0
- package/templates/claude-global/rules/dwy-python-orm.md +61 -0
- package/templates/claude-global/rules/{backend-security.md → dwy-python-security.md} +116 -79
- package/templates/claude-global/rules/dwy-python-testing.md +53 -0
- package/templates/claude-global/rules/{server-security.md → dwy-server-security.md} +1 -2
- package/templates/claude-global/rules/dwy-tdd-development.md +70 -0
- package/templates/claude-global/rules/dwy-tech-stack.md +60 -0
- package/templates/claude-global/rules/dwy-vue-api.md +146 -0
- package/templates/claude-global/rules/{vue-code-style.md → dwy-vue-core.md} +361 -152
- package/templates/claude-global/rules/dwy-vue-pinia.md +75 -0
- package/templates/claude-global/rules/dwy-vue-testing.md +102 -0
- package/templates/claude-global/settings.json +20 -0
- package/templates/claude-global/skills/dwy-deploy-audit/SKILL.md +481 -0
- package/templates/claude-global/skills/dwy-deploy-audit/references/checks-data.md +117 -0
- package/templates/claude-global/skills/dwy-deploy-audit/references/checks-network.md +74 -0
- package/templates/claude-global/skills/dwy-deploy-audit/references/checks-runtime.md +184 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_base.sh +39 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_capacity.sh +278 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_db.sh +122 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_docker.sh +249 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_env.sh +64 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_https.sh +60 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_logs.sh +209 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_nginx.sh +92 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_resilience.sh +157 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_secrets.sh +327 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_services.sh +77 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_ssh.sh +56 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/run_all.sh +105 -0
- package/templates/claude-global/skills/dwy-docker/SKILL.md +187 -0
- package/templates/claude-global/skills/dwy-docker/references/ask-templates.md +142 -0
- package/templates/claude-global/skills/dwy-docker/references/templates.md +479 -0
- package/templates/claude-global/skills/dwy-docker/references/version-rules.md +76 -0
- package/templates/claude-global/skills/dwy-docker/scripts/query_dockerhub.py +204 -0
- package/templates/claude-global/skills/dwy-docker/scripts/scan_images.sh +185 -0
- package/templates/claude-global/skills/dwy-eapi/SKILL.md +433 -0
- package/templates/claude-global/skills/dwy-eapi/references/tasks-integration-guide.md +387 -0
- package/templates/claude-global/skills/dwy-ekit/SKILL.md +85 -0
- package/templates/claude-global/skills/dwy-ekit/references/data.md +135 -0
- package/templates/claude-global/skills/dwy-ekit/references/form.md +68 -0
- package/templates/claude-global/skills/dwy-ekit/references/request.md +183 -0
- package/templates/claude-global/skills/dwy-ekit/references/ui.md +109 -0
- package/templates/claude-global/skills/{dwy-frontend-eui → dwy-eui}/SKILL.md +129 -15
- package/templates/claude-global/skills/dwy-eui/references/eui-design-guide.md +257 -0
- package/templates/claude-global/skills/dwy-eui/references/eui-integration-guide.md +302 -0
- package/templates/claude-global/skills/dwy-eui/references/eui-landing-design-guide.md +437 -0
- package/templates/claude-global/skills/dwy-fullstack-scaffold/SKILL.md +214 -0
- package/templates/claude-global/skills/dwy-mirror-source/SKILL.md +275 -0
- package/templates/claude-global/skills/dwy-mirror-source/references/dockerfile-snippets.md +228 -0
- package/templates/claude-global/skills/dwy-mirror-source/references/mirror-providers.md +150 -0
- package/templates/claude-global/skills/dwy-mirror-source/scripts/__pycache__/check_mirrors.cpython-313.pyc +0 -0
- package/templates/claude-global/skills/dwy-mirror-source/scripts/apply_mirrors.py +666 -0
- package/templates/claude-global/skills/dwy-mirror-source/scripts/check_mirrors.py +868 -0
- package/templates/claude-global/skills/dwy-pentest/SKILL.md +344 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/__init__.py +1 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/__init__.cpython-311.pyc +0 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/config.cpython-311.pyc +0 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/executor.cpython-311.pyc +0 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/severity.cpython-311.pyc +0 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/validator.cpython-311.pyc +0 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/config.py +109 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/executor.py +157 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/severity.py +96 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/validator.py +113 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_auth.py +289 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_recon.py +217 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_report.py +343 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_scan.py +205 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_ssl.py +208 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_vuln.py +205 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_web.py +330 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pyproject.toml +14 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/setup_tools.sh +97 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/uv.lock +415 -0
- package/templates/claude-global/skills/dwy-publish/SKILL.md +135 -0
- package/templates/claude-global/skills/dwy-vue-core/SKILL.md +890 -0
- package/templates/project/backend/.dwy.hbs +5 -0
- package/templates/project/backend/.python-version +1 -0
- package/templates/project/backend/CLAUDE.md.hbs +40 -0
- package/templates/project/{web/backend → backend}/alembic/script.py.mako +2 -0
- package/templates/project/{web/backend → backend}/alembic.ini.hbs +0 -1
- package/templates/project/backend/deploy.sh.hbs +18 -0
- package/templates/project/{web → backend}/docker-compose.prod.yml.hbs +1 -1
- package/templates/project/{web/backend → backend}/pyproject.toml.hbs +4 -5
- package/templates/project/{web/backend → backend}/src/app/config.py.hbs +1 -1
- package/templates/project/backend/src/app/database.py +10 -0
- package/templates/project/backend/src/app/dependencies.py +5 -0
- package/templates/project/{web/backend → backend}/src/app/main.py.hbs +3 -3
- package/templates/project/backend/src/app/models/base.py +5 -0
- package/templates/project/web/CLAUDE.md.hbs +29 -36
- package/templates/project/web/{frontend → apps/web}/index.html.hbs +1 -0
- package/templates/project/web/{frontend → apps/web}/package.json.hbs +4 -3
- package/templates/project/web/apps/web/src/App.vue +9 -0
- package/templates/project/web/apps/web/src/api/client.ts +5 -0
- package/templates/project/web/apps/web/src/composables/.gitkeep +0 -0
- package/templates/project/web/apps/web/src/layouts/DefaultLayout.vue +8 -0
- package/templates/project/web/{frontend → apps/web}/src/router/index.ts +1 -1
- package/templates/project/web/{frontend → apps/web}/src/stores/auth.ts +4 -2
- package/templates/project/web/apps/web/src/style.css +2 -0
- package/templates/project/web/apps/web/src/types/router.d.ts +8 -0
- package/templates/project/web/apps/web/src/views/HomeView.vue +8 -0
- package/templates/project/web/apps/web/tsconfig.app.json +18 -0
- package/templates/project/web/apps/web/tsconfig.json +6 -0
- package/templates/project/web/{frontend → apps/web}/vite.config.ts.hbs +7 -1
- package/templates/project/web/package.json.hbs +16 -0
- package/templates/project/web/packages/.gitkeep +0 -0
- package/templates/project/web/pnpm-workspace.yaml +3 -0
- package/templates/claude-global/rules/agent-team-execution.md +0 -138
- package/templates/claude-global/rules/agent-team-patterns.md +0 -157
- package/templates/claude-global/rules/git-security.md +0 -144
- package/templates/claude-global/skills/dwy-auto-sync/SKILL.md +0 -52
- package/templates/claude-global/skills/dwy-backend-base/SKILL.md +0 -279
- package/templates/claude-global/skills/dwy-frontend-core/SKILL.md +0 -181
- package/templates/project/web/.claude/commands/deploy.md +0 -4
- package/templates/project/web/backend/src/app/models/base.py +0 -5
- package/templates/project/web/deploy.sh.hbs +0 -113
- package/templates/project/web/frontend/src/App.vue +0 -3
- package/templates/project/web/frontend/src/api/client.ts +0 -13
- package/templates/project/web/frontend/src/style.css +0 -1
- package/templates/project/web/frontend/src/views/HomeView.vue +0 -5
- package/templates/project/web/frontend/tsconfig.app.json +0 -14
- package/templates/project/web/frontend/tsconfig.json +0 -4
- /package/templates/{project/web/backend → claude-global/skills/dwy-pentest/scripts}/.python-version +0 -0
- /package/templates/project/{web/backend → backend}/Dockerfile +0 -0
- /package/templates/project/{web/backend → backend}/alembic/env.py.hbs +0 -0
- /package/templates/{claude-global/skills → project/backend/alembic/versions}/.gitkeep +0 -0
- /package/templates/project/{web → backend}/docker-compose.dev.yml.hbs +0 -0
- /package/templates/project/{web/backend → backend}/src/app/__init__.py +0 -0
- /package/templates/project/{web/backend → backend}/src/app/models/__init__.py +0 -0
- /package/templates/project/{web/backend → backend}/src/app/routers/__init__.py +0 -0
- /package/templates/project/{web/backend → backend}/src/app/routers/health.py +0 -0
- /package/templates/project/{web/backend → backend}/src/app/schemas/__init__.py +0 -0
- /package/templates/project/{web/backend → backend}/src/app/services/__init__.py +0 -0
- /package/templates/project/{web/backend → backend}/tests/__init__.py +0 -0
- /package/templates/project/{web/backend → backend}/tests/conftest.py.hbs +0 -0
- /package/templates/project/{web/backend/alembic/versions → mobile}/.gitkeep +0 -0
- /package/templates/project/web/{frontend → apps/web}/src/components/.gitkeep +0 -0
- /package/templates/project/web/{frontend → apps/web}/src/main.ts +0 -0
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
# Agent 并行模式速查表
|
|
2
|
-
|
|
3
|
-
常见任务的 Agent 并行分工模板。执行前对照此表选择合适的模式。
|
|
4
|
-
|
|
5
|
-
## 一、任务级并行模式
|
|
6
|
-
|
|
7
|
-
### 模式 1:批量创建(N 路并行)
|
|
8
|
-
|
|
9
|
-
**场景:** 创建多个独立的组件/页面/测试/demo
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
示例:"给 5 个组件写 demo 页面"
|
|
13
|
-
|
|
14
|
-
Agent-1 (worktree) → ComponentA demo
|
|
15
|
-
Agent-2 (worktree) → ComponentB demo
|
|
16
|
-
Agent-3 (worktree) → ComponentC demo
|
|
17
|
-
Agent-4 (worktree) → ComponentD demo
|
|
18
|
-
Agent-5 (worktree) → ComponentE demo
|
|
19
|
-
→ 全部完成后 code-reviewer 审查
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
**要点:**
|
|
23
|
-
- 每个 Agent 使用 `isolation: "worktree"`
|
|
24
|
-
- 每个 Agent 的 prompt 包含组件的 API 说明和 demo 规范
|
|
25
|
-
- 风险等级:低,直接执行
|
|
26
|
-
|
|
27
|
-
### 模式 2:研究 + 实现(2 阶段流水线)
|
|
28
|
-
|
|
29
|
-
**场景:** 需要先了解代码再动手修改
|
|
30
|
-
|
|
31
|
-
```
|
|
32
|
-
示例:"修复 EForm 组件的验证 bug"
|
|
33
|
-
|
|
34
|
-
阶段 1(并行研究):
|
|
35
|
-
Agent-Explore → 分析 EForm 组件代码结构和数据流
|
|
36
|
-
Agent-Explore → 搜索相关测试和使用示例
|
|
37
|
-
Agent-Explore → 查看 git 历史和相关 issue
|
|
38
|
-
|
|
39
|
-
阶段 2(串行实现):
|
|
40
|
-
基于研究结果,主线程或单 Agent 实现修复
|
|
41
|
-
|
|
42
|
-
阶段 3(并行验证):
|
|
43
|
-
Agent → 运行测试
|
|
44
|
-
Agent → code-review 变更
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
**要点:**
|
|
48
|
-
- 阶段 1 的 Agent 全部用 `subagent_type: "Explore"`
|
|
49
|
-
- 阶段 2 必须等阶段 1 全部完成
|
|
50
|
-
- 风险等级:中,阶段 2 前需确认方案
|
|
51
|
-
|
|
52
|
-
### 模式 3:多模块同步修改
|
|
53
|
-
|
|
54
|
-
**场景:** 一个功能涉及多个独立模块的修改
|
|
55
|
-
|
|
56
|
-
```
|
|
57
|
-
示例:"给所有 API 模块添加分页参数"
|
|
58
|
-
|
|
59
|
-
Agent-1 (worktree) → 修改 user API 模块
|
|
60
|
-
Agent-2 (worktree) → 修改 order API 模块
|
|
61
|
-
Agent-3 (worktree) → 修改 product API 模块
|
|
62
|
-
→ code-reviewer 审查一致性
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
**要点:**
|
|
66
|
-
- 每个 Agent 必须收到统一的修改规范(参数命名、类型定义)
|
|
67
|
-
- 使用 worktree 隔离
|
|
68
|
-
- 风险等级:中,执行前确认修改规范
|
|
69
|
-
|
|
70
|
-
### 模式 4:前后端同步开发
|
|
71
|
-
|
|
72
|
-
**场景:** 接口契约已定,前后端可以同时写
|
|
73
|
-
|
|
74
|
-
```
|
|
75
|
-
示例:"新增用户导出功能"
|
|
76
|
-
|
|
77
|
-
Agent-1 (worktree) → 后端 API + service 实现
|
|
78
|
-
Agent-2 (worktree) → 前端页面 + API 调用
|
|
79
|
-
→ code-reviewer 审查接口契约一致性
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
**要点:**
|
|
83
|
-
- 两个 Agent 必须收到相同的接口契约定义(URL、参数、响应格式)
|
|
84
|
-
- 风险等级:中,确认接口契约后执行
|
|
85
|
-
|
|
86
|
-
## 二、步骤级并行模式
|
|
87
|
-
|
|
88
|
-
### 模式 A:并行信息收集
|
|
89
|
-
|
|
90
|
-
在执行任何任务前,将独立的信息收集步骤并行化:
|
|
91
|
-
|
|
92
|
-
```
|
|
93
|
-
同一消息中发起:
|
|
94
|
-
Grep → 搜索相关代码引用
|
|
95
|
-
Glob → 查找相关文件
|
|
96
|
-
Read → 读取配置文件
|
|
97
|
-
Agent(Explore) → 分析依赖关系
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
### 模式 B:并行验证
|
|
101
|
-
|
|
102
|
-
修改完成后,将独立的验证步骤并行化:
|
|
103
|
-
|
|
104
|
-
```
|
|
105
|
-
同一消息中发起:
|
|
106
|
-
Bash → 运行单元测试
|
|
107
|
-
Bash → 运行 lint 检查
|
|
108
|
-
Bash → 运行类型检查
|
|
109
|
-
Bash → 运行构建
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### 模式 C:并行文档/测试补充
|
|
113
|
-
|
|
114
|
-
主要代码完成后,并行补充配套文件:
|
|
115
|
-
|
|
116
|
-
```
|
|
117
|
-
同一消息中发起:
|
|
118
|
-
Agent → 写单元测试
|
|
119
|
-
Agent → 更新相关文档
|
|
120
|
-
Agent → 更新 changelog
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
## 三、反模式(禁止使用)
|
|
124
|
-
|
|
125
|
-
| 反模式 | 问题 | 正确做法 |
|
|
126
|
-
|--------|------|---------|
|
|
127
|
-
| 多 Agent 改同一文件 | 冲突、覆盖 | 串行修改或拆分文件职责 |
|
|
128
|
-
| 无上下文的 Agent | 产出质量差 | prompt 必须自包含 |
|
|
129
|
-
| 链式依赖硬并行 | B 依赖 A 的输出却并行 | 识别依赖,串行执行 |
|
|
130
|
-
| 10+ Agent 同时派出 | 难以管理、context 爆炸 | 单批最多 5 个 Agent |
|
|
131
|
-
| 跳过 review 直接提交 | 质量无保障 | 遵循 review 闭环 |
|
|
132
|
-
|
|
133
|
-
## 四、Agent 数量指南
|
|
134
|
-
|
|
135
|
-
| 场景 | 建议 Agent 数 |
|
|
136
|
-
|------|-------------|
|
|
137
|
-
| 批量创建同质文件 | 每个文件 1 个,上限 5 个/批 |
|
|
138
|
-
| 并行研究 | 2-3 个 Explore Agent |
|
|
139
|
-
| 多模块修改 | 每个模块 1 个,上限 4 个/批 |
|
|
140
|
-
| 前后端同步 | 2 个 |
|
|
141
|
-
| 步骤级并行工具调用 | 不限(Grep/Glob/Read 等轻量调用尽量并行) |
|
|
142
|
-
|
|
143
|
-
超过 5 个 Agent 的任务,分批执行:先派第一批,完成后再派第二批。
|
|
144
|
-
|
|
145
|
-
## 五、Worktree 使用指南
|
|
146
|
-
|
|
147
|
-
以下场景**必须**使用 `isolation: "worktree"`:
|
|
148
|
-
|
|
149
|
-
- 多个 Agent 各自创建/修改不同文件
|
|
150
|
-
- Agent 的修改可能与当前工作区冲突
|
|
151
|
-
- 需要保持主工作区干净(如正在进行其他工作)
|
|
152
|
-
|
|
153
|
-
以下场景**不需要** worktree:
|
|
154
|
-
|
|
155
|
-
- 纯只读的 Explore Agent
|
|
156
|
-
- 步骤级并行的工具调用(Grep/Glob/Read/Bash)
|
|
157
|
-
- 单个 Agent 执行(无冲突风险)
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: git-security
|
|
3
|
-
description: Git 提交安全规则
|
|
4
|
-
type: claude-rule
|
|
5
|
-
tags: [security, git]
|
|
6
|
-
version: 1.0.0
|
|
7
|
-
author: chances
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# Git 提交安全规则
|
|
11
|
-
|
|
12
|
-
每次执行 `git add` 或 `git commit` 前必须遵循以下规则。违反时必须**立即停止并提示用户确认**。
|
|
13
|
-
|
|
14
|
-
## 一、禁止提交的文件
|
|
15
|
-
|
|
16
|
-
### 绝对禁止(无论任何理由)
|
|
17
|
-
以下文件**永远不得**被 `git add`:
|
|
18
|
-
|
|
19
|
-
| 文件类型 | 示例 |
|
|
20
|
-
|---|---|
|
|
21
|
-
| 环境变量文件 | `.env`、`.env.local`、`.env.production`、`*.env` |
|
|
22
|
-
| SSH 密钥 | `id_rsa`、`id_ed25519`、`*.pem`、`*.key` |
|
|
23
|
-
| 证书文件 | `*.p12`、`*.pfx`、`*.jks`、`*.keystore` |
|
|
24
|
-
| 数据库文件 | `*.sql`(含真实数据的)、`*.dump`、`*.sqlite` |
|
|
25
|
-
| 原始数据 | `*.xlsx`、`*.csv`(含用户/学生等敏感数据的) |
|
|
26
|
-
| 日志文件 | `*.log` |
|
|
27
|
-
| Docker 卷数据 | `pgdata/`、`cache/*.json` |
|
|
28
|
-
|
|
29
|
-
### 检测方法
|
|
30
|
-
在执行 `git add` 前,检查暂存区是否包含上述文件:
|
|
31
|
-
```bash
|
|
32
|
-
git diff --cached --name-only | grep -iE '\.(env|pem|key|p12|pfx|jks|dump|sqlite|log)$'
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## 二、禁止提交的内容
|
|
36
|
-
|
|
37
|
-
### 总则:所有示例必须使用假值
|
|
38
|
-
**禁止在代码、文档、规则文件、注释中出现任何真实的 API Key、Token、密码、IP 地址等敏感信息,即使是作为"示例"也不允许。** 所有示例必须使用明显的假值/脱敏值(如 `sk-xxxxxxxxxxxx`、`your-key-here`、`192.168.x.x`)。GitHub Secret Scanning 不区分上下文,真实格式的 key 即使写在文档示例中也会触发泄露告警。
|
|
39
|
-
|
|
40
|
-
### 必须扫描的敏感模式
|
|
41
|
-
对每个将要提交的文件内容,检查是否包含以下模式:
|
|
42
|
-
|
|
43
|
-
| 类型 | 匹配模式 | 示例(均为假值) |
|
|
44
|
-
|---|---|---|
|
|
45
|
-
| API Key | `sk-[a-zA-Z0-9]{20,}` | `sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxx` |
|
|
46
|
-
| AWS Key | `AKIA[0-9A-Z]{16}` | `AKIAIOSFODNN7EXAMPLE` |
|
|
47
|
-
| 密码赋值 | `password\s*[:=]\s*["'][^"']{4,}` | `password = "abc123"` |
|
|
48
|
-
| 数据库连接串 | `://\w+:[^@\s]+@` | `postgresql://user:pass@host` |
|
|
49
|
-
| 私钥内容 | `-----BEGIN (RSA |EC )?PRIVATE KEY-----` | PEM 私钥 |
|
|
50
|
-
| Token | `token\s*[:=]\s*["'][a-zA-Z0-9_\-]{20,}` | `token = "ghp_xxxx"` |
|
|
51
|
-
| Secret | `secret\s*[:=]\s*["'][^"']{8,}` | `secret = "my-secret"` |
|
|
52
|
-
| 服务器 IP+端口 | `\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+` | `192.168.1.1:8080` |
|
|
53
|
-
| SSH 连接 | `ssh\s+.*@\d{1,3}\.\d{1,3}` | `ssh user@192.168.1.1` |
|
|
54
|
-
|
|
55
|
-
### 例外:允许的模式
|
|
56
|
-
以下情况**不算**违规:
|
|
57
|
-
- `.env.example` 中的占位符(如 `DEEPSEEK_API_KEY=your-key-here`)
|
|
58
|
-
- 代码中从环境变量读取的引用(如 `os.getenv("API_KEY")`)
|
|
59
|
-
- `localhost` 或 `127.0.0.1` 的连接串
|
|
60
|
-
- 文档中的示例/假数据(如 `sk-xxxxxxxxxxxx`)
|
|
61
|
-
- 配置文件中的空字符串默认值(如 `api_key: str = ""`)
|
|
62
|
-
|
|
63
|
-
## 三、提交前检查流程
|
|
64
|
-
|
|
65
|
-
每次 `git commit` 前必须执行:
|
|
66
|
-
|
|
67
|
-
```
|
|
68
|
-
1. 检查暂存文件列表 → 是否包含禁止文件类型?
|
|
69
|
-
2. 检查文件内容差异 → 是否包含敏感模式?
|
|
70
|
-
3. 检查 .gitignore → 是否已排除敏感目录?
|
|
71
|
-
4. 通过 → 执行提交
|
|
72
|
-
5. 未通过 → 停止,提示用户具体违规项
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### 自动检查命令
|
|
76
|
-
```bash
|
|
77
|
-
# 检查暂存区的敏感文件
|
|
78
|
-
git diff --cached --name-only | grep -iE '\.(env|pem|key|log|dump|sqlite)$'
|
|
79
|
-
|
|
80
|
-
# 检查暂存区内容中的敏感信息
|
|
81
|
-
git diff --cached -U0 | grep -iE '(sk-[a-zA-Z0-9]{20}|AKIA[0-9A-Z]{16}|-----BEGIN.*PRIVATE KEY|password\s*[:=]\s*"[^"]{4,}"|://\w+:[^@\s]+@[^l])'
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
## 四、.gitignore 最低要求
|
|
85
|
-
|
|
86
|
-
每个项目的 `.gitignore` 必须至少包含:
|
|
87
|
-
|
|
88
|
-
```gitignore
|
|
89
|
-
# 环境变量
|
|
90
|
-
.env
|
|
91
|
-
*.env
|
|
92
|
-
.env.*
|
|
93
|
-
|
|
94
|
-
# 密钥
|
|
95
|
-
*.pem
|
|
96
|
-
*.key
|
|
97
|
-
*.p12
|
|
98
|
-
|
|
99
|
-
# 缓存与编译
|
|
100
|
-
__pycache__/
|
|
101
|
-
*.pyc
|
|
102
|
-
node_modules/
|
|
103
|
-
dist/
|
|
104
|
-
|
|
105
|
-
# 日志
|
|
106
|
-
*.log
|
|
107
|
-
|
|
108
|
-
# 数据库
|
|
109
|
-
*.sqlite
|
|
110
|
-
*.dump
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
如果项目的 `.gitignore` 缺少以上条目,提交前必须先补全。
|
|
114
|
-
|
|
115
|
-
## 五、已提交敏感数据的处理
|
|
116
|
-
|
|
117
|
-
如果发现敏感数据已存在于 git 历史中:
|
|
118
|
-
|
|
119
|
-
1. **立即通知用户**,说明哪些数据已泄露
|
|
120
|
-
2. **建议轮换凭证**(API Key、密码等)
|
|
121
|
-
3. **提供清理命令**(但不自动执行,等用户确认):
|
|
122
|
-
```bash
|
|
123
|
-
# 从所有历史中删除文件
|
|
124
|
-
git filter-repo --path <file> --invert-paths --force
|
|
125
|
-
|
|
126
|
-
# 或使用 BFG
|
|
127
|
-
bfg --delete-files <filename>
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
## 六、违规处理流程
|
|
131
|
-
|
|
132
|
-
当检测到提交操作包含敏感数据时:
|
|
133
|
-
|
|
134
|
-
1. **立即停止** `git add` 或 `git commit`
|
|
135
|
-
2. **列出具体违规项**:
|
|
136
|
-
- 文件名和行号
|
|
137
|
-
- 匹配到的敏感模式类型
|
|
138
|
-
- 敏感内容的部分摘要(如 `sk-5522****dc3`,脱敏显示)
|
|
139
|
-
3. **提供建议**:
|
|
140
|
-
- 将文件加入 `.gitignore`
|
|
141
|
-
- 将敏感值移到 `.env`
|
|
142
|
-
- 使用环境变量替代硬编码
|
|
143
|
-
4. **等待用户明确确认**后才能继续
|
|
144
|
-
5. 如用户确认提交,添加注释:`# GIT-SECURITY: 用户已确认提交此内容`
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: dwy-auto-sync
|
|
3
|
-
description: "create-dwy 发版后自动更新本地 CLI 并同步 Claude Code 配置。触发条件:执行了 npm publish create-dwy、pnpm publish:cli、或用户要求同步 dwy 配置时。"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# DWY 发版后自动同步
|
|
7
|
-
|
|
8
|
-
当 `create-dwy` 发布新版本后,必须执行以下步骤确保本地 Claude Code 配置与最新版本同步。
|
|
9
|
-
|
|
10
|
-
## 触发时机
|
|
11
|
-
|
|
12
|
-
以下任一情况发生时,执行同步流程:
|
|
13
|
-
|
|
14
|
-
1. 执行了 `npm publish`(在 claude-cli 目录下)
|
|
15
|
-
2. 执行了 `pnpm publish:cli`
|
|
16
|
-
3. 用户要求"同步 dwy"或"更新配置"
|
|
17
|
-
|
|
18
|
-
## 同步流程
|
|
19
|
-
|
|
20
|
-
发版成功后,按顺序执行:
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
# 1. 更新全局 CLI 到最新版
|
|
24
|
-
npm i -g create-dwy@latest
|
|
25
|
-
|
|
26
|
-
# 2. 验证版本
|
|
27
|
-
dwy --version
|
|
28
|
-
|
|
29
|
-
# 3. 同步 Claude Code 配置(skills/rules/hooks/commands/settings)
|
|
30
|
-
dwy sync claude
|
|
31
|
-
|
|
32
|
-
# 4. 验证同步结果
|
|
33
|
-
ls ~/.claude/skills/
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## 同步了什么
|
|
37
|
-
|
|
38
|
-
`dwy sync claude` 会将以下内容从 CLI 模板同步到 `~/.claude/`:
|
|
39
|
-
|
|
40
|
-
| 目录 | 内容 | 同步方式 |
|
|
41
|
-
|------|------|---------|
|
|
42
|
-
| `~/.claude/skills/` | AI 速查文档 (dwy-frontend-eui, dwy-frontend-core, dwy-backend-base) | 覆盖同名,保留已有 |
|
|
43
|
-
| `~/.claude/rules/` | 编码规范 (python-code-style, vue-code-style, 安全规则等) | 覆盖同名,保留已有 |
|
|
44
|
-
| `~/.claude/hooks/` | 自动化脚本 (pre-git-commit 检查) | 覆盖同名,保留已有 |
|
|
45
|
-
| `~/.claude/commands/` | 自定义命令 | 覆盖同名,保留已有 |
|
|
46
|
-
| `~/.claude/settings.json` | Claude Code 设置 | 浅合并(新 key 加入,同名覆盖,已有独有 key 保留) |
|
|
47
|
-
|
|
48
|
-
## 注意事项
|
|
49
|
-
|
|
50
|
-
- npm 注册表有传播延迟(~1 分钟),如果 `npm i -g create-dwy@latest` 安装的不是刚发布的版本,等待后重试
|
|
51
|
-
- `dwy sync claude` 从 Gitee 仓库拉取模板,确保代码已 push 到 Gitee 后再执行
|
|
52
|
-
- 同步不会删除用户手动添加的 skills/rules,只覆盖同名文件
|
|
@@ -1,279 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: dwy-backend-base
|
|
3
|
-
description: "danweiyuan-base FastAPI 后端基础设施速查。触发条件:使用 FastAPI 构建后端、配置数据库/Redis/JWT/异常处理时。"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# danweiyuan-base 后端基础设施速查
|
|
7
|
-
|
|
8
|
-
FastAPI 项目基础设施包,Python 3.11+,全异步。8 个扁平模块,无子包。
|
|
9
|
-
|
|
10
|
-
## 安装
|
|
11
|
-
|
|
12
|
-
```bash
|
|
13
|
-
pip install danweiyuan-base
|
|
14
|
-
# 或
|
|
15
|
-
uv add danweiyuan-base
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
```python
|
|
19
|
-
from danweiyuan_base.config import BaseSettings
|
|
20
|
-
from danweiyuan_base.database import Base, TimestampMixin, create_async_engine_factory, create_session_factory
|
|
21
|
-
from danweiyuan_base.security import hash_password, verify_password, create_token, decode_token
|
|
22
|
-
from danweiyuan_base.exceptions import NotFoundError, BusinessError, register_exception_handlers
|
|
23
|
-
from danweiyuan_base.response import success, fail, paginated
|
|
24
|
-
from danweiyuan_base.pagination import PaginationParams, paginate
|
|
25
|
-
from danweiyuan_base.cache import configure as configure_redis, get_redis, close_redis
|
|
26
|
-
from danweiyuan_base.dependencies import create_get_db
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## 查阅源码
|
|
30
|
-
|
|
31
|
-
每个模块在 `backend/src/danweiyuan_base/{module}.py`,单文件设计可直接阅读。
|
|
32
|
-
|
|
33
|
-
---
|
|
34
|
-
|
|
35
|
-
## config — Pydantic Settings
|
|
36
|
-
|
|
37
|
-
```python
|
|
38
|
-
from danweiyuan_base.config import BaseSettings
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
子类化后使用,从 `.env` 或环境变量读取配置。
|
|
42
|
-
|
|
43
|
-
```python
|
|
44
|
-
class Settings(BaseSettings):
|
|
45
|
-
app_name: str = "My API"
|
|
46
|
-
# 继承的必填字段:database_url, redis_url, secret_key
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
| 字段 | 类型 | 默认值 | 说明 |
|
|
50
|
-
|------|------|--------|------|
|
|
51
|
-
| database_url | str | **必填** | 异步数据库 URL |
|
|
52
|
-
| redis_url | str | **必填** | Redis URL |
|
|
53
|
-
| secret_key | str | **必填** | JWT 签名密钥 |
|
|
54
|
-
| jwt_algorithm | str | `"HS256"` | JWT 算法 |
|
|
55
|
-
| access_token_expire_minutes | int | `30` | Token 过期分钟 |
|
|
56
|
-
| debug | bool | `False` | 调试模式 |
|
|
57
|
-
| allowed_origins | list[str] | `[]` | CORS 允许域名 |
|
|
58
|
-
|
|
59
|
-
---
|
|
60
|
-
|
|
61
|
-
## database — 异步 SQLAlchemy
|
|
62
|
-
|
|
63
|
-
```python
|
|
64
|
-
from danweiyuan_base.database import Base, TimestampMixin, create_async_engine_factory, create_session_factory
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
### 模型定义
|
|
68
|
-
|
|
69
|
-
```python
|
|
70
|
-
from sqlalchemy.orm import Mapped, mapped_column
|
|
71
|
-
from sqlalchemy import String
|
|
72
|
-
|
|
73
|
-
class User(Base, TimestampMixin):
|
|
74
|
-
__tablename__ = "users"
|
|
75
|
-
id: Mapped[int] = mapped_column(primary_key=True)
|
|
76
|
-
name: Mapped[str] = mapped_column(String(50))
|
|
77
|
-
# TimestampMixin 自动添加 created_at, updated_at
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### 引擎和会话
|
|
81
|
-
|
|
82
|
-
```python
|
|
83
|
-
engine = create_async_engine_factory(settings.database_url, echo=settings.debug)
|
|
84
|
-
# 参数: database_url, pool_size=5, max_overflow=10, echo=False
|
|
85
|
-
# SQLite 自动跳过连接池参数
|
|
86
|
-
|
|
87
|
-
session_factory = create_session_factory(engine)
|
|
88
|
-
# 返回 async_sessionmaker[AsyncSession],expire_on_commit=False
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
---
|
|
92
|
-
|
|
93
|
-
## dependencies — FastAPI 依赖注入
|
|
94
|
-
|
|
95
|
-
```python
|
|
96
|
-
from danweiyuan_base.dependencies import create_get_db
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### create_get_db(session_factory)
|
|
100
|
-
|
|
101
|
-
生成 `get_db` FastAPI Depends 函数,自动管理会话生命周期。
|
|
102
|
-
|
|
103
|
-
```python
|
|
104
|
-
get_db = create_get_db(session_factory)
|
|
105
|
-
|
|
106
|
-
@router.get("/users/{user_id}")
|
|
107
|
-
async def get_user(user_id: int, db: AsyncSession = Depends(get_db)):
|
|
108
|
-
...
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
---
|
|
112
|
-
|
|
113
|
-
## security — JWT + bcrypt
|
|
114
|
-
|
|
115
|
-
```python
|
|
116
|
-
from danweiyuan_base.security import hash_password, verify_password, create_token, decode_token
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
所有函数无状态,密钥通过参数传入。
|
|
120
|
-
|
|
121
|
-
| 函数 | 签名 | 说明 |
|
|
122
|
-
|------|------|------|
|
|
123
|
-
| hash_password | `(password: str) -> str` | bcrypt 哈希 |
|
|
124
|
-
| verify_password | `(plain: str, hashed: str) -> bool` | 验证密码 |
|
|
125
|
-
| create_token | `(data: dict, secret: str, expires_minutes: int, algorithm="HS256") -> str` | 创建 JWT,自动添加 exp |
|
|
126
|
-
| decode_token | `(token: str, secret: str, algorithm="HS256") -> dict \| None` | 解码 JWT,失败返回 None |
|
|
127
|
-
|
|
128
|
-
```python
|
|
129
|
-
hashed = hash_password("my-password")
|
|
130
|
-
is_valid = verify_password("my-password", hashed)
|
|
131
|
-
|
|
132
|
-
token = create_token({"sub": str(user.id)}, settings.secret_key, settings.access_token_expire_minutes)
|
|
133
|
-
payload = decode_token(token, settings.secret_key) # {"sub": "1", "exp": ...} 或 None
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
---
|
|
137
|
-
|
|
138
|
-
## exceptions — 异常体系
|
|
139
|
-
|
|
140
|
-
```python
|
|
141
|
-
from danweiyuan_base.exceptions import (
|
|
142
|
-
AppError, NotFoundError, BusinessError, PermissionDeniedError, AuthenticationError,
|
|
143
|
-
register_exception_handlers,
|
|
144
|
-
)
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
### 异常层级
|
|
148
|
-
|
|
149
|
-
| 异常类 | HTTP 状态码 | code | message |
|
|
150
|
-
|--------|-----------|------|---------|
|
|
151
|
-
| AppError(message, code) | — | 自定义 | 自定义 |
|
|
152
|
-
| NotFoundError(resource) | 404 | `NOT_FOUND` | `{resource}不存在` |
|
|
153
|
-
| BusinessError(message, code) | 422 | 自定义 | 自定义 |
|
|
154
|
-
| PermissionDeniedError() | 403 | `PERMISSION_DENIED` | `权限不足` |
|
|
155
|
-
| AuthenticationError() | 401 | `AUTHENTICATION_FAILED` | `认证失败` |
|
|
156
|
-
|
|
157
|
-
### 注册到 FastAPI
|
|
158
|
-
|
|
159
|
-
```python
|
|
160
|
-
app = FastAPI()
|
|
161
|
-
register_exception_handlers(app)
|
|
162
|
-
# 之后在 service 层直接 raise NotFoundError("用户") 即可返回 404
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
响应格式统一为 `{"code": "ERROR_CODE", "message": "描述"}`。
|
|
166
|
-
|
|
167
|
-
### 使用原则
|
|
168
|
-
|
|
169
|
-
- **service 层**抛业务异常(NotFoundError, BusinessError 等)
|
|
170
|
-
- **router 层**不写 try/except,由 register_exception_handlers 统一处理
|
|
171
|
-
- **禁止**在 service 层抛 HTTPException
|
|
172
|
-
|
|
173
|
-
---
|
|
174
|
-
|
|
175
|
-
## response — 统一响应
|
|
176
|
-
|
|
177
|
-
```python
|
|
178
|
-
from danweiyuan_base.response import success, fail, paginated
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
| 函数 | 签名 | 返回结构 |
|
|
182
|
-
|------|------|---------|
|
|
183
|
-
| success | `(data=None, message="success") -> dict` | `{"code": 200, "message": ..., "data": ..., "timestamp": ...}` |
|
|
184
|
-
| fail | `(code=400, message="fail") -> dict` | `{"code": ..., "message": ..., "data": None, "timestamp": ...}` |
|
|
185
|
-
| paginated | `(items, total, page, page_size) -> dict` | success 包装 `{"items": [...], "total": N, "page": N, "page_size": N}` |
|
|
186
|
-
|
|
187
|
-
```python
|
|
188
|
-
@router.get("/users/{user_id}")
|
|
189
|
-
async def get_user(user_id: int, service=Depends(get_user_service)):
|
|
190
|
-
user = await service.get_by_id(user_id)
|
|
191
|
-
return success(data=UserResponse.model_validate(user))
|
|
192
|
-
|
|
193
|
-
@router.get("/users")
|
|
194
|
-
async def list_users(params: PaginationParams = Depends(), service=Depends(get_user_service)):
|
|
195
|
-
items, total = await service.list(params)
|
|
196
|
-
return paginated(items, total, params.page, params.page_size)
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
---
|
|
200
|
-
|
|
201
|
-
## pagination — 分页工具
|
|
202
|
-
|
|
203
|
-
```python
|
|
204
|
-
from danweiyuan_base.pagination import PaginationParams, paginate, OffsetLimit
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
### PaginationParams
|
|
208
|
-
|
|
209
|
-
FastAPI Depends 注入的查询参数模型。
|
|
210
|
-
|
|
211
|
-
| 字段 | 类型 | 默认 | 约束 |
|
|
212
|
-
|------|------|------|------|
|
|
213
|
-
| page | int | 1 | >= 1 |
|
|
214
|
-
| page_size | int | 20 | 1 ~ 100 |
|
|
215
|
-
|
|
216
|
-
### paginate(page, page_size) -> OffsetLimit
|
|
217
|
-
|
|
218
|
-
```python
|
|
219
|
-
offset_limit = paginate(params.page, params.page_size)
|
|
220
|
-
# OffsetLimit(offset=0, limit=20) — 可直接用于 SQLAlchemy .offset().limit()
|
|
221
|
-
stmt = select(User).offset(offset_limit.offset).limit(offset_limit.limit)
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
---
|
|
225
|
-
|
|
226
|
-
## cache — 异步 Redis
|
|
227
|
-
|
|
228
|
-
```python
|
|
229
|
-
from danweiyuan_base import cache
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
| 函数 | 签名 | 说明 |
|
|
233
|
-
|------|------|------|
|
|
234
|
-
| cache.configure | `(redis_url: str) -> None` | 设置 Redis URL(启动时调用一次) |
|
|
235
|
-
| cache.get_redis | `async () -> Redis` | 获取/创建共享连接(未 configure 时抛 RuntimeError) |
|
|
236
|
-
| cache.close_redis | `async () -> None` | 关闭连接(安全幂等) |
|
|
237
|
-
|
|
238
|
-
```python
|
|
239
|
-
# lifespan 中初始化
|
|
240
|
-
@asynccontextmanager
|
|
241
|
-
async def lifespan(app: FastAPI):
|
|
242
|
-
cache.configure(settings.redis_url)
|
|
243
|
-
yield
|
|
244
|
-
await cache.close_redis()
|
|
245
|
-
|
|
246
|
-
# 业务中使用
|
|
247
|
-
redis = await cache.get_redis()
|
|
248
|
-
await redis.set("key", "value", ex=3600)
|
|
249
|
-
val = await redis.get("key")
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
---
|
|
253
|
-
|
|
254
|
-
## 典型项目接入流程
|
|
255
|
-
|
|
256
|
-
```python
|
|
257
|
-
# config.py
|
|
258
|
-
from danweiyuan_base.config import BaseSettings
|
|
259
|
-
|
|
260
|
-
class Settings(BaseSettings):
|
|
261
|
-
app_name: str = "My API"
|
|
262
|
-
|
|
263
|
-
settings = Settings()
|
|
264
|
-
|
|
265
|
-
# database.py
|
|
266
|
-
from danweiyuan_base.database import Base, create_async_engine_factory, create_session_factory
|
|
267
|
-
from danweiyuan_base.dependencies import create_get_db
|
|
268
|
-
|
|
269
|
-
engine = create_async_engine_factory(settings.database_url)
|
|
270
|
-
session_factory = create_session_factory(engine)
|
|
271
|
-
get_db = create_get_db(session_factory)
|
|
272
|
-
|
|
273
|
-
# main.py
|
|
274
|
-
from danweiyuan_base.exceptions import register_exception_handlers
|
|
275
|
-
from danweiyuan_base import cache
|
|
276
|
-
|
|
277
|
-
app = FastAPI(lifespan=lifespan)
|
|
278
|
-
register_exception_handlers(app)
|
|
279
|
-
```
|