From d7cafbe5825c8b999732740b674f022629ec2215 Mon Sep 17 00:00:00 2001 From: xuxiang Date: Mon, 2 Feb 2026 18:57:56 +0800 Subject: [PATCH] chore: sync with upstream e7cb442 + update zh translations --- .claude-plugin/plugin.json | 1 + .claude/skills/oneskill/.gitignore | 90 + .claude/skills/oneskill/.openskills.json | 7 + .claude/skills/oneskill/LICENSE | 176 ++ .claude/skills/oneskill/README.md | 88 + .claude/skills/oneskill/README_CN.md | 92 + .claude/skills/oneskill/SKILL.md | 59 + .claude/skills/oneskill/package-lock.json | 2026 +++++++++++++++++ .claude/skills/oneskill/package.json | 45 + .claude/skills/oneskill/src/cli.ts | 82 + .../skills/oneskill/src/commands/doctor.ts | 19 + .claude/skills/oneskill/src/commands/info.ts | 11 + .claude/skills/oneskill/src/commands/list.ts | 16 + .claude/skills/oneskill/src/commands/map.ts | 23 + .../skills/oneskill/src/commands/search.ts | 32 + .claude/skills/oneskill/src/commands/sync.ts | 25 + .claude/skills/oneskill/src/core/fs.ts | 24 + .claude/skills/oneskill/src/core/lock.ts | 23 + .claude/skills/oneskill/src/core/log.ts | 11 + .claude/skills/oneskill/src/core/manifest.ts | 33 + .claude/skills/oneskill/src/core/map.ts | 49 + .claude/skills/oneskill/src/core/mapping.ts | 85 + .../skills/oneskill/src/core/openskills.ts | 42 + .claude/skills/oneskill/src/core/registry.ts | 110 + .claude/skills/oneskill/src/core/root.ts | 38 + .claude/skills/oneskill/src/core/types.ts | 81 + .claude/skills/oneskill/src/core/versions.ts | 29 + .claude/skills/oneskill/src/utils/json.ts | 3 + .claude/skills/oneskill/tsconfig.json | 14 + .claude/skills/oneskill/tsup.config.ts | 10 + .gitignore | 2 + README.md | 341 +-- README.zh-CN.md | 83 +- agents/build-error-resolver.md | 12 +- agents/python-reviewer.md | 469 ++++ agents/tdd-guide.md | 2 +- commands/evolve.md | 172 +- commands/go-build.md | 10 +- commands/instinct-export.md | 76 +- commands/instinct-import.md | 146 +- commands/instinct-status.md | 80 +- commands/python-review.md | 297 +++ commands/skill-create.md | 166 +- commands/tdd.md | 24 +- docs/zh-TW/README.md | 430 ++-- package-lock.json | 5 +- .../continuous-learning-v2/commands/evolve.md | 186 -- .../commands/instinct-export.md | 91 - .../commands/instinct-import.md | 135 -- .../commands/instinct-status.md | 79 - skills/django-patterns/SKILL.md | 734 ++++++ skills/django-security/SKILL.md | 592 +++++ skills/django-tdd/SKILL.md | 728 ++++++ skills/django-verification/SKILL.md | 460 ++++ skills/golang-patterns/SKILL.md | 2 +- skills/java-coding-standards/SKILL.md | 100 +- skills/jpa-patterns/SKILL.md | 68 +- skills/python-patterns/SKILL.md | 751 ++++++ skills/python-testing/SKILL.md | 815 +++++++ skills/springboot-patterns/SKILL.md | 108 +- skills/springboot-security/SKILL.md | 104 +- skills/springboot-tdd/SKILL.md | 66 +- skills/springboot-verification/SKILL.md | 82 +- translation_workdir/cache/translation_db.json | 92 +- .../scripts/batch_processor.py | 8 +- translation_workdir/scripts/sync_upstream.sh | 0 66 files changed, 9395 insertions(+), 1465 deletions(-) create mode 100644 .claude/skills/oneskill/.gitignore create mode 100644 .claude/skills/oneskill/.openskills.json create mode 100644 .claude/skills/oneskill/LICENSE create mode 100644 .claude/skills/oneskill/README.md create mode 100644 .claude/skills/oneskill/README_CN.md create mode 100644 .claude/skills/oneskill/SKILL.md create mode 100644 .claude/skills/oneskill/package-lock.json create mode 100644 .claude/skills/oneskill/package.json create mode 100644 .claude/skills/oneskill/src/cli.ts create mode 100644 .claude/skills/oneskill/src/commands/doctor.ts create mode 100644 .claude/skills/oneskill/src/commands/info.ts create mode 100644 .claude/skills/oneskill/src/commands/list.ts create mode 100644 .claude/skills/oneskill/src/commands/map.ts create mode 100644 .claude/skills/oneskill/src/commands/search.ts create mode 100644 .claude/skills/oneskill/src/commands/sync.ts create mode 100644 .claude/skills/oneskill/src/core/fs.ts create mode 100644 .claude/skills/oneskill/src/core/lock.ts create mode 100644 .claude/skills/oneskill/src/core/log.ts create mode 100644 .claude/skills/oneskill/src/core/manifest.ts create mode 100644 .claude/skills/oneskill/src/core/map.ts create mode 100644 .claude/skills/oneskill/src/core/mapping.ts create mode 100644 .claude/skills/oneskill/src/core/openskills.ts create mode 100644 .claude/skills/oneskill/src/core/registry.ts create mode 100644 .claude/skills/oneskill/src/core/root.ts create mode 100644 .claude/skills/oneskill/src/core/types.ts create mode 100644 .claude/skills/oneskill/src/core/versions.ts create mode 100644 .claude/skills/oneskill/src/utils/json.ts create mode 100644 .claude/skills/oneskill/tsconfig.json create mode 100644 .claude/skills/oneskill/tsup.config.ts create mode 100644 agents/python-reviewer.md create mode 100644 commands/python-review.md delete mode 100644 skills/continuous-learning-v2/commands/evolve.md delete mode 100644 skills/continuous-learning-v2/commands/instinct-export.md delete mode 100644 skills/continuous-learning-v2/commands/instinct-import.md delete mode 100644 skills/continuous-learning-v2/commands/instinct-status.md create mode 100644 skills/django-patterns/SKILL.md create mode 100644 skills/django-security/SKILL.md create mode 100644 skills/django-tdd/SKILL.md create mode 100644 skills/django-verification/SKILL.md create mode 100644 skills/python-patterns/SKILL.md create mode 100644 skills/python-testing/SKILL.md mode change 100644 => 100755 translation_workdir/scripts/sync_upstream.sh diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index a90f4e5..8826781 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -33,6 +33,7 @@ "./agents/go-build-resolver.md", "./agents/go-reviewer.md", "./agents/planner.md", + "./agents/python-reviewer.md", "./agents/refactor-cleaner.md", "./agents/security-reviewer.md", "./agents/tdd-guide.md" diff --git a/.claude/skills/oneskill/.gitignore b/.claude/skills/oneskill/.gitignore new file mode 100644 index 0000000..2ec0e8a --- /dev/null +++ b/.claude/skills/oneskill/.gitignore @@ -0,0 +1,90 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.test +.env.production + +# parcel-bundler cache +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line if your project uses Gatsby and you want to debug the +# build, as it contains some public assets. These assets are usually +# generated from other files and don't need to be in version control. +# public + +# vue-cli build runner target +target/ + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# Tern device bin file +.tern-port + +# Stores VS Code state +.vscode + +# Build output +dist/ + +# OS metadata +.DS_Store +Thumbs.db diff --git a/.claude/skills/oneskill/.openskills.json b/.claude/skills/oneskill/.openskills.json new file mode 100644 index 0000000..2bab6ae --- /dev/null +++ b/.claude/skills/oneskill/.openskills.json @@ -0,0 +1,7 @@ +{ + "source": "xu-xiang/oneskill", + "sourceType": "git", + "repoUrl": "https://github.com/xu-xiang/oneskill", + "subpath": "", + "installedAt": "2026-01-31T16:23:32.899Z" +} \ No newline at end of file diff --git a/.claude/skills/oneskill/LICENSE b/.claude/skills/oneskill/LICENSE new file mode 100644 index 0000000..efe8c07 --- /dev/null +++ b/.claude/skills/oneskill/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of Your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/.claude/skills/oneskill/README.md b/.claude/skills/oneskill/README.md new file mode 100644 index 0000000..5499615 --- /dev/null +++ b/.claude/skills/oneskill/README.md @@ -0,0 +1,88 @@ +
+ +# OneSkill 元管理器(Meta-Manager) + +**AI 智能体技能(Agent Skills)的通用桥梁。** +从 OpenSkills 注册表中发现、安装并映射功能到您的环境。 + +[![](https://img.shields.io/npm/v/oneskill?color=brightgreen)](https://www.npmjs.com/package/oneskill) +[![](https://img.shields.io/npm/l/oneskill)](LICENSE) + +[**🇺🇸 English**](README.md) | [**🇨🇳 中文指南**](README_CN.md) + +
+ +--- + +## ⚡️ 什么是 OneSkill? + +**OneSkill** 是一款专为 AI 智能体(Agent)(以及人类)设计的元工具,用于轻松扩展其功能。它是 [OpenSkills](https://github.com/Starttoaster/openskills) 生态系统的搜索引擎和工作流管理器(Workflow Manager)。 + +虽然 `openskills` 处理文件的原始安装,但 **OneSkill** 提供: +1. **智能搜索(Intelligent Search)**:使用自然语言或关键词找到适合该任务的工具。 +2. **工作流指南(Workflow Guidance)**:为智能体(Agent)安全获取新技能提供标准化流程。 +3. **环境映射(Environment Mapping)**:至关重要的一点是,它弥合了 `openskills`(标准结构)与 **Gemini CLI**(自定义结构)等使用者之间的鸿沟。 + +## 🚀 快速开始 + +您无需永久安装。只需使用 `npx` 运行即可。 + +```bash +# 搜索技能(例如,用于浏览网页) +npx oneskill search "puppeteer browser" + +# 搜索按流行度排序的数据库工具 +npx oneskill search "database" --sort stars +``` + +## 🛠 工作流 + +为您的智能体(Agent)添加新功能的标准生命周期: + +1. **搜索(Search)**:查找技能。 + ```bash + npx oneskill search "github integration" + ``` +2. **安装(Install)**:使用标准的 `openskills` 安装程序。 + ```bash + npx openskills install anthropics/skills + ``` +3. **映射(Map)(对 Gemini 至关重要)**:如果您正在使用 **Gemini CLI**,则必须将安装的技能映射到您的配置中。 + ```bash + # 将安装的技能映射到 Gemini 的配置 + npx oneskill map --target gemini + ``` + +## 📖 命令参考 + +### `search` +在全局注册表中搜索技能。 +```bash +npx oneskill search [options] + +# 选项: +# --category 按类别过滤 +# --sort 按 'stars'、'created' 或 'updated' 排序 +# --limit 限制结果数量(默认值:10) +``` + +### `map` +为特定的智能体(Agent)环境生成配置。 +```bash +npx oneskill map --target + +# 目标: +# gemini 生成/更新 Gemini CLI 配置 +``` + +### `list` +列出本地映射的技能(`openskills list` 的封装)。 +```bash +npx oneskill list +``` + +--- + +
+ 由 OneSkill 社区用 ❤️ 构建 +
diff --git a/.claude/skills/oneskill/README_CN.md b/.claude/skills/oneskill/README_CN.md new file mode 100644 index 0000000..9901da5 --- /dev/null +++ b/.claude/skills/oneskill/README_CN.md @@ -0,0 +1,92 @@ +
+ +# OneSkill 元管理器 (Meta-Manager) + +**AI 智能体 (Agent) 技能的通用桥梁** +帮助你发现、安装并将 OpenSkills 注册表中的能力映射到你的运行环境。 + +[![](https://img.shields.io/npm/v/oneskill?color=brightgreen)](https://www.npmjs.com/package/oneskill) +[![](https://img.shields.io/npm/l/oneskill)](LICENSE) + +[**🇺🇸 English**](README.md) | [**🇨🇳 中文指南**](README_CN.md) + +
+ +--- + +## ⚡️ 什么是 OneSkill? + +**OneSkill** 是一个为 AI 智能体 (Agent) 设计的通用技能管理工具。它作为 [OpenSkills](https://github.com/Starttoaster/openskills) 生态系统的搜索引擎和工作流管理器 (Workflow Manager),帮助你发现、安装并将能力映射到你的运行环境中。 + +虽然 `openskills` 负责文件的下载安装,但 **OneSkill** 提供了: +1. **智能搜索**: 支持通过自然语言或关键词搜索注册表中的技能 (Skill)。 +2. **工作流引导**: 为智能体 (Agent) 提供了一套标准的扩展能力流程(搜索 -> 确认 -> 安装)。 +3. **环境映射 (Mapping)**: 解决了安装路径与运行环境不一致的问题。特别是对于 **Gemini CLI** 用户,OneSkill 能自动将下载的技能 (Skill) 映射到 Gemini 的配置文件中。 + +## 🚀 快速开始 + +无需全局安装,直接使用 `npx` 运行即可: + +```bash +# 搜索技能 (例如:想要网页浏览能力) +npx oneskill search "puppeteer browser" + +# 搜索数据库相关技能,并按星级排序 +npx oneskill search "database" --sort stars +``` + +## 🛠 使用流程 + +为你的智能体 (Agent) 添加新能力的推荐步骤: + +1. **搜索 (Search)**: 查找你需要的技能 (Skill)。 + ```bash + npx oneskill search "github integration" + ``` + +2. **安装 (Install)**: 使用 `openskills` 标准命令进行下载。 + ```bash + npx openskills install anthropics/skills + ``` + +3. **映射 (Map)**: **(Gemini 用户必读)** + `openskills` 默认将文件下载到通用目录,Gemini CLI 无法直接读取。必须执行映射 (Mapping) 命令: + ```bash + # 自动识别已安装的技能 (Skill) 并配置到 Gemini + npx oneskill map --target gemini + ``` + *如果你的技能 (Skill) 是全局安装的 (加了 --global),这里也需要加 --global。* + +## 📖 命令参考 + +### `search` (搜索) +在全局注册表中搜索技能 (Skill)。 +```bash +npx oneskill search <查询词> [选项] + +# 选项: +# --category 按分类筛选 +# --sort 排序方式: 'stars' (星级), 'created' (创建时间), 'updated' (更新时间) +# --limit 限制返回数量 (默认: 10) +``` + +### `map` (映射) +为特定环境生成配置。 +```bash +npx oneskill map --target <环境> + +# 支持的目标: +# gemini 更新 Gemini CLI 的配置与路径映射 +``` + +### `list` (列表) +查看本地已安装的技能 (Skill)。 +```bash +npx oneskill list +``` + +--- + +
+ Built with ❤️ by the OneSkill Community +
diff --git a/.claude/skills/oneskill/SKILL.md b/.claude/skills/oneskill/SKILL.md new file mode 100644 index 0000000..667135e --- /dev/null +++ b/.claude/skills/oneskill/SKILL.md @@ -0,0 +1,59 @@ +--- +name: oneskill +description: 发现技能 (Skill),迭代查询,并在任何环境中自动安装技能。 +--- + +# OneSkill 元管理器 (Meta-Manager) + +使用此技能来发现新功能、优化搜索查询,并使用 OpenSkills 简化技能设置。这为扩展环境功能提供了一种统一的方式。 + +## 何时使用 + +- 当用户要求的某些功能你目前不具备时。 +- 当任务复杂、属于特定领域,或在尝试 2 次后仍被反复阻断时。 +- 当可能存在更好的技能(Skill)时(例如:网页浏览、GitHub 集成、数据库管理、云基础设施)。 + +## 工作流 (Workflow) + +1. 搜索注册表: + - 运行:`npx oneskill search "" [options]` + - 支持的选项:`--category`、`--limit`、`--offset`、`--sort`。 + - 示例: + - `npx oneskill search "browser" --sort stars` + - `npx oneskill search "" --category database --limit 5` +2. 分析结果: + - 确定最佳匹配项,或优化查询并再次搜索。 +3. 与用户确认: + - 说明该技能的功能及其来源。 +4. 在获得明确批准后进行安装(使用 openskills): + - 运行:`npx openskills install ` + - 示例:`npx openskills install anthropics/skills` +5. 处理特定环境的设置: + - **Gemini CLI 用户:** `openskills` 不会自动配置 Gemini。安装后你**必须**运行映射命令: + - `npx oneskill map --target gemini`(如果是全局安装,请添加 `--global`) +6. 应用新技能以完成原始请求。 + +## OpenSkills 基础 + +- `npx openskills install [options]` # 从 GitHub、本地路径或私有仓库安装 +- `npx openskills sync [-y] [-o ]` # 更新 AGENTS.md (或自定义输出) +- `npx openskills list` # 显示已安装的技能 +- `npx openskills read ` # 加载技能(供智能体 (Agent) 使用) +- `npx openskills update [name...]` # 更新已安装的技能(默认:全部) +- `npx openskills manage` # 移除技能(交互式) +- `npx openskills remove ` # 移除特定技能 + +示例: +- `npx openskills install anthropics/skills` +- `npx openskills sync` + +默认设置:安装在项目本地(`./.claude/skills`,或者带 `--universal` 参数安装在 `./.agent/skills`)。使用 `--global` 安装在 `~/.claude/skills`。 + +## 安全提示 (Safety Reminders) + +- 未经用户明确确认,请勿安装。 +- 除非用户同意覆盖现有目标,否则避免使用 `--force-map`。 +- 使用 openskills 进行安装/更新;OneSkill 仅为 Gemini 提供搜索和映射。 +- 对于 Gemini,请在安装后运行 `npx oneskill map --target gemini`。 +- 默认安装/映射是项目本地的,与 openskills 相同;全局安装请使用 `--global`。 +- 安装 OneSkill 本身时,建议使用 `--global`,以便在跨项目时可用。 diff --git a/.claude/skills/oneskill/package-lock.json b/.claude/skills/oneskill/package-lock.json new file mode 100644 index 0000000..7bdde6b --- /dev/null +++ b/.claude/skills/oneskill/package-lock.json @@ -0,0 +1,2026 @@ +{ + "name": "oneskill", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "oneskill", + "version": "0.1.0", + "license": "Apache-2.0", + "dependencies": { + "chalk": "^5.6.2", + "commander": "^12.1.0", + "openskills": "file:../openskills" + }, + "bin": { + "oneskill": "dist/cli.js" + }, + "devDependencies": { + "@types/node": "^24.9.1", + "tsup": "^8.5.0", + "typescript": "^5.9.3", + "vitest": "^4.0.3" + }, + "engines": { + "node": ">=20.6.0" + } + }, + "../openskills": { + "version": "1.5.0", + "license": "Apache-2.0", + "dependencies": { + "@inquirer/prompts": "^7.9.0", + "chalk": "^5.6.2", + "commander": "^12.1.0", + "ora": "^9.0.0" + }, + "bin": { + "openskills": "dist/cli.js" + }, + "devDependencies": { + "@types/node": "^24.9.1", + "tsup": "^8.5.0", + "typescript": "^5.9.3", + "vitest": "^4.0.3" + }, + "engines": { + "node": ">=20.6.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.10.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.9.tgz", + "integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@vitest/expect": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", + "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", + "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.18", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", + "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.18", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", + "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/bundle-require": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", + "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.18" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fix-dts-default-cjs-exports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", + "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "rollup": "^4.34.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/openskills": { + "resolved": "../openskills", + "link": true + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tsup": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", + "integrity": "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-require": "^5.1.0", + "cac": "^6.7.14", + "chokidar": "^4.0.3", + "consola": "^3.4.0", + "debug": "^4.4.0", + "esbuild": "^0.27.0", + "fix-dts-default-cjs-exports": "^1.0.0", + "joycon": "^3.1.1", + "picocolors": "^1.1.1", + "postcss-load-config": "^6.0.1", + "resolve-from": "^5.0.0", + "rollup": "^4.34.8", + "source-map": "^0.7.6", + "sucrase": "^3.35.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.11", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@microsoft/api-extractor": "^7.36.0", + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.5.0" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", + "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.18", + "@vitest/browser-preview": "4.0.18", + "@vitest/browser-webdriverio": "4.0.18", + "@vitest/ui": "4.0.18", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/.claude/skills/oneskill/package.json b/.claude/skills/oneskill/package.json new file mode 100644 index 0000000..7b32ec7 --- /dev/null +++ b/.claude/skills/oneskill/package.json @@ -0,0 +1,45 @@ +{ + "name": "oneskill", + "version": "0.1.0", + "description": "Meta-skill manager for AI coding agents", + "type": "module", + "main": "./dist/cli.js", + "bin": { + "oneskill": "dist/cli.js" + }, + "files": [ + "dist", + "README.md", + "SKILL.md" + ], + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "typecheck": "tsc --noEmit", + "test": "vitest run" + }, + "keywords": [ + "skills", + "ai", + "agents", + "meta-skill", + "codex", + "gemini", + "claude" + ], + "author": "OneSkill Contributors", + "license": "Apache-2.0", + "engines": { + "node": ">=20.6.0" + }, + "dependencies": { + "chalk": "^5.6.2", + "commander": "^12.1.0" + }, + "devDependencies": { + "@types/node": "^24.9.1", + "tsup": "^8.5.0", + "typescript": "^5.9.3", + "vitest": "^4.0.3" + } +} diff --git a/.claude/skills/oneskill/src/cli.ts b/.claude/skills/oneskill/src/cli.ts new file mode 100644 index 0000000..a1fd8d9 --- /dev/null +++ b/.claude/skills/oneskill/src/cli.ts @@ -0,0 +1,82 @@ +#!/usr/bin/env node +import { Command } from 'commander'; +import { runSearch } from './commands/search.js'; +import { runInfo } from './commands/info.js'; +import { runList } from './commands/list.js'; +import { runDoctor } from './commands/doctor.js'; +import { runSync } from './commands/sync.js'; +import { runMap } from './commands/map.js'; +import { getOneskillVersion } from './core/versions.js'; + +const program = new Command(); + +program + .name('oneskill') + .description('Meta-skill manager for AI coding agents') + .version(getOneskillVersion()); + +program + .command('search ') + .description('Search the skill registry') + .option('--registry ', 'Registry base URL override') + .option('--category ', 'Filter by category slug') + .option('--limit ', 'Results per page (max 100)', (value) => Number.parseInt(value, 10)) + .option('--offset ', 'Pagination offset', (value) => Number.parseInt(value, 10)) + .option('--sort ', 'Sort by: votes, recent, stars') + .action(async (query: string, options: { registry?: string; category?: string; limit?: number; offset?: number; sort?: string }) => { + await runSearch(query, options); + }); + +program + .command('info ') + .description('Fetch skill info from registry') + .option('--registry ', 'Registry base URL override') + .action(async (slug: string, options: { registry?: string }) => { + await runInfo(slug, options); + }); + +program + .command('sync') + .description('Forward to openskills sync') + .option('-y, --yes', 'Skip interactive selection, sync all skills') + .option('-o, --output ', 'Output file path (default: AGENTS.md)') + .action(async (options: { yes?: boolean; output?: string }) => { + await runSync(options); + }); + +program + .command('map') + .description('Map installed skills into Gemini directory') + .option('--target ', 'Target environment (gemini only)') + .option('--global', 'Map from global openskills install', false) + .option('--universal', 'Map from universal (.agent/skills)', false) + .option('--force-map', 'Overwrite target mapping if it exists', false) + .action(async (options: { target?: string; global?: boolean; universal?: boolean; forceMap?: boolean }) => { + await runMap({ + target: options.target as 'gemini' | undefined, + global: options.global, + universal: options.universal, + forceMap: options.forceMap, + }); + }); + +program + .command('list') + .description('List managed skills') + .option('--root ', 'Override workspace root') + .action(async (options: { root?: string }) => { + await runList(options); + }); + +program + .command('doctor') + .description('Diagnose OneSkill environment') + .option('--root ', 'Override workspace root') + .action(async (options: { root?: string }) => { + await runDoctor(options); + }); + +program.parseAsync(process.argv).catch((error) => { + console.error(error instanceof Error ? error.message : String(error)); + process.exitCode = 1; +}); diff --git a/.claude/skills/oneskill/src/commands/doctor.ts b/.claude/skills/oneskill/src/commands/doctor.ts new file mode 100644 index 0000000..88880d3 --- /dev/null +++ b/.claude/skills/oneskill/src/commands/doctor.ts @@ -0,0 +1,19 @@ +import { detectRoot } from '../core/root.js'; +import { printJson } from '../utils/json.js'; + +export interface DoctorCommandOptions { + root?: string; +} + +export async function runDoctor(options: DoctorCommandOptions): Promise { + const rootInfo = detectRoot(options.root || process.cwd()); + const root = rootInfo.root; + const paths = { + agent: `${root}/.agent/skills`, + claude: `${root}/.claude/skills`, + gemini: `${root}/.gemini/skills`, + codex: `${root}/.codex/skills`, + oneskillLogs: `${root}/.oneskill/logs`, + }; + printJson({ schemaVersion: '1', root: rootInfo.root, reason: rootInfo.reason, paths }); +} diff --git a/.claude/skills/oneskill/src/commands/info.ts b/.claude/skills/oneskill/src/commands/info.ts new file mode 100644 index 0000000..a7081c3 --- /dev/null +++ b/.claude/skills/oneskill/src/commands/info.ts @@ -0,0 +1,11 @@ +import { fetchRegistryInfo } from '../core/registry.js'; +import { printJson } from '../utils/json.js'; + +export interface InfoCommandOptions { + registry?: string; +} + +export async function runInfo(slug: string, options: InfoCommandOptions): Promise { + const result = await fetchRegistryInfo(slug, options.registry); + printJson({ schemaVersion: '1', item: result.item }); +} diff --git a/.claude/skills/oneskill/src/commands/list.ts b/.claude/skills/oneskill/src/commands/list.ts new file mode 100644 index 0000000..6455186 --- /dev/null +++ b/.claude/skills/oneskill/src/commands/list.ts @@ -0,0 +1,16 @@ +import { spawnSync } from 'child_process'; +import { resolveOpenskillsCli } from '../core/openskills.js'; + +export interface ListCommandOptions {} + +export async function runList(_options: ListCommandOptions): Promise { + const cliPath = resolveOpenskillsCli(); + const args = [cliPath, 'list']; + const result = spawnSync(process.execPath, args, { + cwd: process.cwd(), + stdio: 'inherit', + }); + if (result.status !== 0) { + throw new Error('openskills list failed'); + } +} diff --git a/.claude/skills/oneskill/src/commands/map.ts b/.claude/skills/oneskill/src/commands/map.ts new file mode 100644 index 0000000..5a5d702 --- /dev/null +++ b/.claude/skills/oneskill/src/commands/map.ts @@ -0,0 +1,23 @@ +import type { TargetEnvironment } from '../core/types.js'; +import { mapInstalledSkills } from '../core/map.js'; +import { printJson } from '../utils/json.js'; + +export interface MapCommandOptions { + target?: TargetEnvironment; + global?: boolean; + universal?: boolean; + forceMap?: boolean; +} + +export async function runMap(options: MapCommandOptions): Promise { + if (!options.target) { + throw new Error('map requires --target gemini'); + } + const result = mapInstalledSkills({ + target: options.target, + global: options.global, + universal: options.universal, + forceMap: options.forceMap, + }); + printJson({ schemaVersion: '1', ...result }); +} diff --git a/.claude/skills/oneskill/src/commands/search.ts b/.claude/skills/oneskill/src/commands/search.ts new file mode 100644 index 0000000..62be659 --- /dev/null +++ b/.claude/skills/oneskill/src/commands/search.ts @@ -0,0 +1,32 @@ +import { searchRegistry } from '../core/registry.js'; +import { printJson } from '../utils/json.js'; + +export interface SearchCommandOptions { + registry?: string; + category?: string; + limit?: number; + offset?: number; + sort?: string; +} + +export async function runSearch(query: string, options: SearchCommandOptions): Promise { + const result = await searchRegistry( + { + q: query, + category: options.category, + limit: options.limit, + offset: options.offset, + sort: options.sort, + }, + options.registry + ); + const raw = result.raw as { registry?: unknown; version?: unknown; pagination?: unknown } | undefined; + printJson({ + schemaVersion: '1', + query, + registry: raw?.registry, + version: raw?.version, + pagination: raw?.pagination, + items: result.items, + }); +} diff --git a/.claude/skills/oneskill/src/commands/sync.ts b/.claude/skills/oneskill/src/commands/sync.ts new file mode 100644 index 0000000..fc04ae8 --- /dev/null +++ b/.claude/skills/oneskill/src/commands/sync.ts @@ -0,0 +1,25 @@ +import { spawnSync } from 'child_process'; +import { resolveOpenskillsCli } from '../core/openskills.js'; + +export interface SyncCommandOptions { + yes?: boolean; + output?: string; +} + +export async function runSync(options: SyncCommandOptions): Promise { + const cliPath = resolveOpenskillsCli(); + const args = [cliPath, 'sync']; + if (options.yes) { + args.push('--yes'); + } + if (options.output) { + args.push('--output', options.output); + } + const result = spawnSync(process.execPath, args, { + cwd: process.cwd(), + stdio: 'inherit', + }); + if (result.status !== 0) { + throw new Error('openskills sync failed'); + } +} diff --git a/.claude/skills/oneskill/src/core/fs.ts b/.claude/skills/oneskill/src/core/fs.ts new file mode 100644 index 0000000..77ec809 --- /dev/null +++ b/.claude/skills/oneskill/src/core/fs.ts @@ -0,0 +1,24 @@ +import { mkdirSync, readFileSync, writeFileSync, existsSync } from 'fs'; +import { dirname, resolve, sep } from 'path'; + +export function ensureDir(path: string): void { + mkdirSync(path, { recursive: true }); +} + +export function readJsonFile(path: string): T | null { + if (!existsSync(path)) return null; + const content = readFileSync(path, 'utf-8'); + return JSON.parse(content) as T; +} + +export function writeJsonFile(path: string, data: unknown): void { + ensureDir(dirname(path)); + writeFileSync(path, JSON.stringify(data, null, 2) + '\n', 'utf-8'); +} + +export function isPathInside(targetPath: string, baseDir: string): boolean { + const resolvedTarget = resolve(targetPath); + const resolvedBase = resolve(baseDir); + const baseWithSep = resolvedBase.endsWith(sep) ? resolvedBase : resolvedBase + sep; + return resolvedTarget === resolvedBase || resolvedTarget.startsWith(baseWithSep); +} diff --git a/.claude/skills/oneskill/src/core/lock.ts b/.claude/skills/oneskill/src/core/lock.ts new file mode 100644 index 0000000..ac33c82 --- /dev/null +++ b/.claude/skills/oneskill/src/core/lock.ts @@ -0,0 +1,23 @@ +import { join } from 'path'; +import { readJsonFile, writeJsonFile, ensureDir } from './fs.js'; +import type { LockFile, LockedSkill } from './types.js'; + +const LOCK_FILE_NAME = 'lock.json'; + +export function getLockPath(root: string): string { + return join(root, '.oneskill', LOCK_FILE_NAME); +} + +export function readLock(root: string): LockFile | null { + return readJsonFile(getLockPath(root)); +} + +export function writeLock(root: string, lock: LockFile): void { + ensureDir(join(root, '.oneskill')); + writeJsonFile(getLockPath(root), lock); +} + +export function upsertLockSkill(lock: LockFile, skill: LockedSkill): void { + lock.skills[skill.id] = skill; + lock.updatedAt = new Date().toISOString(); +} diff --git a/.claude/skills/oneskill/src/core/log.ts b/.claude/skills/oneskill/src/core/log.ts new file mode 100644 index 0000000..25a78fa --- /dev/null +++ b/.claude/skills/oneskill/src/core/log.ts @@ -0,0 +1,11 @@ +import { appendFileSync } from 'fs'; +import { join } from 'path'; +import { ensureDir } from './fs.js'; + +export function appendLog(root: string, event: Record): void { + const logDir = join(root, '.oneskill', 'logs'); + ensureDir(logDir); + const date = new Date().toISOString().slice(0, 10); + const logPath = join(logDir, `${date}.jsonl`); + appendFileSync(logPath, JSON.stringify(event) + '\n', 'utf-8'); +} diff --git a/.claude/skills/oneskill/src/core/manifest.ts b/.claude/skills/oneskill/src/core/manifest.ts new file mode 100644 index 0000000..5cbb2ad --- /dev/null +++ b/.claude/skills/oneskill/src/core/manifest.ts @@ -0,0 +1,33 @@ +import { lstatSync, readdirSync } from 'fs'; +import { join } from 'path'; +import type { ManifestSummary } from './types.js'; + +export function buildManifestSummary(root: string): ManifestSummary { + let files = 0; + let bytes = 0; + let hasSymlinks = false; + + const walk = (dir: string): void => { + const entries = readdirSync(dir, { withFileTypes: true }); + for (const entry of entries) { + const fullPath = join(dir, entry.name); + const stat = lstatSync(fullPath); + if (stat.isSymbolicLink()) { + hasSymlinks = true; + files += 1; + continue; + } + if (stat.isDirectory()) { + walk(fullPath); + continue; + } + if (stat.isFile()) { + files += 1; + bytes += stat.size; + } + } + }; + + walk(root); + return { files, bytes, hasSymlinks }; +} diff --git a/.claude/skills/oneskill/src/core/map.ts b/.claude/skills/oneskill/src/core/map.ts new file mode 100644 index 0000000..e688578 --- /dev/null +++ b/.claude/skills/oneskill/src/core/map.ts @@ -0,0 +1,49 @@ +import { existsSync, readdirSync } from 'fs'; +import { basename, join } from 'path'; +import { homedir } from 'os'; +import { mapSkill } from './mapping.js'; +import { detectRoot } from './root.js'; +import type { TargetEnvironment } from './types.js'; + +export interface MapOptions { + target: TargetEnvironment; + global?: boolean; + universal?: boolean; + forceMap?: boolean; +} + +function getSourceBase(root: string, options: MapOptions): string { + const baseRoot = options.global ? homedir() : root; + const folder = options.universal ? '.agent/skills' : '.claude/skills'; + return join(baseRoot, folder); +} + +function getTargetRoot(root: string, options: MapOptions): string { + return options.global ? homedir() : root; +} + +export function mapInstalledSkills(options: MapOptions): { mapped: number; sourceBase: string; targetRoot: string } { + if (options.target !== 'gemini') { + throw new Error('map currently supports only --target gemini'); + } + const rootInfo = detectRoot(process.cwd()); + const sourceBase = getSourceBase(rootInfo.root, options); + const targetRoot = getTargetRoot(rootInfo.root, options); + + if (!existsSync(sourceBase)) { + return { mapped: 0, sourceBase, targetRoot }; + } + + const entries = readdirSync(sourceBase, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => join(sourceBase, entry.name)); + + let mapped = 0; + for (const dir of entries) { + const name = basename(dir); + mapSkill(targetRoot, 'gemini', name, dir, { forceMap: options.forceMap }); + mapped += 1; + } + + return { mapped, sourceBase, targetRoot }; +} diff --git a/.claude/skills/oneskill/src/core/mapping.ts b/.claude/skills/oneskill/src/core/mapping.ts new file mode 100644 index 0000000..ef61d68 --- /dev/null +++ b/.claude/skills/oneskill/src/core/mapping.ts @@ -0,0 +1,85 @@ +import { existsSync, lstatSync, readlinkSync, rmSync, symlinkSync, cpSync, writeFileSync } from 'fs'; +import { join, resolve } from 'path'; +import { ensureDir, isPathInside } from './fs.js'; +import type { SkillMappingRecord, TargetEnvironment } from './types.js'; + +const TARGET_DIRS: Record = { + codex: '.codex/skills', + gemini: '.gemini/skills', + claude: '.claude/skills', + agent: '.agent/skills', +}; + +export interface MapOptions { + forceMap?: boolean; + codexHome?: string; +} + +function getTargetBase(root: string, target: TargetEnvironment, options?: MapOptions): string { + if (target === 'codex' && options?.codexHome) { + return resolve(options.codexHome, 'skills'); + } + return join(root, TARGET_DIRS[target]); +} + +function readLinkTarget(path: string): string | null { + try { + return readlinkSync(path); + } catch { + return null; + } +} + +export function mapSkill( + root: string, + target: TargetEnvironment, + skillName: string, + storePath: string, + options?: MapOptions +): SkillMappingRecord { + const baseDir = getTargetBase(root, target, options); + const targetPath = join(baseDir, skillName); + + if (target !== 'codex' && !isPathInside(targetPath, root)) { + throw new Error(`Target path escapes root: ${targetPath}`); + } + + ensureDir(baseDir); + + if (existsSync(targetPath)) { + const stat = lstatSync(targetPath); + if (stat.isSymbolicLink()) { + const linkTarget = readLinkTarget(targetPath); + if (linkTarget && resolve(linkTarget) === resolve(storePath)) { + return { target, path: targetPath, mode: process.platform === 'win32' ? 'junction' : 'symlink', updatedAt: new Date().toISOString() }; + } + if (!options?.forceMap) { + throw new Error(`Target already exists (symlink): ${targetPath}`); + } + } else { + if (!options?.forceMap) { + throw new Error(`Target already exists: ${targetPath}`); + } + } + rmSync(targetPath, { recursive: true, force: true }); + } + + const linkType = process.platform === 'win32' ? 'junction' : 'dir'; + try { + symlinkSync(storePath, targetPath, linkType); + return { target, path: targetPath, mode: process.platform === 'win32' ? 'junction' : 'symlink', updatedAt: new Date().toISOString() }; + } catch (error) { + if (!options?.forceMap) { + // Fall through to copy on Windows permission errors or other failures. + } + } + + cpSync(storePath, targetPath, { recursive: true, dereference: false }); + writeFileSync( + join(targetPath, '.oneskill-meta.json'), + JSON.stringify({ source: storePath, mappedAt: new Date().toISOString(), mode: 'copy' }, null, 2) + '\n', + 'utf-8' + ); + + return { target, path: targetPath, mode: 'copy', updatedAt: new Date().toISOString() }; +} diff --git a/.claude/skills/oneskill/src/core/openskills.ts b/.claude/skills/oneskill/src/core/openskills.ts new file mode 100644 index 0000000..af9a073 --- /dev/null +++ b/.claude/skills/oneskill/src/core/openskills.ts @@ -0,0 +1,42 @@ +import { readFileSync, existsSync } from 'fs'; +import { dirname, resolve } from 'path'; +import { createRequire } from 'module'; +import { spawnSync } from 'child_process'; + +const require = createRequire(import.meta.url); + +export function resolveOpenskillsCli(): string { + const pkgPath = require.resolve('openskills/package.json'); + const pkgDir = dirname(pkgPath); + const content = readFileSync(pkgPath, 'utf-8'); + const parsed = JSON.parse(content) as { bin?: Record | string; main?: string }; + const bin = parsed.bin; + let candidate: string | null = null; + if (typeof bin === 'string') { + candidate = resolve(pkgDir, bin); + } else if (bin && typeof bin === 'object') { + const first = Object.values(bin)[0]; + if (first) candidate = resolve(pkgDir, first); + } else if (parsed.main) { + candidate = resolve(pkgDir, parsed.main); + } + + if (candidate && existsSync(candidate)) { + return candidate; + } + + // Build openskills locally if the CLI entry is missing. + const result = spawnSync('npm', ['run', 'build'], { + cwd: pkgDir, + stdio: 'inherit', + }); + if (result.status !== 0) { + throw new Error('Failed to build openskills'); + } + + if (candidate && existsSync(candidate)) { + return candidate; + } + + throw new Error('Unable to resolve openskills CLI entry'); +} diff --git a/.claude/skills/oneskill/src/core/registry.ts b/.claude/skills/oneskill/src/core/registry.ts new file mode 100644 index 0000000..b7cacb3 --- /dev/null +++ b/.claude/skills/oneskill/src/core/registry.ts @@ -0,0 +1,110 @@ +import type { RegistryInfoResponse, RegistrySearchResponse, SkillListItem } from './types.js'; + +const DEFAULT_REGISTRY_URL = 'https://skillsdirectory.com/api/registry'; + +export interface SearchParams { + q?: string; + category?: string; + limit?: number; + offset?: number; + sort?: string; +} + +function getRegistryBase(override?: string): string { + return (override || process.env.ONESKILL_REGISTRY_URL || DEFAULT_REGISTRY_URL).replace(/\/$/, ''); +} + +async function fetchJson(url: string): Promise { + const res = await fetch(url, { headers: { 'accept': 'application/json' } }); + if (!res.ok) { + const text = await res.text(); + throw new Error(`Registry request failed (${res.status}): ${text.slice(0, 200)}`); + } + return res.json(); +} + +function normalizeSkill(item: Record): SkillListItem { + const slug = String(item.slug || item.id || item.name || ''); + const name = String(item.name || item.title || slug || ''); + const description = String(item.description || item.summary || ''); + const repository = String(item.repository || item.repo || item.url || ''); + const verified = typeof item.verified === 'boolean' ? item.verified : undefined; + const stars = + typeof item.stars === 'number' + ? item.stars + : typeof (item.github as { stars?: unknown } | undefined)?.stars === 'number' + ? (item.github as { stars: number }).stars + : undefined; + const tags = Array.isArray(item.tags) ? item.tags.map(String) : undefined; + const authorObj = item.author as { name?: unknown; url?: unknown } | undefined; + const authorName = typeof item.author === 'string' ? String(item.author) : authorObj?.name ? String(authorObj.name) : undefined; + const author = authorName + ? { name: authorName, url: authorObj?.url ? String(authorObj.url) : undefined } + : undefined; + + const signals = { + lastUpdated: item.lastUpdated ? String(item.lastUpdated) : undefined, + license: + item.license ? String(item.license) + : typeof (item.github as { license?: unknown } | undefined)?.license === 'string' + ? String((item.github as { license: string }).license) + : undefined, + riskHints: Array.isArray(item.riskHints) ? item.riskHints.map(String) : undefined, + }; + + return { + schemaVersion: '1', + slug, + name, + description, + repository, + verified, + stars, + tags, + author, + signals, + }; +} + +function normalizeSearchPayload(payload: unknown): SkillListItem[] { + if (!payload || typeof payload !== 'object') return []; + const data = payload as Record; + const candidates = + (Array.isArray(data.skills) ? data.skills : null) || + (Array.isArray(data.items) ? data.items : null) || + (Array.isArray(data.results) ? data.results : null) || + (Array.isArray(data.data) ? data.data : null) || + (Array.isArray(payload) ? (payload as unknown[]) : null); + + if (!candidates) return []; + return candidates + .filter((item) => item && typeof item === 'object') + .map((item) => normalizeSkill(item as Record)); +} + +export async function searchRegistry(params: SearchParams, overrideUrl?: string): Promise { + const base = getRegistryBase(overrideUrl); + const url = new URL(base); + if (params.q) url.searchParams.set('q', params.q); + if (params.category) url.searchParams.set('category', params.category); + if (typeof params.limit === 'number') url.searchParams.set('limit', String(params.limit)); + if (typeof params.offset === 'number') url.searchParams.set('offset', String(params.offset)); + if (params.sort) url.searchParams.set('sort', params.sort); + const raw = await fetchJson(url.toString()); + const items = normalizeSearchPayload(raw); + return { items, raw }; +} + +export async function fetchRegistryInfo(slug: string, overrideUrl?: string): Promise { + const base = getRegistryBase(overrideUrl); + const url = `${base}/${encodeURIComponent(slug)}`; + const raw = await fetchJson(url); + if (raw && typeof raw === 'object') { + const record = raw as Record; + const candidate = (record.skill as Record | undefined) || (record.item as Record | undefined) || record; + if (candidate && typeof candidate === 'object') { + return { item: normalizeSkill(candidate as Record), raw }; + } + } + throw new Error('Registry info failed'); +} diff --git a/.claude/skills/oneskill/src/core/root.ts b/.claude/skills/oneskill/src/core/root.ts new file mode 100644 index 0000000..7ecb43e --- /dev/null +++ b/.claude/skills/oneskill/src/core/root.ts @@ -0,0 +1,38 @@ +import { existsSync, readFileSync } from 'fs'; +import { dirname, join, resolve } from 'path'; +import type { RootDetection } from './types.js'; + +const MARKER_DIRS = ['.git', '.agent', '.claude', '.gemini', '.codex']; + +function hasOneskillConfig(pkgPath: string): boolean { + try { + const content = readFileSync(pkgPath, 'utf-8'); + const parsed = JSON.parse(content) as { oneskill?: unknown }; + return Boolean(parsed.oneskill); + } catch { + return false; + } +} + +function hasMarkers(dir: string): string | null { + for (const marker of MARKER_DIRS) { + if (existsSync(join(dir, marker))) return marker; + } + if (existsSync(join(dir, 'AGENTS.md'))) return 'AGENTS.md'; + const pkgPath = join(dir, 'package.json'); + if (existsSync(pkgPath) && hasOneskillConfig(pkgPath)) return 'package.json:oneskill'; + return null; +} + +export function detectRoot(startDir: string): RootDetection { + let current = resolve(startDir); + while (true) { + const reason = hasMarkers(current); + if (reason) return { root: current, reason }; + const parent = dirname(current); + if (parent === current) { + return { root: resolve(startDir), reason: 'fallback:cwd' }; + } + current = parent; + } +} diff --git a/.claude/skills/oneskill/src/core/types.ts b/.claude/skills/oneskill/src/core/types.ts new file mode 100644 index 0000000..fc70070 --- /dev/null +++ b/.claude/skills/oneskill/src/core/types.ts @@ -0,0 +1,81 @@ +export type TargetEnvironment = 'codex' | 'gemini' | 'claude' | 'agent'; + +export interface SkillListItem { + schemaVersion: '1'; + slug: string; + name: string; + description: string; + repository: string; + verified?: boolean; + stars?: number; + tags?: string[]; + author?: { + name: string; + url?: string; + }; + signals?: { + lastUpdated?: string; + license?: string; + riskHints?: string[]; + }; +} + +export interface RegistryInfoResponse { + item: SkillListItem; + raw: unknown; +} + +export interface RegistrySearchResponse { + items: SkillListItem[]; + raw: unknown; +} + +export interface ManifestSummary { + files: number; + bytes: number; + hasSymlinks: boolean; +} + +export interface SkillSource { + input: string; + type: 'slug' | 'repository' | 'local'; + repository?: string; + ref?: string; +} + +export interface SkillMappingRecord { + target: TargetEnvironment; + path: string; + mode: 'symlink' | 'junction' | 'copy'; + updatedAt: string; +} + +export interface LockedSkill { + id: string; + name: string; + source: SkillSource; + installedAt: string; + storePath: string; + manifest: ManifestSummary; + mappings: SkillMappingRecord[]; +} + +export interface LockFile { + schemaVersion: '1'; + root: string; + oneskillVersion: string; + openskillsVersion: string; + updatedAt: string; + skills: Record; +} + +export interface RootDetection { + root: string; + reason: string; +} + +export interface InstallResult { + root: string; + skills: LockedSkill[]; + target?: TargetEnvironment; +} diff --git a/.claude/skills/oneskill/src/core/versions.ts b/.claude/skills/oneskill/src/core/versions.ts new file mode 100644 index 0000000..65712c7 --- /dev/null +++ b/.claude/skills/oneskill/src/core/versions.ts @@ -0,0 +1,29 @@ +import { readFileSync } from 'fs'; +import { dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { createRequire } from 'module'; + +const require = createRequire(import.meta.url); + +export function getOneskillVersion(): string { + const __dirname = dirname(fileURLToPath(import.meta.url)); + const pkgPath = join(__dirname, '../../package.json'); + try { + const content = readFileSync(pkgPath, 'utf-8'); + const parsed = JSON.parse(content) as { version?: string }; + return parsed.version || '0.0.0'; + } catch { + return '0.0.0'; + } +} + +export function getOpenskillsVersion(): string { + try { + const pkgPath = require.resolve('openskills/package.json'); + const content = readFileSync(pkgPath, 'utf-8'); + const parsed = JSON.parse(content) as { version?: string }; + return parsed.version || '0.0.0'; + } catch { + return '0.0.0'; + } +} diff --git a/.claude/skills/oneskill/src/utils/json.ts b/.claude/skills/oneskill/src/utils/json.ts new file mode 100644 index 0000000..4e42179 --- /dev/null +++ b/.claude/skills/oneskill/src/utils/json.ts @@ -0,0 +1,3 @@ +export function printJson(data: unknown): void { + process.stdout.write(JSON.stringify(data, null, 2) + '\n'); +} diff --git a/.claude/skills/oneskill/tsconfig.json b/.claude/skills/oneskill/tsconfig.json new file mode 100644 index 0000000..ffd80c7 --- /dev/null +++ b/.claude/skills/oneskill/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "Bundler", + "outDir": "dist", + "rootDir": "src", + "strict": true, + "declaration": true, + "skipLibCheck": true, + "esModuleInterop": true + }, + "include": ["src"] +} diff --git a/.claude/skills/oneskill/tsup.config.ts b/.claude/skills/oneskill/tsup.config.ts new file mode 100644 index 0000000..eb81b2e --- /dev/null +++ b/.claude/skills/oneskill/tsup.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: ['src/cli.ts'], + format: ['esm'], + dts: false, + sourcemap: true, + clean: true, + target: 'node20' +}); diff --git a/.gitignore b/.gitignore index 31ba5f1..162a0bf 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ examples/sessions/*.tmp # Local drafts marketing/ +!.claude/ +!.claude-plugin/ diff --git a/README.md b/README.md index 272b54d..a28cfbc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -**语言:** [English](README.md) | 繁體中文 +**语言:** [English](README.md) | **简体中文** | [繁體中文](docs/zh-TW/README.md) # Everything Claude Code @@ -9,54 +9,101 @@ ![Go](https://img.shields.io/badge/-Go-00ADD8?logo=go&logoColor=white) ![Markdown](https://img.shields.io/badge/-Markdown-000000?logo=markdown&logoColor=white) -

- English | - 简体中文 -

+--- -**由 Anthropic 黑客松获胜者整理的 Claude Code 配置完整合集。** +
-这是在 10 个月以上高强度日常开发真实产品的过程中,不断演进出的生产级智能体(Agents)、技能(Skills)、钩子(Hooks)、命令(Commands)、规则(Rules)以及 MCP 配置。 +**🌐 Language / 语言 / 語言** + +[**English**](README.md) | [简体中文](README.zh-CN.md) | [繁體中文](docs/zh-TW/README.md) + +
--- -## 指南(The Guides) +**来自 Anthropic 黑客松获胜者的 Claude Code 配置全集。** -本仓库仅包含原始代码。以下指南将解释所有细节。 +包含生产级的智能体(Agents)、技能(Skills)、钩子(Hooks)、命令(Commands)、规则(Rules)以及 MCP 配置。这些配置源自 10 个多月在构建真实产品过程中的高强度日常使用与演进。 + +--- + +## 指南文档 + +本仓库仅包含原始代码。以下指南将详细解释一切: - +
- -Everything Claude Code 简明指南 + +Everything Claude Code 简明指南 - -Everything Claude Code 深度指南 + +Everything Claude Code 深度指南
简明指南 (Shorthand Guide)
安装、基础、哲学。请先阅读此篇。
深度指南 (Longform Guide)
Token 优化、内存持久化、评测(Evals)、并行化。
深度指南 (Longform Guide)
Token 优化、记忆持久化、评测(Evals)、并行化。
-| 主题 | 你将学到什么 | +| 主题 | 你将学到 | |-------|-------------------| | Token 优化 | 模型选择、系统提示词精简、后台进程 | -| 内存持久化 | 跨会话(Session)自动保存/加载上下文的钩子(Hooks) | -| 持续学习 | 从会话中自动提取模式并转化为可复用的技能(Skills) | -| 验证循环 | 检查点(Checkpoint)与持续评测(Evals)、评分器类型、pass@k 指标 | +| 记忆持久化 | 跨会话自动保存/加载上下文的钩子(Hooks) | +| 持续学习 | 从会话中自动提取模式并转化为可重用的技能(Skills) | +| 验证循环 | 检查点(Checkpoint)vs 持续评测、评分器类型、pass@k 指标 | | 并行化 | Git worktrees、级联方法、何时扩展实例 | | 子智能体编排 | 上下文问题、迭代检索模式 | --- -## 跨平台支持 +## 🚀 快速开始 -该插件现已全面支持 **Windows、macOS 和 Linux**。所有钩子(Hooks)和脚本都已使用 Node.js 重写,以实现最大的兼容性。 +不到 2 分钟即可完成配置: + +### 第一步:安装插件 + +```bash +# 添加市场 +/plugin marketplace add affaan-m/everything-claude-code + +# 安装插件 +/plugin install everything-claude-code@everything-claude-code +``` + +### 第二步:安装规则(必选) + +> ⚠️ **重要提示:** Claude Code 插件无法自动分发 `rules`。请手动安装: + +```bash +# 首先克隆仓库 +git clone https://github.com/affaan-m/everything-claude-code.git + +# 复制规则(适用于所有项目) +cp -r everything-claude-code/rules/* ~/.claude/rules/ +``` + +### 第三步:开始使用 + +```bash +# 尝试一个命令 +/plan "Add user authentication" + +# 查看可用命令 +/plugin list everything-claude-code@everything-claude-code +``` + +✨ **大功告成!** 你现在可以使用 15+ 个智能体、30+ 个技能和 20+ 个命令了。 + +--- + +## 🌐 跨平台支持 + +该插件现已全面支持 **Windows、macOS 和 Linux**。所有钩子和脚本均已使用 Node.js 重写,以确保最大兼容性。 ### 包管理器检测 @@ -65,9 +112,9 @@ 1. **环境变量**:`CLAUDE_PACKAGE_MANAGER` 2. **项目配置**:`.claude/package-manager.json` 3. **package.json**:`packageManager` 字段 -4. **锁文件**:根据 package-lock.json, yarn.lock, pnpm-lock.yaml 或 bun.lockb 检测 +4. **锁文件**:根据 package-lock.json, yarn.lock, pnpm-lock.yaml, 或 bun.lockb 检测 5. **全局配置**:`~/.claude/package-manager.json` -6. **备选项**:第一个可用的包管理器 +6. **兜底方案**:第一个可用的包管理器 设置你偏好的包管理器: @@ -89,65 +136,65 @@ node scripts/setup-package-manager.js --detect --- -## 包含内容 +## 📦 内容清单 -本仓库是一个 **Claude Code 插件** —— 你可以直接安装它,或者手动复制组件。 +本仓库是一个 **Claude Code 插件** —— 你可以直接安装,也可以手动复制组件。 ``` everything-claude-code/ -|-- .claude-plugin/ # 插件与市场清单 -| |-- plugin.json # 插件元数据与组件路径 +|-- .claude-plugin/ # 插件和市场清单文件 +| |-- plugin.json # 插件元数据和组件路径 | |-- marketplace.json # 用于 /plugin marketplace add 的市场目录 | -|-- agents/ # 用于任务委派的专用子智能体(Subagents) +|-- agents/ # 用于任务委派的专业化子智能体(Subagents) | |-- planner.md # 功能实现规划 | |-- architect.md # 系统设计决策 | |-- tdd-guide.md # 测试驱动开发(TDD) -| |-- code-reviewer.md # 代码质量与安全评审 +| |-- code-reviewer.md # 质量与安全审查 | |-- security-reviewer.md # 漏洞分析 -| |-- build-error-resolver.md # 构建错误解决 -| |-- e2e-runner.md # Playwright 端到端测试 +| |-- build-error-resolver.md # 构建错误修复 +| |-- e2e-runner.md # Playwright 端到端(E2E)测试 | |-- refactor-cleaner.md # 死代码清理 | |-- doc-updater.md # 文档同步 -| |-- go-reviewer.md # Go 代码评审(新增) -| |-- go-build-resolver.md # Go 构建错误解决(新增) +| |-- go-reviewer.md # Go 代码审查(新增) +| |-- go-build-resolver.md # Go 构建错误修复(新增) | -|-- skills/ # 工作流(Workflow)定义与领域知识 +|-- skills/ # 工作流定义与领域知识 | |-- coding-standards/ # 语言最佳实践 | |-- backend-patterns/ # API、数据库、缓存模式 | |-- frontend-patterns/ # React、Next.js 模式 -| |-- continuous-learning/ # 从会话中自动提取模式(深度指南内容) -| |-- continuous-learning-v2/ # 基于直觉(Instinct)的带置信度评分学习系统 -| |-- iterative-retrieval/ # 子智能体的渐进式上下文精炼 -| |-- strategic-compact/ # 手动压缩建议(深度指南内容) +| |-- continuous-learning/ # 从会话中自动提取模式(深度指南) +| |-- continuous-learning-v2/ # 基于“直觉(Instinct)”的学习,带有置信度评分 +| |-- iterative-retrieval/ # 为子智能体提供渐进式上下文精炼 +| |-- strategic-compact/ # 手动压缩建议(深度指南) | |-- tdd-workflow/ # TDD 方法论 -| |-- security-review/ # 安全自查表 -| |-- eval-harness/ # 验证循环评测(深度指南内容) -| |-- verification-loop/ # 持续验证(深度指南内容) +| |-- security-review/ # 安全检查清单 +| |-- eval-harness/ # 验证循环评测(深度指南) +| |-- verification-loop/ # 持续验证(深度指南) | |-- golang-patterns/ # Go 惯用法与最佳实践(新增) | |-- golang-testing/ # Go 测试模式、TDD、基准测试(新增) | -|-- commands/ # 用于快速执行的斜杠命令(Slash Commands) +|-- commands/ # 用于快速执行的斜杠命令 | |-- tdd.md # /tdd - 测试驱动开发 -| |-- plan.md # /plan - 实现规划 -| |-- e2e.md # /e2e - 端到端测试生成 -| |-- code-review.md # /code-review - 质量评审 +| |-- plan.md # /plan - 实现方案规划 +| |-- e2e.md # /e2e - 生成 E2E 测试 +| |-- code-review.md # /code-review - 质量审查 | |-- build-fix.md # /build-fix - 修复构建错误 | |-- refactor-clean.md # /refactor-clean - 移除死代码 -| |-- learn.md # /learn - 会话中途提取模式(深度指南内容) -| |-- checkpoint.md # /checkpoint - 保存验证状态(深度指南内容) -| |-- verify.md # /verify - 运行验证循环(深度指南内容) +| |-- learn.md # /learn - 在会话中途提取模式(深度指南) +| |-- checkpoint.md # /checkpoint - 保存验证状态(深度指南) +| |-- verify.md # /verify - 运行验证循环(深度指南) | |-- setup-pm.md # /setup-pm - 配置包管理器 -| |-- go-review.md # /go-review - Go 代码评审(新增) +| |-- go-review.md # /go-review - Go 代码审查(新增) | |-- go-test.md # /go-test - Go TDD 工作流(新增) | |-- go-build.md # /go-build - 修复 Go 构建错误(新增) -| |-- skill-create.md # /skill-create - 从 git 历史生成技能(新增) +| |-- skill-create.md # /skill-create - 从 Git 历史生成技能(新增) | |-- instinct-status.md # /instinct-status - 查看已学习的直觉(新增) | |-- instinct-import.md # /instinct-import - 导入直觉(新增) | |-- instinct-export.md # /instinct-export - 导出直觉(新增) -| |-- evolve.md # /evolve - 将相关直觉聚类为技能(新增) +| |-- evolve.md # /evolve - 将直觉聚类为技能(新增) | -|-- rules/ # 必须遵守的准则(复制到 ~/.claude/rules/) +|-- rules/ # 必须遵守的指南(需复制到 ~/.claude/rules/) | |-- security.md # 强制性安全检查 | |-- coding-style.md # 不可变性、文件组织 | |-- testing.md # TDD、80% 覆盖率要求 @@ -157,17 +204,17 @@ everything-claude-code/ | |-- hooks/ # 基于触发器的自动化 | |-- hooks.json # 所有钩子配置(PreToolUse, PostToolUse, Stop 等) -| |-- memory-persistence/ # 会话生命周期钩子(深度指南内容) -| |-- strategic-compact/ # 压缩建议(深度指南内容) +| |-- memory-persistence/ # 会话生命周期钩子(深度指南) +| |-- strategic-compact/ # 压缩建议(深度指南) | |-- scripts/ # 跨平台 Node.js 脚本(新增) -| |-- lib/ # 共享实用程序 +| |-- lib/ # 共享工具库 | | |-- utils.js # 跨平台文件/路径/系统工具 | | |-- package-manager.js # 包管理器检测与选择 | |-- hooks/ # 钩子实现 | | |-- session-start.js # 会话开始时加载上下文 | | |-- session-end.js # 会话结束时保存状态 -| | |-- pre-compact.js # 压缩前状态保存 +| | |-- pre-compact.js # 压缩前的状态保存 | | |-- suggest-compact.js # 战略性压缩建议 | | |-- evaluate-session.js # 从会话中提取模式 | |-- setup-package-manager.js # 交互式包管理器设置 @@ -177,108 +224,108 @@ everything-claude-code/ | |-- hooks/ # 钩子测试 | |-- run-all.js # 运行所有测试 | -|-- contexts/ # 动态系统提示词注入上下文(深度指南内容) +|-- contexts/ # 动态系统提示词注入上下文(深度指南) | |-- dev.md # 开发模式上下文 -| |-- review.md # 代码评审模式上下文 +| |-- review.md # 代码审查模式上下文 | |-- research.md # 研究/探索模式上下文 | -|-- examples/ # 配置与会话示例 +|-- examples/ # 配置和会话示例 | |-- CLAUDE.md # 项目级配置示例 | |-- user-CLAUDE.md # 用户级配置示例 | |-- mcp-configs/ # MCP 服务配置 -| |-- mcp-servers.json # GitHub, Supabase, Vercel, Railway 等 +| |-- mcp-servers.json # GitHub, Supabase, Vercel, Railway 等配置 | |-- marketplace.json # 自托管市场配置(用于 /plugin marketplace add) ``` --- -## 生态工具 +## 🛠️ 生态工具 ### 技能创建器(Skill Creator) -有两种方法可以从你的仓库生成 Claude Code 技能: +有两种方式可以从你的仓库生成 Claude Code 技能: -#### 方案 A:本地分析(内置) +#### 选项 A:本地分析(内置) 使用 `/skill-create` 命令进行本地分析,无需外部服务: ```bash /skill-create # 分析当前仓库 -/skill-create --instincts # 同时为持续学习(continuous-learning)生成直觉(instincts) +/skill-create --instincts # 同时为持续学习生成“直觉(Instincts)” ``` -该命令会在本地分析你的 git 历史并生成 SKILL.md 文件。 +这会在本地分析你的 Git 历史并生成 SKILL.md 文件。 -#### 方案 B:GitHub App(高级) +#### 选项 B:GitHub App(高级版) -适用于高级功能(1万+ commit、自动 PR、团队共享): +适用于高级功能(10k+ 提交、自动 PR、团队共享): [安装 GitHub App](https://github.com/apps/skill-creator) | [ecc.tools](https://ecc.tools) ```bash -# 在任何 issue 下留言: +# 在任何 Issue 下回复: /skill-creator analyze -# 或者在 push 到默认分支时自动触发 +# 或在推送到默认分支时自动触发 ``` -两种方案都会创建: +两种选项都会创建: - **SKILL.md 文件** - 可直接用于 Claude Code 的技能 -- **直觉集合(Instinct collections)** - 用于 continuous-learning-v2 -- **模式提取(Pattern extraction)** - 从你的提交历史中学习 +- **直觉集合 (Instinct collections)** - 用于 continuous-learning-v2 +- **模式提取** - 从你的提交历史中学习 -### 持续学习(Continuous Learning)v2 +### 🧠 持续学习 v2 (Continuous Learning v2) -基于直觉(instinct)的学习系统会自动学习你的开发模式: +基于“直觉(Instinct)”的学习系统会自动学习你的模式: ```bash -/instinct-status # 显示带有置信度的已学习直觉 +/instinct-status # 显示已学习的直觉及其置信度 /instinct-import # 导入他人的直觉 -/instinct-export # 导出你的直觉以便分享 -/evolve # 将相关的直觉聚类为技能(skills) +/instinct-export # 导出你的直觉以便共享 +/evolve # 将相关的直觉聚类为技能 ``` -详见 `skills/continuous-learning-v2/` 的完整文档。 +详见 `skills/continuous-learning-v2/` 完整文档。 --- -## 要求 +## 📋 运行要求 ### Claude Code CLI 版本 **最低版本:v2.1.0 或更高** -由于插件系统处理钩子(hooks)方式的变更,本插件要求 Claude Code CLI v2.1.0+。 +由于插件系统处理钩子(Hooks)方式的变更,此插件需要 Claude Code CLI v2.1.0+。 检查你的版本: ```bash claude --version ``` -### 重要:钩子(Hooks)自动加载行为 +### 重要:钩子自动加载行为 -> ⚠️ **对贡献者的提醒:** 请勿在 `.claude-plugin/plugin.json` 中添加 `"hooks"` 字段。这是由回归测试强制执行的。 +> ⚠️ **致贡献者:** 请勿在 `.claude-plugin/plugin.json` 中添加 `"hooks"` 字段。这是通过回归测试强制执行的。 -Claude Code v2.1+ 会**自动加载**已安装插件中约定的 `hooks/hooks.json`。在 `plugin.json` 中显式声明会导致重复检测错误: +按照约定,Claude Code v2.1+ 会**自动加载**任何已安装插件中的 `hooks/hooks.json`。如果在 `plugin.json` 中显式声明,会导致重复检测错误: ``` Duplicate hooks file detected: ./hooks/hooks.json resolves to already-loaded file ``` -**历史背景:** 此问题在本仓库中曾多次出现修复/回退循环([#29](https://github.com/affaan-m/everything-claude-code/issues/29), [#52](https://github.com/affaan-m/everything-claude-code/issues/52), [#103](https://github.com/affaan-m/everything-claude-code/issues/103))。Claude Code 版本间的行为差异导致了混淆。我们现在已加入回归测试以防止此类问题再次发生。 +**历史背景:** 此问题在本仓库中引发了多次修复/回滚循环([#29](https://github.com/affaan-m/everything-claude-code/issues/29), [#52](https://github.com/affaan-m/everything-claude-code/issues/52), [#103](https://github.com/affaan-m/everything-claude-code/issues/103))。由于 Claude Code 版本间的行为变更导致了混淆,我们现在通过回归测试来防止此问题再次引入。 --- -## 安装 +## 📥 安装 -### 方案 1:作为插件安装(推荐) +### 选项 1:作为插件安装(推荐) -这是使用本仓库最简单的方法 —— 作为 Claude Code 插件安装: +使用本仓库最简单的方式 —— 作为 Claude Code 插件安装: ```bash -# 将此仓库添加为市场(marketplace) +# 将此仓库添加为市场 /plugin marketplace add affaan-m/everything-claude-code # 安装插件 @@ -303,91 +350,91 @@ Duplicate hooks file detected: ./hooks/hooks.json resolves to already-loaded fil } ``` -安装后即可立即使用所有命令(commands)、智能体(agents)、技能(skills)和钩子(hooks)。 +安装后即可立即使用所有命令、智能体、技能和钩子。 -> **注意:** Claude Code 插件系统目前不支持通过插件分发规则(`rules`)(这是 [上游限制](https://code.claude.com/docs/en/plugins-reference))。你需要手动安装规则: +> **注意:** Claude Code 插件系统目前不支持通过插件分发 `rules`([上游限制](https://code.claude.com/docs/en/plugins-reference))。你需要手动安装规则: > > ```bash > # 首先克隆仓库 > git clone https://github.com/affaan-m/everything-claude-code.git > -> # 方案 A:用户级规则(应用于所有项目) +> # 选项 A:用户级规则(适用于所有项目) > cp -r everything-claude-code/rules/* ~/.claude/rules/ > -> # 方案 B:项目级规则(仅应用于当前项目) +> # 选项 B:项目级规则(仅适用于当前项目) > mkdir -p .claude/rules > cp -r everything-claude-code/rules/* .claude/rules/ > ``` --- -### 方案 2:手动安装 +### 🔧 选项 2:手动安装 -如果你更倾向于手动控制安装的内容: +如果你更喜欢手动控制安装内容: ```bash # 克隆仓库 git clone https://github.com/affaan-m/everything-claude-code.git -# 复制智能体(agents)到你的 Claude 配置目录 +# 将智能体(Agents)复制到你的 Claude 配置中 cp everything-claude-code/agents/*.md ~/.claude/agents/ -# 复制规则(rules) +# 复制规则(Rules) cp everything-claude-code/rules/*.md ~/.claude/rules/ -# 复制命令(commands) +# 复制命令(Commands) cp everything-claude-code/commands/*.md ~/.claude/commands/ -# 复制技能(skills) +# 复制技能(Skills) cp -r everything-claude-code/skills/* ~/.claude/skills/ ``` -#### 将钩子(hooks)添加到 settings.json +#### 将钩子(Hooks)添加到 settings.json -将 `hooks/hooks.json` 中的钩子配置复制到你的 `~/.claude/settings.json` 中。 +将 `hooks/hooks.json` 中的钩子内容复制到你的 `~/.claude/settings.json`。 -#### 配置 MCPs +#### 配置 MCP -将 `mcp-configs/mcp-servers.json` 中你需要的 MCP 服务配置复制到你的 `~/.claude.json`。 +将 `mcp-configs/mcp-servers.json` 中所需的 MCP 服务器配置复制到你的 `~/.claude.json`。 -**重要:** 请将 `YOUR_*_HERE` 占位符替换为你真实的 API 密钥。 +**重要提示:** 请将 `YOUR_*_HERE` 占位符替换为你真实的 API 密钥。 --- -## 核心概念 +## 🎯 核心概念 -### 智能体(Agents) +### 智能体 (Agents) -子智能体(Subagents)负责处理受限范围内的委派任务。示例: +子智能体(Subagents)负责处理具有特定范围的委派任务。示例: ```markdown --- name: code-reviewer -description: 评审代码质量、安全性与可维护性 +description: 审查代码的质量、安全性和可维护性 tools: ["Read", "Grep", "Glob", "Bash"] model: opus --- -你是一名资深代码评审员... +你是一名资深代码审查员... ``` -### 技能(Skills) +### 技能 (Skills) -技能(Skills)是可由命令或智能体调用的工作流定义: +技能是可被命令或智能体调用的工作流定义: ```markdown # TDD 工作流 1. 首先定义接口 -2. 编写失败的测试(RED) -3. 实现最简代码(GREEN) -4. 重构(IMPROVE) -5. 验证覆盖率是否达到 80% 以上 +2. 编写失败的测试 (RED) +3. 实现最简代码 (GREEN) +4. 重构 (IMPROVE) +5. 验证 80%+ 的覆盖率 ``` -### 钩子(Hooks) +### 钩子 (Hooks) -钩子(Hooks)在工具事件发生时触发。示例 —— 针对 console.log 发出警告: +钩子在工具事件发生时触发。示例 —— 警告 `console.log` 的使用: ```json { @@ -399,22 +446,22 @@ model: opus } ``` -### 规则(Rules) +### 规则 (Rules) -规则(Rules)是必须始终遵守的准则。请保持它们的模块化: +规则是必须始终遵循的指南。请保持其模块化: ``` ~/.claude/rules/ - security.md # 禁止硬编码密钥 + security.md # 不允许硬编码密钥 coding-style.md # 不可变性、文件限制 testing.md # TDD、覆盖率要求 ``` --- -## 运行测试 +## 🧪 运行测试 -本插件包含一个全面的测试套件: +该插件包含完整的测试套件: ```bash # 运行所有测试 @@ -428,78 +475,78 @@ node tests/hooks/hooks.test.js --- -## 贡献 +## 🤝 参与贡献 -**非常欢迎并鼓励贡献。** +**欢迎并鼓励各类贡献。** -本仓库旨在成为一个社区资源。如果你有: +本仓库旨在成为社区资源。如果你有: - 有用的智能体或技能 - 巧妙的钩子 - 更好的 MCP 配置 - 改进后的规则 -请提交你的贡献!参考 [CONTRIBUTING.md](CONTRIBUTING.md) 了解指南。 +请尽管贡献!请参阅 [CONTRIBUTING.md](CONTRIBUTING.md) 获取指南。 ### 贡献思路 -- 语言专用技能(Python, Rust 模式)—— 已包含 Go! -- 框架专用配置(Django, Rails, Laravel) +- 语言特定技能(Python, Rust 模式)—— 已包含 Go! +- 框架特定配置(Django, Rails, Laravel) - DevOps 智能体(Kubernetes, Terraform, AWS) - 测试策略(不同框架) -- 领域特定知识(机器学习、数据工程、移动开发) +- 领域特定知识(机器学习、数据工程、移动端) --- -## 背景 +## 📖 项目背景 -我从 Claude Code 实验阶段就开始使用了。在 2025 年 9 月的 Anthropic x Forum Ventures 黑客松中,我与 [@DRodriguezFX](https://x.com/DRodriguezFX) 合作开发了 [zenith.chat](https://zenith.chat),并获得了冠军 —— 该项目完全使用 Claude Code 构建。 +自 Claude Code 实验阶段起我就一直在使用它。2025 年 9 月,我与 [@DRodriguezFX](https://x.com/DRodriguezFX) 凭借 [zenith.chat](https://zenith.chat) 赢得了 Anthropic x Forum Ventures 黑客松 —— 该项目完全使用 Claude Code 构建。 -这些配置在多个生产级应用中经过了实战检验。 +这些配置在多个生产级应用中经过了实战测试。 --- -## 重要提示 +## ⚠️ 重要说明 -### 上下文窗口管理 +### 上下文窗口管理 (Context Window Management) -**至关重要:** 不要一次性启用所有 MCP。如果启用的工具过多,你的 200k 上下文窗口可能会缩减到 70k。 +**至关重要:** 不要同时启用所有 MCP。过多的工具会导致 200k 的上下文窗口缩减至 70k。 经验法则: - 配置 20-30 个 MCP -- 每个项目保持启用 10 个以内 -- 活跃工具总数控制在 80 个以内 +- 每个项目保持启用 10 个以下 +- 活跃工具总数保持在 80 个以下 -在项目配置中使用 `disabledMcpServers` 来禁用不需要的服务。 +使用项目配置中的 `disabledMcpServers` 来禁用不常用的服务器。 ### 自定义 这些配置适用于我的工作流。你应该: -1. 从你产生共鸣的内容开始 +1. 从引起你共鸣的部分开始 2. 根据你的技术栈进行修改 -3. 移除你不需要的内容 -4. 加入你自己的模式 +3. 移除你不需要的部分 +4. 添加你自己的模式 --- -## Star 历史 +## 🌟 Star 历史 [![Star History Chart](https://api.star-history.com/svg?repos=affaan-m/everything-claude-code&type=Date)](https://star-history.com/#affaan-m/everything-claude-code&Date) --- -## 相关链接 +## 🔗 相关链接 -- **简明指南(入门必读):** [Everything Claude Code 简明指南](https://x.com/affaanmustafa/status/2012378465664745795) -- **深度指南(进阶参考):** [Everything Claude Code 深度指南](https://x.com/affaanmustafa/status/2014040193557471352) +- **简明指南 (从这里开始):** [Everything Claude Code 简明指南](https://x.com/affaanmustafa/status/2012378465664745795) +- **深度指南 (进阶必读):** [Everything Claude Code 深度指南](https://x.com/affaanmustafa/status/2014040193557471352) - **关注我:** [@affaanmustafa](https://x.com/affaanmustafa) -- **zenith.chat:** [zenith.chat](https://zenith.chat) +- **zenith.chat:** [zenith.chat](https://zenith.chat) --- -## 许可证 +## 📄 开源协议 -MIT - 自由使用,按需修改,如果可以请回馈社区。 +MIT - 自由使用、按需修改,如能回馈社区不胜感激。 --- -**如果对你有帮助,请给本仓库点个 Star。阅读两份指南。构建伟大的产品。** +**如果此仓库对你有帮助,请点亮 Star。阅读两篇指南。去构建伟大的产品吧。** diff --git a/README.zh-CN.md b/README.zh-CN.md index a0b5ee9..1499d54 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -7,10 +7,17 @@ ![Go](https://img.shields.io/badge/-Go-00ADD8?logo=go&logoColor=white) ![Markdown](https://img.shields.io/badge/-Markdown-000000?logo=markdown&logoColor=white) -

- English | - 简体中文 -

+--- + +
+ +**🌐 Language / 语言 / 語言** + +[**English**](README.md) | [简体中文](README.zh-CN.md) | [繁體中文](docs/zh-TW/README.md) + +
+ +--- **来自 Anthropic 黑客马拉松获胜者的完整 Claude Code 配置集合。** @@ -52,7 +59,47 @@ --- -## 跨平台支持 +## 🚀 快速开始 + +在 2 分钟内快速上手: + +### 第一步:安装插件 + +```bash +# 添加市场 +/plugin marketplace add affaan-m/everything-claude-code + +# 安装插件 +/plugin install everything-claude-code@everything-claude-code +``` + +### 第二步:安装规则(必需) + +> ⚠️ **重要提示:** Claude Code 插件无法自动分发 `rules`,需要手动安装: + +```bash +# 首先克隆仓库 +git clone https://github.com/affaan-m/everything-claude-code.git + +# 复制规则(应用于所有项目) +cp -r everything-claude-code/rules/* ~/.claude/rules/ +``` + +### 第三步:开始使用 + +```bash +# 尝试一个命令 +/plan "添加用户认证" + +# 查看可用命令 +/plugin list everything-claude-code@everything-claude-code +``` + +✨ **完成!** 你现在可以使用 15+ 代理、30+ 技能和 20+ 命令。 + +--- + +## 🌐 跨平台支持 此插件现在完全支持 **Windows、macOS 和 Linux**。所有钩子和脚本都已用 Node.js 重写,以实现最大的兼容性。 @@ -87,7 +134,7 @@ node scripts/setup-package-manager.js --detect --- -## 里面有什么 +## 📦 里面有什么 这个仓库是一个 **Claude Code 插件** - 直接安装或手动复制组件。 @@ -192,7 +239,7 @@ everything-claude-code/ --- -## 生态系统工具 +## 🛠️ 生态系统工具 ### 技能创建器 @@ -227,7 +274,7 @@ everything-claude-code/ - **直觉集合** - 用于 continuous-learning-v2 - **模式提取** - 从你的提交历史中学习 -### 持续学习 v2 +### 🧠 持续学习 v2 基于直觉的学习系统自动学习你的模式: @@ -242,7 +289,7 @@ everything-claude-code/ --- -## 安装 +## 📥 安装 ### 选项 1:作为插件安装(推荐) @@ -292,7 +339,7 @@ everything-claude-code/ --- -### 选项 2:手动安装 +### 🔧 选项 2:手动安装 如果你希望对安装的内容进行手动控制: @@ -325,7 +372,7 @@ cp -r everything-claude-code/skills/* ~/.claude/skills/ --- -## 关键概念 +## 🎯 关键概念 ### 代理 @@ -383,7 +430,7 @@ model: opus --- -## 运行测试 +## 🧪 运行测试 插件包含一个全面的测试套件: @@ -399,7 +446,7 @@ node tests/hooks/hooks.test.js --- -## 贡献 +## 🤝 贡献 **欢迎并鼓励贡献。** @@ -421,7 +468,7 @@ node tests/hooks/hooks.test.js --- -## 背景 +## 📖 背景 自实验性推出以来,我一直在使用 Claude Code。2025 年 9 月,与 [@DRodriguezFX](https://x.com/DRodriguezFX) 一起使用 Claude Code 构建 [zenith.chat](https://zenith.chat),赢得了 Anthropic x Forum Ventures 黑客马拉松。 @@ -429,7 +476,7 @@ node tests/hooks/hooks.test.js --- -## 重要说明 +## ⚠️ 重要说明 ### 上下文窗口管理 @@ -452,13 +499,13 @@ node tests/hooks/hooks.test.js --- -## Star 历史 +## 🌟 Star 历史 [![Star History Chart](https://api.star-history.com/svg?repos=affaan-m/everything-claude-code&type=Date)](https://star-history.com/#affaan-m/everything-claude-code&Date) --- -## 链接 +## 🔗 链接 - **精简指南(从这里开始):** [The Shorthand Guide to Everything Claude Code](https://x.com/affaanmustafa/status/2012378465664745795) - **详细指南(高级):** [The Longform Guide to Everything Claude Code](https://x.com/affaanmustafa/status/2014040193557471352) @@ -467,7 +514,7 @@ node tests/hooks/hooks.test.js --- -## 许可证 +## 📄 许可证 MIT - 自由使用,根据需要修改,如果可以请回馈。 diff --git a/agents/build-error-resolver.md b/agents/build-error-resolver.md index c175682..a4cf173 100644 --- a/agents/build-error-resolver.md +++ b/agents/build-error-resolver.md @@ -11,12 +11,12 @@ model: opus ## 核心职责 -1. **TypeScript 错误解决** - 修复类型错误、推断问题、泛型约束。 -2. **构建错误修复** - 解决编译失败、模块解析(Module Resolution)问题。 -3. **依赖问题** - 修复导入错误、缺失的包、版本冲突。 -4. **配置错误** - 解决 `tsconfig.json`、webpack、Next.js 配置问题。 -5. **最小差异修改 (Minimal Diffs)** - 尽可能通过最小的改动来修复错误。 -6. **禁止架构更改** - 仅修复错误,不进行重构或重新设计。 +1. **TypeScript 错误解决** - 修复类型错误、推断问题、泛型约束。 +2. **构建错误修复** - 解决编译失败、模块解析(Module Resolution)问题。 +3. **依赖问题** - 修复导入错误、缺失的包、版本冲突。 +4. **配置错误** - 解决 `tsconfig.json`、webpack、Next.js 配置问题。 +5. **最小差异修改 (Minimal Diffs)** - 尽可能通过最小的改动来修复错误。 +6. **禁止架构更改** - 仅修复错误,不进行重构或重新设计。 ## 可用工具 diff --git a/agents/python-reviewer.md b/agents/python-reviewer.md new file mode 100644 index 0000000..8dde17e --- /dev/null +++ b/agents/python-reviewer.md @@ -0,0 +1,469 @@ +--- +name: python-reviewer +description: Expert Python code reviewer specializing in PEP 8 compliance, Pythonic idioms, type hints, security, and performance. Use for all Python code changes. MUST BE USED for Python projects. +tools: ["Read", "Grep", "Glob", "Bash"] +model: opus +--- + +你是一位资深的 Python 代码审查员(Code Reviewer),致力于确保代码符合高标准的 Pythonic 规范及最佳实践。 + +当被调用时: +1. 运行 `git diff -- '*.py'` 以查看最近的 Python 文件变更 +2. 如果可用,运行静态分析工具(ruff、mypy、pylint、black --check) +3. 重点关注修改过的 `.py` 文件 +4. 立即开始审查 + +## 安全检查 (严重/CRITICAL) + +- **SQL 注入 (SQL Injection)**:数据库查询中的字符串拼接 + ```python + # 错误做法 + cursor.execute(f"SELECT * FROM users WHERE id = {user_id}") + # 正确做法 + cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,)) + ``` + +- **命令注入 (Command Injection)**:subprocess/os.system 中未经验证的输入 + ```python + # 错误做法 + os.system(f"curl {url}") + # 正确做法 + subprocess.run(["curl", url], check=True) + ``` + +- **路径穿越 (Path Traversal)**:用户控制的文件路径 + ```python + # 错误做法 + open(os.path.join(base_dir, user_path)) + # 正确做法 + clean_path = os.path.normpath(user_path) + if clean_path.startswith(".."): + raise ValueError("Invalid path") + safe_path = os.path.join(base_dir, clean_path) + ``` + +- **Eval/Exec 滥用**:在 eval/exec 中使用用户输入 +- **Pickle 不安全反序列化**:加载不可信的 pickle 数据 +- **硬编码密钥 (Hardcoded Secrets)**:源码中包含 API 密钥、密码 +- **弱加密**:出于安全目的使用 MD5/SHA1 +- **YAML 不安全加载**:使用不带 Loader 的 yaml.load + +## 错误处理 (严重/CRITICAL) + +- **空 except 语句 (Bare Except Clauses)**:捕获所有异常 + ```python + # 错误做法 + try: + process() + except: + pass + + # 正确做法 + try: + process() + except ValueError as e: + logger.error(f"Invalid value: {e}") + ``` + +- **吞掉异常 (Swallowing Exceptions)**:静默失败 +- **用异常代替流程控制**:将异常用于正常的控制流 +- **缺失 finally**:资源未被清理 + ```python + # 错误做法 + f = open("file.txt") + data = f.read() + # 如果发生异常,文件永远不会关闭 + + # 正确做法 + with open("file.txt") as f: + data = f.read() + # 或者 + f = open("file.txt") + try: + data = f.read() + finally: + f.close() + ``` + +## 类型提示 (高优先级/HIGH) + +- **缺失类型提示 (Type Hints)**:公共函数没有类型标注 + ```python + # 错误做法 + def process_user(user_id): + return get_user(user_id) + + # 正确做法 + from typing import Optional + + def process_user(user_id: str) -> Optional[User]: + return get_user(user_id) + ``` + +- **使用 Any 而非特定类型** + ```python + # 错误做法 + from typing import Any + + def process(data: Any) -> Any: + return data + + # 正确做法 + from typing import TypeVar + + T = TypeVar('T') + + def process(data: T) -> T: + return data + ``` + +- **错误的返回类型**:标注与实际不符 +- **未合理使用 Optional**:可为 None 的参数未标记为 Optional + +## Pythonic 代码 (高优先级/HIGH) + +- **未使用上下文管理器 (Context Managers)**:手动进行资源管理 + ```python + # 错误做法 + f = open("file.txt") + try: + content = f.read() + finally: + f.close() + + # 正确做法 + with open("file.txt") as f: + content = f.read() + ``` + +- **C 风格循环**:未使用推导式(Comprehensions)或迭代器 + ```python + # 错误做法 + result = [] + for item in items: + if item.active: + result.append(item.name) + + # 正确做法 + result = [item.name for item in items if item.active] + ``` + +- **使用 isinstance 检查类型**:而非使用 type() + ```python + # 错误做法 + if type(obj) == str: + process(obj) + + # 正确做法 + if isinstance(obj, str): + process(obj) + ``` + +- **未使用枚举 (Enum) 或存在魔术数字 (Magic Numbers)** + ```python + # 错误做法 + if status == 1: + process() + + # 正确做法 + from enum import Enum + + class Status(Enum): + ACTIVE = 1 + INACTIVE = 2 + + if status == Status.ACTIVE: + process() + ``` + +- **循环中的字符串拼接**:使用 + 构建字符串 + ```python + # 错误做法 + result = "" + for item in items: + result += str(item) + + # 正确做法 + result = "".join(str(item) for item in items) + ``` + +- **可变默认参数 (Mutable Default Arguments)**:经典的 Python 陷阱 + ```python + # 错误做法 + def process(items=[]): + items.append("new") + return items + + # 正确做法 + def process(items=None): + if items is None: + items = [] + items.append("new") + return items + ``` + +## 代码质量 (高优先级/HIGH) + +- **参数过多**:函数参数超过 5 个 + ```python + # 错误做法 + def process_user(name, email, age, address, phone, status): + pass + + # 正确做法 + from dataclasses import dataclass + + @dataclass + class UserData: + name: str + email: str + age: int + address: str + phone: str + status: str + + def process_user(data: UserData): + pass + ``` + +- **过长函数**:函数超过 50 行 +- **嵌套过深**:缩进超过 4 层 +- **上帝类/模块 (God Classes/Modules)**:承担了过多职责 +- **重复代码**:重复的模式 +- **魔术数字 (Magic Numbers)**:未命名的常量 + ```python + # 错误做法 + if len(data) > 512: + compress(data) + + # 正确做法 + MAX_UNCOMPRESSED_SIZE = 512 + + if len(data) > MAX_UNCOMPRESSED_SIZE: + compress(data) + ``` + +## 并发 (高优先级/HIGH) + +- **缺失锁 (Lock)**:共享状态未进行同步 + ```python + # 错误做法 + counter = 0 + + def increment(): + global counter + counter += 1 # 竞态条件 (Race condition)! + + # 正确做法 + import threading + + counter = 0 + lock = threading.Lock() + + def increment(): + global counter + with lock: + counter += 1 + ``` + +- **全局解释器锁 (GIL) 假设**:盲目假设线程安全 +- **Async/Await 滥用**:错误地混合同步和异步代码 + +## 性能 (中优先级/MEDIUM) + +- **N+1 查询**:在循环中进行数据库查询 + ```python + # 错误做法 + for user in users: + orders = get_orders(user.id) # N 次查询! + + # 正确做法 + user_ids = [u.id for u in users] + orders = get_orders_for_users(user_ids) # 1 次查询 + ``` + +- **低效的字符串操作** + ```python + # 错误做法 + text = "hello" + for i in range(1000): + text += " world" # O(n²) + + # 正确做法 + parts = ["hello"] + for i in range(1000): + parts.append(" world") + text = "".join(parts) # O(n) + ``` + +- **布尔上下文中的列表**:使用 len() 而非真值性检查 + ```python + # 错误做法 + if len(items) > 0: + process(items) + + # 正确做法 + if items: + process(items) + ``` + +- **不必要的列表创建**:在不需要时使用 list() + ```python + # 错误做法 + for item in list(dict.keys()): + process(item) + + # 正确做法 + for item in dict: + process(item) + ``` + +## 最佳实践 (中优先级/MEDIUM) + +- **PEP 8 合规性**:代码格式违规 + - 导入顺序(标准库、第三方库、本地库) + - 行宽(Black 默认为 88,PEP 8 为 79) + - 命名规范(函数/变量使用 snake_case,类使用 PascalCase) + - 运算符周围的空格 + +- **文档字符串 (Docstrings)**:缺失或格式不良的文档字符串 + ```python + # 错误做法 + def process(data): + return data.strip() + + # 正确做法 + def process(data: str) -> str: + """从输入字符串中移除首尾空格。 + + Args: + data: 要处理的输入字符串。 + + Returns: + 移除空格后的处理字符串。 + """ + return data.strip() + ``` + +- **日志记录 vs Print**:使用 print() 进行日志记录 + ```python + # 错误做法 + print("Error occurred") + + # 正确做法 + import logging + logger = logging.getLogger(__name__) + logger.error("Error occurred") + ``` + +- **相对导入**:在脚本中使用相对导入 +- **未使用的导入**:死代码 (Dead code) +- **缺失 `if __name__ == "__main__"`**:脚本入口点未加保护 + +## Python 特有的反模式 (Anti-Patterns) + +- **`from module import *`**:命名空间污染 + ```python + # 错误做法 + from os.path import * + + # 正确做法 + from os.path import join, exists + ``` + +- **未使用 `with` 语句**:资源泄露 +- **静默异常**:空的 `except: pass` +- **使用 == 与 None 比较** + ```python + # 错误做法 + if value == None: + process() + + # 正确做法 + if value is None: + process() + ``` + +- **未使用 `isinstance` 进行类型检查**:使用了 type() +- **遮蔽内置变量 (Shadowing Built-ins)**:将变量命名为 `list`、`dict`、`str` 等 + ```python + # 错误做法 + list = [1, 2, 3] # 遮蔽了内置的 list 类型 + + # 正确做法 + items = [1, 2, 3] + ``` + +## 审查输出格式 + +针对每个问题: +```text +[CRITICAL] SQL 注入漏洞 +文件: app/routes/user.py:42 +问题: 用户输入直接插入到了 SQL 查询中 +修复建议: 使用参数化查询 + +query = f"SELECT * FROM users WHERE id = {user_id}" # 错误 +query = "SELECT * FROM users WHERE id = %s" # 正确 +cursor.execute(query, (user_id,)) +``` + +## 诊断命令 + +运行以下检查: +```bash +# 类型检查 +mypy . + +# 代码检查 (Linting) +ruff check . +pylint app/ + +# 格式检查 +black --check . +isort --check-only . + +# 安全扫描 +bandit -r . + +# 依赖项审计 +pip-audit +safety check + +# 测试 +pytest --cov=app --cov-report=term-missing +``` + +## 批准标准 + +- **批准 (Approve)**:无严重(CRITICAL)或高(HIGH)优先级问题 +- **警告 (Warning)**:仅存在中(MEDIUM)优先级问题(可谨慎合并) +- **阻止 (Block)**:发现严重(CRITICAL)或高(HIGH)优先级问题 + +## Python 版本注意事项 + +- 检查 `pyproject.toml` 或 `setup.py` 以确认 Python 版本要求 +- 注意代码是否使用了较新 Python 版本的特性(类型提示 | 3.5+,f-strings 3.6+,walrus 3.8+,match 3.10+) +- 标记已弃用的标准库模块 +- 确保类型提示与最低 Python 版本兼容 + +## 框架特定检查 + +### Django +- **N+1 查询**:使用 `select_related` 和 `prefetch_related` +- **缺失迁移**:模型变更但未生成迁移文件 +- **原生 SQL**:在 ORM 可行的情况下使用了 `raw()` 或 `execute()` +- **事务管理**:多步操作缺失 `atomic()` + +### FastAPI/Flask +- **CORS 配置错误**:跨域限制过于宽松 +- **依赖注入**:正确使用 Depends/injection +- **响应模型**:缺失或错误的响应模型 +- **验证**:使用 Pydantic 模型进行请求验证 + +### 异步 (FastAPI/aiohttp) +- **异步函数中的阻塞调用**:在异步上下文中使用了同步库 +- **缺失 await**:忘记 await 协程 +- **异步生成器**:正确的异步迭代 + +审查时请思考:“这段代码能通过顶级 Python 团队或开源项目的审查吗?” diff --git a/agents/tdd-guide.md b/agents/tdd-guide.md index 5f13406..3c44d1d 100644 --- a/agents/tdd-guide.md +++ b/agents/tdd-guide.md @@ -277,4 +277,4 @@ npm test && npm run lint npm test -- --coverage --ci ``` -**记住**:没有测试就没有代码。测试不是可选的。它们是安全网,能够让你自信地重构、快速开发并确保生产环境的可靠性。 +**记住**:没有测试就没有代码。测试不是可选的。它们是安全网,能够让你自信地重构、快速开发并确保生产环境的可靠性。 \ No newline at end of file diff --git a/commands/evolve.md b/commands/evolve.md index 6f82c12..1f80861 100644 --- a/commands/evolve.md +++ b/commands/evolve.md @@ -1,144 +1,144 @@ --- name: evolve -description: Cluster related instincts into skills, commands, or agents +description: 将相关的直觉(Instincts)聚类为技能(Skills)、命令(Commands)或智能体(Agents) command: true --- -# Evolve Command +# Evolve 命令 -## Implementation +## 实现 (Implementation) -Run the instinct CLI using the plugin root path: +使用插件根路径运行直觉(Instinct)命令行界面(CLI): ```bash python3 "${CLAUDE_PLUGIN_ROOT}/skills/continuous-learning-v2/scripts/instinct-cli.py" evolve [--generate] ``` -Or if `CLAUDE_PLUGIN_ROOT` is not set (manual installation): +或者如果未设置 `CLAUDE_PLUGIN_ROOT`(手动安装): ```bash python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py evolve [--generate] ``` -Analyzes instincts and clusters related ones into higher-level structures: -- **Commands**: When instincts describe user-invoked actions -- **Skills**: When instincts describe auto-triggered behaviors -- **Agents**: When instincts describe complex, multi-step processes +分析直觉(Instincts)并将相关的直觉聚类为更高级的结构: +- **命令(Commands)**:当直觉描述用户调用的操作时 +- **技能(Skills)**:当直觉描述自动触发的行为时 +- **智能体(Agents)**:当直觉描述复杂的、多步骤的流程时 -## Usage +## 用法 (Usage) ``` -/evolve # Analyze all instincts and suggest evolutions -/evolve --domain testing # Only evolve instincts in testing domain -/evolve --dry-run # Show what would be created without creating -/evolve --threshold 5 # Require 5+ related instincts to cluster +/evolve # 分析所有直觉并建议演进方案 +/evolve --domain testing # 仅演进测试领域(testing domain)中的直觉 +/evolve --dry-run # 显示将要创建的内容而不实际创建 +/evolve --threshold 5 # 要求至少有 5 个以上的相关直觉才进行聚类 ``` -## Evolution Rules +## 演进规则 (Evolution Rules) -### → Command (User-Invoked) -When instincts describe actions a user would explicitly request: -- Multiple instincts about "when user asks to..." -- Instincts with triggers like "when creating a new X" -- Instincts that follow a repeatable sequence +### → 命令 (Command)(用户调用) +当直觉描述用户会明确请求的操作时: +- 多个关于“当用户要求...”的直觉 +- 带有“当创建新的 X 时”等触发器的直觉 +- 遵循可重复序列的直觉 -Example: +示例: - `new-table-step1`: "when adding a database table, create migration" - `new-table-step2`: "when adding a database table, update schema" - `new-table-step3`: "when adding a database table, regenerate types" -→ Creates: `/new-table` command +→ 创建:`/new-table` 命令 -### → Skill (Auto-Triggered) -When instincts describe behaviors that should happen automatically: -- Pattern-matching triggers -- Error handling responses -- Code style enforcement +### → 技能 (Skill)(自动触发) +当直觉描述应该自动发生的行为时: +- 模式匹配触发器 +- 错误处理响应 +- 代码风格强制执行 -Example: +示例: - `prefer-functional`: "when writing functions, prefer functional style" - `use-immutable`: "when modifying state, use immutable patterns" - `avoid-classes`: "when designing modules, avoid class-based design" -→ Creates: `functional-patterns` skill +→ 创建:`functional-patterns` 技能(Skill) -### → Agent (Needs Depth/Isolation) -When instincts describe complex, multi-step processes that benefit from isolation: -- Debugging workflows -- Refactoring sequences -- Research tasks +### → 智能体 (Agent)(需要深度/隔离) +当直觉描述复杂的、多步骤的流程,且受益于隔离环境时: +- 调试工作流(Workflow) +- 重构序列 +- 研究任务 -Example: +示例: - `debug-step1`: "when debugging, first check logs" - `debug-step2`: "when debugging, isolate the failing component" - `debug-step3`: "when debugging, create minimal reproduction" - `debug-step4`: "when debugging, verify fix with test" -→ Creates: `debugger` agent +→ 创建:`debugger` 智能体(Agent) -## What to Do +## 操作步骤 (What to Do) -1. Read all instincts from `~/.claude/homunculus/instincts/` -2. Group instincts by: - - Domain similarity - - Trigger pattern overlap - - Action sequence relationship -3. For each cluster of 3+ related instincts: - - Determine evolution type (command/skill/agent) - - Generate the appropriate file - - Save to `~/.claude/homunculus/evolved/{commands,skills,agents}/` -4. Link evolved structure back to source instincts +1. 从 `~/.claude/homunculus/instincts/` 读取所有直觉(Instincts) +2. 按以下维度对直觉进行分组: + - 领域(Domain)相似性 + - 触发模式重合度 + - 操作序列关联性 +3. 对于每个包含 3 个及以上相关直觉的聚类: + - 确定演进类型(命令/技能/智能体) + - 生成相应的文件 + - 保存至 `~/.claude/homunculus/evolved/{commands,skills,agents}/` +4. 将演进后的结构链接回原始直觉 -## Output Format +## 输出格式 (Output Format) ``` -🧬 Evolve Analysis +🧬 演进分析 (Evolve Analysis) ================== -Found 3 clusters ready for evolution: +发现 3 个已准备好演进的聚类: -## Cluster 1: Database Migration Workflow -Instincts: new-table-migration, update-schema, regenerate-types -Type: Command -Confidence: 85% (based on 12 observations) +## 聚类 1:数据库迁移工作流 (Database Migration Workflow) +直觉 (Instincts): new-table-migration, update-schema, regenerate-types +类型: 命令 (Command) +置信度: 85% (基于 12 次观察) -Would create: /new-table command -Files: +将创建: /new-table 命令 +文件: - ~/.claude/homunculus/evolved/commands/new-table.md -## Cluster 2: Functional Code Style -Instincts: prefer-functional, use-immutable, avoid-classes, pure-functions -Type: Skill -Confidence: 78% (based on 8 observations) +## 聚类 2:函数式代码风格 (Functional Code Style) +直觉 (Instincts): prefer-functional, use-immutable, avoid-classes, pure-functions +类型: 技能 (Skill) +置信度: 78% (基于 8 次观察) -Would create: functional-patterns skill -Files: +将创建: functional-patterns 技能 (Skill) +文件: - ~/.claude/homunculus/evolved/skills/functional-patterns.md -## Cluster 3: Debugging Process -Instincts: debug-check-logs, debug-isolate, debug-reproduce, debug-verify -Type: Agent -Confidence: 72% (based on 6 observations) +## 聚类 3:调试流程 (Debugging Process) +直觉 (Instincts): debug-check-logs, debug-isolate, debug-reproduce, debug-verify +类型: 智能体 (Agent) +置信度: 72% (基于 6 次观察) -Would create: debugger agent -Files: +将创建: debugger 智能体 (Agent) +文件: - ~/.claude/homunculus/evolved/agents/debugger.md --- -Run `/evolve --execute` to create these files. +运行 `/evolve --execute` 来创建这些文件。 ``` -## Flags +## 参数标志 (Flags) -- `--execute`: Actually create the evolved structures (default is preview) -- `--dry-run`: Preview without creating -- `--domain `: Only evolve instincts in specified domain -- `--threshold `: Minimum instincts required to form cluster (default: 3) -- `--type `: Only create specified type +- `--execute`: 实际创建演进后的结构(默认为预览) +- `--dry-run`: 预览而不创建 +- `--domain `: 仅演进指定领域(Domain)中的直觉 +- `--threshold `: 形成聚类所需的最小直觉数量(默认值:3) +- `--type `: 仅创建指定类型 -## Generated File Format +## 生成的文件格式 (Generated File Format) -### Command +### 命令 (Command) ```markdown --- name: new-table @@ -150,16 +150,16 @@ evolved_from: - regenerate-types --- -# New Table Command +# New Table 命令 -[Generated content based on clustered instincts] +[基于聚类直觉生成的正文内容] -## Steps +## 步骤 1. ... 2. ... ``` -### Skill +### 技能 (Skill) ```markdown --- name: functional-patterns @@ -170,12 +170,12 @@ evolved_from: - avoid-classes --- -# Functional Patterns Skill +# Functional Patterns 技能 (Skill) -[Generated content based on clustered instincts] +[基于聚类直觉生成的正文内容] ``` -### Agent +### 智能体 (Agent) ```markdown --- name: debugger @@ -187,7 +187,7 @@ evolved_from: - debug-reproduce --- -# Debugger Agent +# Debugger 智能体 (Agent) -[Generated content based on clustered instincts] +[基于聚类直觉生成的正文内容] ``` diff --git a/commands/go-build.md b/commands/go-build.md index 5f90b53..aac2773 100644 --- a/commands/go-build.md +++ b/commands/go-build.md @@ -8,11 +8,11 @@ description: 增量修复 Go 构建错误、go vet 警告和 linter 问题。调 ## 此命令的作用 -1. **运行诊断**:执行 `go build`、`go vet`、`staticcheck` -2. **解析错误**:按文件分组并按严重程度排序 -3. **增量修复**:一次修复一个错误 -4. **验证每次修复**:每次更改后重新运行构建 -5. **报告摘要**:显示已修复的内容和剩余的问题 +1. **运行诊断**:执行 `go build`、`go vet`、`staticcheck` +2. **解析错误**:按文件分组并按严重程度排序 +3. **增量修复**:一次修复一个错误 +4. **验证每次修复**:每次更改后重新运行构建 +5. **报告摘要**:显示已修复的内容和剩余的问题 ## 何时使用 diff --git a/commands/instinct-export.md b/commands/instinct-export.md index a93f4e2..7bfaa9e 100644 --- a/commands/instinct-export.md +++ b/commands/instinct-export.md @@ -1,38 +1,38 @@ --- name: instinct-export -description: Export instincts for sharing with teammates or other projects +description: 导出直觉(Instincts)以便与团队成员或其他项目共享 command: /instinct-export --- -# Instinct Export Command +# 直觉导出(Instinct Export)命令 -Exports instincts to a shareable format. Perfect for: -- Sharing with teammates -- Transferring to a new machine -- Contributing to project conventions +将直觉(Instincts)导出为可共享的格式。非常适用于: +- 与团队成员共享 +- 迁移到新机器 +- 为项目规范(Conventions)贡献内容 -## Usage +## 用法 ``` -/instinct-export # Export all personal instincts -/instinct-export --domain testing # Export only testing instincts -/instinct-export --min-confidence 0.7 # Only export high-confidence instincts +/instinct-export # 导出所有个人直觉 +/instinct-export --domain testing # 仅导出测试相关的直觉 +/instinct-export --min-confidence 0.7 # 仅导出高置信度的直觉 /instinct-export --output team-instincts.yaml ``` -## What to Do +## 执行流程 -1. Read instincts from `~/.claude/homunculus/instincts/personal/` -2. Filter based on flags -3. Strip sensitive information: - - Remove session IDs - - Remove file paths (keep only patterns) - - Remove timestamps older than "last week" -4. Generate export file +1. 从 `~/.claude/homunculus/instincts/personal/` 读取直觉 +2. 根据标志位(Flags)进行过滤 +3. 脱敏处理(剥离敏感信息): + - 移除会话 ID(Session IDs) + - 移除文件路径(仅保留模式匹配符 Pattern) + - 移除早于“上周”的时间戳 +4. 生成导出文件 -## Output Format +## 输出格式 -Creates a YAML file: +创建一个 YAML 文件: ```yaml # Instincts Export @@ -67,25 +67,25 @@ instincts: observations: 6 ``` -## Privacy Considerations +## 隐私考量 -Exports include: -- ✅ Trigger patterns -- ✅ Actions -- ✅ Confidence scores -- ✅ Domains -- ✅ Observation counts +导出内容包括: +- ✅ 触发模式(Trigger patterns) +- ✅ 动作(Actions) +- ✅ 置信度分数(Confidence scores) +- ✅ 域(Domains) +- ✅ 观察次数(Observation counts) -Exports do NOT include: -- ❌ Actual code snippets -- ❌ File paths -- ❌ Session transcripts -- ❌ Personal identifiers +导出内容 **不包括**: +- ❌ 实际代码片段 +- ❌ 文件路径 +- ❌ 会话转录文本 +- ❌ 个人身份标识符 -## Flags +## 标志位(Flags) -- `--domain `: Export only specified domain -- `--min-confidence `: Minimum confidence threshold (default: 0.3) -- `--output `: Output file path (default: instincts-export-YYYYMMDD.yaml) -- `--format `: Output format (default: yaml) -- `--include-evidence`: Include evidence text (default: excluded) +- `--domain `: 仅导出指定的域(Domain) +- `--min-confidence `: 最低置信度阈值(默认值:0.3) +- `--output `: 输出文件路径(默认值:instincts-export-YYYYMMDD.yaml) +- `--format `: 输出格式(默认值:yaml) +- `--include-evidence`: 包含证据文本(默认值:排除) diff --git a/commands/instinct-import.md b/commands/instinct-import.md index 0dea62b..8b1d27a 100644 --- a/commands/instinct-import.md +++ b/commands/instinct-import.md @@ -1,32 +1,32 @@ --- name: instinct-import -description: Import instincts from teammates, Skill Creator, or other sources +description: 从团队成员、技能创建者(Skill Creator)或其他来源导入直觉(Instincts) command: true --- -# Instinct Import Command +# 直觉导入命令(Instinct Import Command) -## Implementation +## 实现 -Run the instinct CLI using the plugin root path: +使用插件根路径运行直觉 CLI: ```bash python3 "${CLAUDE_PLUGIN_ROOT}/skills/continuous-learning-v2/scripts/instinct-cli.py" import [--dry-run] [--force] [--min-confidence 0.7] ``` -Or if `CLAUDE_PLUGIN_ROOT` is not set (manual installation): +如果未设置 `CLAUDE_PLUGIN_ROOT`(手动安装): ```bash python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py import ``` -Import instincts from: -- Teammates' exports -- Skill Creator (repo analysis) -- Community collections -- Previous machine backups +可以从以下来源导入直觉: +- 团队成员的导出文件 +- 技能创建者(Skill Creator)(仓库分析) +- 社区集合 +- 以前的机器备份 -## Usage +## 用法 ``` /instinct-import team-instincts.yaml @@ -34,109 +34,109 @@ Import instincts from: /instinct-import --from-skill-creator acme/webapp ``` -## What to Do +## 核心流程 -1. Fetch the instinct file (local path or URL) -2. Parse and validate the format -3. Check for duplicates with existing instincts -4. Merge or add new instincts -5. Save to `~/.claude/homunculus/instincts/inherited/` +1. 获取直觉文件(本地路径或 URL) +2. 解析并验证格式 +3. 检查与现有直觉是否重复 +4. 合并或添加新直觉 +5. 保存至 `~/.claude/homunculus/instincts/inherited/` -## Import Process +## 导入过程示例 ``` -📥 Importing instincts from: team-instincts.yaml +📥 正在从以下路径导入直觉:team-instincts.yaml ================================================ -Found 12 instincts to import. +发现 12 条待导入的直觉。 -Analyzing conflicts... +正在分析冲突... -## New Instincts (8) -These will be added: - ✓ use-zod-validation (confidence: 0.7) - ✓ prefer-named-exports (confidence: 0.65) - ✓ test-async-functions (confidence: 0.8) +## 新直觉 (8) +以下内容将被添加: + ✓ use-zod-validation (置信度: 0.7) + ✓ prefer-named-exports (置信度: 0.65) + ✓ test-async-functions (置信度: 0.8) ... -## Duplicate Instincts (3) -Already have similar instincts: +## 重复直觉 (3) +已存在类似的直觉: ⚠️ prefer-functional-style - Local: 0.8 confidence, 12 observations - Import: 0.7 confidence - → Keep local (higher confidence) + 本地: 0.8 置信度, 12 次观察 + 导入: 0.7 置信度 + → 保留本地版本(置信度更高) ⚠️ test-first-workflow - Local: 0.75 confidence - Import: 0.9 confidence - → Update to import (higher confidence) + 本地: 0.75 置信度 + 导入: 0.9 置信度 + → 更新为导入版本(置信度更高) -## Conflicting Instincts (1) -These contradict local instincts: +## 冲突直觉 (1) +这些直觉与本地直觉冲突: ❌ use-classes-for-services - Conflicts with: avoid-classes - → Skip (requires manual resolution) + 冲突项:avoid-classes + → 跳过(需要手动解决) --- -Import 8 new, update 1, skip 3? +导入 8 条新直觉,更新 1 条,跳过 3 条? ``` -## Merge Strategies +## 合并策略(Merge Strategies) -### For Duplicates -When importing an instinct that matches an existing one: -- **Higher confidence wins**: Keep the one with higher confidence -- **Merge evidence**: Combine observation counts -- **Update timestamp**: Mark as recently validated +### 处理重复项 +当导入的直觉与现有直觉匹配时: +- **高置信度优先**:保留置信度较高的版本 +- **合并证据**:合并观察计数(observation counts) +- **更新时间戳**:标记为最近已验证 -### For Conflicts -When importing an instinct that contradicts an existing one: -- **Skip by default**: Don't import conflicting instincts -- **Flag for review**: Mark both as needing attention -- **Manual resolution**: User decides which to keep +### 处理冲突 +当导入的直觉与现有直觉相矛盾时: +- **默认跳过**:不导入冲突的直觉 +- **标记待评审**:将两者都标记为需要关注 +- **手动解决**:由用户决定保留哪一个 -## Source Tracking +## 来源追踪 -Imported instincts are marked with: +导入的直觉会带有以下标记: ```yaml source: "inherited" imported_from: "team-instincts.yaml" imported_at: "2025-01-22T10:30:00Z" -original_source: "session-observation" # or "repo-analysis" +original_source: "session-observation" # 或 "repo-analysis" ``` -## Skill Creator Integration +## 技能创建者(Skill Creator)集成 -When importing from Skill Creator: +从技能创建者导入时: ``` /instinct-import --from-skill-creator acme/webapp ``` -This fetches instincts generated from repo analysis: -- Source: `repo-analysis` -- Higher initial confidence (0.7+) -- Linked to source repository +这将获取通过仓库分析生成的直觉: +- 来源:`repo-analysis` +- 初始置信度较高 (0.7+) +- 链接到源仓库 -## Flags +## 参数选项(Flags) -- `--dry-run`: Preview without importing -- `--force`: Import even if conflicts exist -- `--merge-strategy `: How to handle duplicates -- `--from-skill-creator `: Import from Skill Creator analysis -- `--min-confidence `: Only import instincts above threshold +- `--dry-run`:预览而不执行导入 +- `--force`:即使存在冲突也执行导入 +- `--merge-strategy `:如何处理重复项 +- `--from-skill-creator `:从技能创建者分析中导入 +- `--min-confidence `:仅导入高于阈值的直觉 -## Output +## 输出 -After import: +导入完成后: ``` -✅ Import complete! +✅ 导入完成! -Added: 8 instincts -Updated: 1 instinct -Skipped: 3 instincts (2 duplicates, 1 conflict) +添加:8 条直觉 +更新:1 条直觉 +跳过:3 条直觉 (2 条重复,1 条冲突) -New instincts saved to: ~/.claude/homunculus/instincts/inherited/ +新直觉已保存至:~/.claude/homunculus/instincts/inherited/ -Run /instinct-status to see all instincts. +运行 /instinct-status 以查看所有直觉。 ``` diff --git a/commands/instinct-status.md b/commands/instinct-status.md index 346ed47..2a84e36 100644 --- a/commands/instinct-status.md +++ b/commands/instinct-status.md @@ -1,28 +1,28 @@ --- name: instinct-status -description: Show all learned instincts with their confidence levels +description: 显示所有已学习的本能及其置信度 command: true --- -# Instinct Status Command +# 本能状态查询命令(Instinct Status Command) -Shows all learned instincts with their confidence scores, grouped by domain. +显示所有已学习的本能(Instincts)及其置信度分数,并按领域(Domain)进行分组。 -## Implementation +## 实现方式 -Run the instinct CLI using the plugin root path: +使用插件根路径运行本能 CLI: ```bash python3 "${CLAUDE_PLUGIN_ROOT}/skills/continuous-learning-v2/scripts/instinct-cli.py" status ``` -Or if `CLAUDE_PLUGIN_ROOT` is not set (manual installation), use: +如果未设置 `CLAUDE_PLUGIN_ROOT`(手动安装),请使用: ```bash python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py status ``` -## Usage +## 用法 ``` /instinct-status @@ -30,57 +30,57 @@ python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py status /instinct-status --low-confidence ``` -## What to Do +## 执行逻辑 -1. Read all instinct files from `~/.claude/homunculus/instincts/personal/` -2. Read inherited instincts from `~/.claude/homunculus/instincts/inherited/` -3. Display them grouped by domain with confidence bars +1. 从 `~/.claude/homunculus/instincts/personal/` 读取所有个人本能文件。 +2. 从 `~/.claude/homunculus/instincts/inherited/` 读取继承的本能。 +3. 按领域分组显示,并附带置信度进度条。 -## Output Format +## 输出格式 ``` -📊 Instinct Status +📊 本能状态 (Instinct Status) ================== -## Code Style (4 instincts) +## 代码风格 (Code Style) (4 条本能) ### prefer-functional-style -Trigger: when writing new functions -Action: Use functional patterns over classes -Confidence: ████████░░ 80% -Source: session-observation | Last updated: 2025-01-22 +触发器 (Trigger):编写新函数时 +动作 (Action):优先使用函数式模式而非类 +置信度 (Confidence):████████░░ 80% +来源 (Source):会话观察 (session-observation) | 最近更新:2025-01-22 ### use-path-aliases -Trigger: when importing modules -Action: Use @/ path aliases instead of relative imports -Confidence: ██████░░░░ 60% -Source: repo-analysis (github.com/acme/webapp) +触发器 (Trigger):导入模块时 +动作 (Action):使用 @/ 路径别名而非相对导入 +置信度 (Confidence):██████░░░░ 60% +来源 (Source):仓库分析 (repo-analysis) (github.com/acme/webapp) -## Testing (2 instincts) +## 测试 (Testing) (2 条本能) ### test-first-workflow -Trigger: when adding new functionality -Action: Write test first, then implementation -Confidence: █████████░ 90% -Source: session-observation +触发器 (Trigger):添加新功能时 +动作 (Action):先写测试,再写实现 +置信度 (Confidence):█████████░ 90% +来源 (Source):会话观察 (session-observation) -## Workflow (3 instincts) +## 工作流 (Workflow) (3 条本能) ### grep-before-edit -Trigger: when modifying code -Action: Search with Grep, confirm with Read, then Edit -Confidence: ███████░░░ 70% -Source: session-observation +触发器 (Trigger):修改代码时 +动作 (Action):先用 Grep 搜索,再用 Read 确认,最后 Edit 编辑 +置信度 (Confidence):███████░░░ 70% +来源 (Source):会话观察 (session-observation) --- -Total: 9 instincts (4 personal, 5 inherited) -Observer: Running (last analysis: 5 min ago) +总计:9 条本能 (4 条个人, 5 条继承) +观察器 (Observer):运行中 (最近分析:5 分钟前) ``` -## Flags +## 参数 (Flags) -- `--domain `: Filter by domain (code-style, testing, git, etc.) -- `--low-confidence`: Show only instincts with confidence < 0.5 -- `--high-confidence`: Show only instincts with confidence >= 0.7 -- `--source `: Filter by source (session-observation, repo-analysis, inherited) -- `--json`: Output as JSON for programmatic use +- `--domain `: 按领域筛选(如 code-style, testing, git 等) +- `--low-confidence`: 仅显示置信度 < 0.5 的本能 +- `--high-confidence`: 仅显示置信度 >= 0.7 的本能 +- `--source `: 按来源筛选(session-observation, repo-analysis, inherited) +- `--json`: 以 JSON 格式输出,便于程序化调用 diff --git a/commands/python-review.md b/commands/python-review.md new file mode 100644 index 0000000..3a2b9a7 --- /dev/null +++ b/commands/python-review.md @@ -0,0 +1,297 @@ +--- +description: 针对 PEP 8 标准、类型提示、安全性及 Pythonic 惯用写法的 Python 代码全面审查。调用 python-reviewer 智能体(Agent)。 +--- + +# Python 代码审查 (Python Code Review) + +此命令调用 **python-reviewer** 智能体(Agent),进行全面的 Python 专项代码审查。 + +## 此命令的作用 + +1. **识别 Python 变更**:通过 `git diff` 查找修改过的 `.py` 文件 +2. **运行静态分析**:执行 `ruff`、`mypy`、`pylint`、`black --check` +3. **安全扫描**:检查 SQL 注入、命令注入、不安全的反序列化 +4. **类型安全审查**:分析类型提示(Type Hints)和 mypy 错误 +5. **Pythonic 代码检查**:验证代码是否符合 PEP 8 和 Python 最佳实践 +6. **生成报告**:按严重程度(Severity)对问题进行分类 + +## 适用场景 + +在以下情况下使用 `/python-review`: +- 编写或修改 Python 代码后 +- 提交 Python 变更前 +- 审查包含 Python 代码的拉取请求(Pull Requests) +- 接入新的 Python 代码库时 +- 学习 Pythonic 模式和惯用法时 + +## 审查类别 + +### 严重 (CRITICAL) (必须修复) +- SQL/命令注入漏洞 +- 不安全的 eval/exec 使用 +- Pickle 不安全的反序列化 +- 硬编码凭据 +- YAML 不安全的加载 (unsafe load) +- 隐藏错误的裸 except 子句 + +### 高 (HIGH) (应该修复) +- 公共函数缺失类型提示 +- 可变默认参数 (Mutable default arguments) +- 静默吞掉异常 +- 未对资源使用上下文管理器 (Context Managers) +- 使用 C 风格循环而非推导式 (Comprehensions) +- 使用 type() 而非 isinstance() +- 无锁状态下的竞态条件 + +### 中 (MEDIUM) (建议考虑) +- 违反 PEP 8 格式规范 +- 公共函数缺失文档字符串 (Docstrings) +- 使用 print 语句而非日志 (Logging) +- 低效的字符串操作 +- 无命名常量的魔术数字 (Magic numbers) +- 未使用 f-strings 进行格式化 +- 不必要的列表创建 + +## 运行的自动化检查 + +```bash +# 类型检查 +mypy . + +# 代码检查与格式化 +ruff check . +black --check . +isort --check-only . + +# 安全扫描 +bandit -r . + +# 依赖审计 +pip-audit +safety check + +# 测试 +pytest --cov=app --cov-report=term-missing +``` + +## 示例用法 + +```text +User: /python-review + +Agent: +# Python 代码审查报告 + +## 已审查文件 +- app/routes/user.py (已修改) +- app/services/auth.py (已修改) + +## 静态分析结果 +✓ ruff: 无问题 +✓ mypy: 无错误 +⚠️ black: 2 个文件需要重新格式化 +✓ bandit: 无安全问题 + +## 发现的问题 + +[CRITICAL] SQL 注入漏洞 +文件: app/routes/user.py:42 +问题: 用户输入直接插入到 SQL 查询中 +```python +query = f"SELECT * FROM users WHERE id = {user_id}" # 不良做法 +``` +修复: 使用参数化查询 +```python +query = "SELECT * FROM users WHERE id = %s" # 推荐做法 +cursor.execute(query, (user_id,)) +``` + +[HIGH] 可变默认参数 +文件: app/services/auth.py:18 +问题: 可变默认参数会导致状态共享 +```python +def process_items(items=[]): # 不良做法 + items.append("new") + return items +``` +修复: 使用 None 作为默认值 +```python +def process_items(items=None): # 推荐做法 + if items is None: + items = [] + items.append("new") + return items +``` + +[MEDIUM] 缺失类型提示 +文件: app/services/auth.py:25 +问题: 公共函数没有类型注解 +```python +def get_user(user_id): # 不良做法 + return db.find(user_id) +``` +修复: 添加类型提示 +```python +def get_user(user_id: str) -> Optional[User]: # 推荐做法 + return db.find(user_id) +``` + +[MEDIUM] 未使用上下文管理器 +文件: app/routes/user.py:55 +问题: 异常发生时文件未关闭 +```python +f = open("config.json") # 不良做法 +data = f.read() +f.close() +``` +修复: 使用上下文管理器 +```python +with open("config.json") as f: # 推荐做法 + data = f.read() +``` + +## 摘要 +- 严重 (CRITICAL): 1 +- 高 (HIGH): 1 +- 中 (MEDIUM): 2 + +建议: ❌ 在修复严重问题前阻止合并 + +## 需要格式化 +运行: `black app/routes/user.py app/services/auth.py` +``` + +## 批准标准 + +| 状态 | 条件 | +|--------|-----------| +| ✅ 批准 (Approve) | 无“严重”或“高”级别问题 | +| ⚠️ 警告 (Warning) | 仅存在“中”级别问题(谨慎合并) | +| ❌ 阻止 (Block) | 发现“严重”或“高”级别问题 | + +## 与其他命令的集成 + +- 先使用 `/python-test` 确保测试通过 +- 使用 `/code-review` 处理非 Python 专项的关注点 +- 在提交(commit)前使用 `/python-review` +- 如果静态分析工具报错,使用 `/build-fix` + +## 框架专项审查 + +### Django 项目 +审查者会检查: +- N+1 查询问题(使用 `select_related` 和 `prefetch_related`) +- 模型变更缺失迁移文件 +- 在 ORM 可用的情况下使用原生 SQL +- 多步操作缺失 `transaction.atomic()` + +### FastAPI 项目 +审查者会检查: +- CORS 配置错误 +- 用于请求校验的 Pydantic 模型 +- 响应模型的正确性 +- 恰当的 async/await 使用 +- 依赖注入模式 + +### Flask 项目 +审查者会检查: +- 上下文管理(应用上下文、请求上下文) +- 恰当的错误处理 +- 蓝图 (Blueprint) 组织结构 +- 配置管理 + +## 相关 + +- 智能体 (Agent): `agents/python-reviewer.md` +- 技能 (Skills): `skills/python-patterns/`, `skills/python-testing/` + +## 常见修复方案 + +### 添加类型提示 +```python +# 修复前 +def calculate(x, y): + return x + y + +# 修复后 +from typing import Union + +def calculate(x: Union[int, float], y: Union[int, float]) -> Union[int, float]: + return x + y +``` + +### 使用上下文管理器 +```python +# 修复前 +f = open("file.txt") +data = f.read() +f.close() + +# 修复后 +with open("file.txt") as f: + data = f.read() +``` + +### 使用列表推导式 +```python +# 修复前 +result = [] +for item in items: + if item.active: + result.append(item.name) + +# 修复后 +result = [item.name for item in items if item.active] +``` + +### 修复可变默认参数 +```python +# 修复前 +def append(value, items=[]): + items.append(value) + return items + +# 修复后 +def append(value, items=None): + if items is None: + items = [] + items.append(value) + return items +``` + +### 使用 f-strings (Python 3.6+) +```python +# 修复前 +name = "Alice" +greeting = "Hello, " + name + "!" +greeting2 = "Hello, {}".format(name) + +# 修复后 +greeting = f"Hello, {name}!" +``` + +### 修复循环中的字符串拼接 +```python +# 修复前 +result = "" +for item in items: + result += str(item) + +# 修复后 +result = "".join(str(item) for item in items) +``` + +## Python 版本兼容性 + +审查者会提示代码何时使用了较新 Python 版本的特性: + +| 特性 | 最低 Python 版本 | +|---------|----------------| +| 类型提示 (Type hints) | 3.5+ | +| f-strings | 3.6+ | +| 海象运算符 (Walrus operator `:=`) | 3.8+ | +| 仅限位置参数 (Position-only parameters) | 3.8+ | +| 匹配语句 (Match statements) | 3.10+ | +| 类型联合 (Type unions `x | None`) | 3.10+ | + +请确保项目的 `pyproject.toml` 或 `setup.py` 指定了正确的最低 Python 版本。 diff --git a/commands/skill-create.md b/commands/skill-create.md index dcf1df7..3227180 100644 --- a/commands/skill-create.md +++ b/commands/skill-create.md @@ -1,59 +1,59 @@ --- name: skill-create -description: Analyze local git history to extract coding patterns and generate SKILL.md files. Local version of the Skill Creator GitHub App. +description: 分析本地 Git 历史以提取编码模式并生成 SKILL.md 文件。Skill Creator GitHub App 的本地版本。 allowed_tools: ["Bash", "Read", "Write", "Grep", "Glob"] --- -# /skill-create - Local Skill Generation +# /skill-create - 本地技能生成(Local Skill Generation) -Analyze your repository's git history to extract coding patterns and generate SKILL.md files that teach Claude your team's practices. +分析你仓库的 Git 历史记录以提取编码模式,并生成 SKILL.md 文件,以便让 Claude 学习你团队的工程实践。 -## Usage +## 用法(Usage) ```bash -/skill-create # Analyze current repo -/skill-create --commits 100 # Analyze last 100 commits -/skill-create --output ./skills # Custom output directory -/skill-create --instincts # Also generate instincts for continuous-learning-v2 +/skill-create # 分析当前仓库 +/skill-create --commits 100 # 分析最近 100 条提交 +/skill-create --output ./skills # 指定自定义输出目录 +/skill-create --instincts # 同时为 continuous-learning-v2 生成直觉(instincts) ``` -## What It Does +## 功能说明(What It Does) -1. **Parses Git History** - Analyzes commits, file changes, and patterns -2. **Detects Patterns** - Identifies recurring workflows and conventions -3. **Generates SKILL.md** - Creates valid Claude Code skill files -4. **Optionally Creates Instincts** - For the continuous-learning-v2 system +1. **解析 Git 历史** - 分析提交(commits)、文件变更和模式。 +2. **检测模式** - 识别循环出现的工作流(Workflow)和约定。 +3. **生成 SKILL.md** - 创建有效的 Claude Code 技能(Skill)文件。 +4. **可选生成直觉(Instincts)** - 用于 continuous-learning-v2 系统。 -## Analysis Steps +## 分析步骤(Analysis Steps) -### Step 1: Gather Git Data +### 第 1 步:收集 Git 数据 ```bash -# Get recent commits with file changes +# 获取带有文件变更的近期提交 git log --oneline -n ${COMMITS:-200} --name-only --pretty=format:"%H|%s|%ad" --date=short -# Get commit frequency by file +# 获取按文件统计的提交频率 git log --oneline -n 200 --name-only | grep -v "^$" | grep -v "^[a-f0-9]" | sort | uniq -c | sort -rn | head -20 -# Get commit message patterns +# 获取提交信息模式 git log --oneline -n 200 | cut -d' ' -f2- | head -50 ``` -### Step 2: Detect Patterns +### 第 2 步:检测模式 -Look for these pattern types: +寻找以下模式类型: -| Pattern | Detection Method | +| 模式 (Pattern) | 检测方法 (Detection Method) | |---------|-----------------| -| **Commit conventions** | Regex on commit messages (feat:, fix:, chore:) | -| **File co-changes** | Files that always change together | -| **Workflow sequences** | Repeated file change patterns | -| **Architecture** | Folder structure and naming conventions | -| **Testing patterns** | Test file locations, naming, coverage | +| **提交规范 (Commit conventions)** | 对提交信息使用正则匹配 (feat:, fix:, chore:) | +| **文件关联变更 (File co-changes)** | 总是同时发生变化的文件 | +| **工作流序列 (Workflow sequences)** | 重复出现的文件变更模式 | +| **架构 (Architecture)** | 文件夹结构和命名规范 | +| **测试模式 (Testing patterns)** | 测试文件位置、命名、覆盖率 | -### Step 3: Generate SKILL.md +### 第 3 步:生成 SKILL.md -Output format: +输出格式: ```markdown --- @@ -64,24 +64,24 @@ source: local-git-analysis analyzed_commits: {count} --- -# {Repo Name} Patterns +# {Repo Name} 模式 -## Commit Conventions -{detected commit message patterns} +## 提交规范 +{检测到的提交信息模式} -## Code Architecture -{detected folder structure and organization} +## 代码架构 +{检测到的文件夹结构和组织方式} -## Workflows -{detected repeating file change patterns} +## 工作流 +{检测到的重复文件变更模式} -## Testing Patterns -{detected test conventions} +## 测试模式 +{检测到的测试约定} ``` -### Step 4: Generate Instincts (if --instincts) +### 第 4 步:生成直觉 (如果使用了 --instincts) -For continuous-learning-v2 integration: +用于 continuous-learning-v2 集成: ```yaml --- @@ -92,19 +92,19 @@ domain: git source: local-repo-analysis --- -# Use Conventional Commits +# 使用约定式提交 (Conventional Commits) -## Action -Prefix commits with: feat:, fix:, chore:, docs:, test:, refactor: +## 操作 (Action) +在提交信息前添加前缀:feat:, fix:, chore:, docs:, test:, refactor: -## Evidence -- Analyzed {n} commits -- {percentage}% follow conventional commit format +## 证据 (Evidence) +- 已分析 {n} 条提交 +- {percentage}% 遵循约定式提交格式 ``` -## Example Output +## 输出示例 -Running `/skill-create` on a TypeScript project might produce: +在 TypeScript 项目上运行 `/skill-create` 可能会产生: ```markdown --- @@ -115,60 +115,60 @@ source: local-git-analysis analyzed_commits: 150 --- -# My App Patterns +# My App 模式 -## Commit Conventions +## 提交规范 (Commit Conventions) -This project uses **conventional commits**: -- `feat:` - New features -- `fix:` - Bug fixes -- `chore:` - Maintenance tasks -- `docs:` - Documentation updates +该项目使用 **约定式提交 (conventional commits)**: +- `feat:` - 新功能 +- `fix:` - 错误修复 +- `chore:` - 维护任务 +- `docs:` - 文档更新 -## Code Architecture +## 代码架构 (Code Architecture) ``` src/ -├── components/ # React components (PascalCase.tsx) -├── hooks/ # Custom hooks (use*.ts) -├── utils/ # Utility functions -├── types/ # TypeScript type definitions -└── services/ # API and external services +├── components/ # React 组件 (PascalCase.tsx) +├── hooks/ # 自定义 Hooks (use*.ts) +├── utils/ # 工具函数 +├── types/ # TypeScript 类型定义 +└── services/ # API 和外部服务 ``` -## Workflows +## 工作流 (Workflows) -### Adding a New Component -1. Create `src/components/ComponentName.tsx` -2. Add tests in `src/components/__tests__/ComponentName.test.tsx` -3. Export from `src/components/index.ts` +### 添加新组件 +1. 创建 `src/components/ComponentName.tsx` +2. 在 `src/components/__tests__/ComponentName.test.tsx` 中添加测试 +3. 从 `src/components/index.ts` 导出 -### Database Migration -1. Modify `src/db/schema.ts` -2. Run `pnpm db:generate` -3. Run `pnpm db:migrate` +### 数据库迁移 +1. 修改 `src/db/schema.ts` +2. 运行 `pnpm db:generate` +3. 运行 `pnpm db:migrate` -## Testing Patterns +## 测试模式 (Testing patterns) -- Test files: `__tests__/` directories or `.test.ts` suffix -- Coverage target: 80%+ -- Framework: Vitest +- 测试文件:`__tests__/` 目录或 `.test.ts` 后缀 +- 覆盖率目标:80%+ +- 框架:Vitest ``` -## GitHub App Integration +## GitHub App 集成 -For advanced features (10k+ commits, team sharing, auto-PRs), use the [Skill Creator GitHub App](https://github.com/apps/skill-creator): +对于高级功能(1万+ 提交、团队共享、自动 PR),请使用 [Skill Creator GitHub App](https://github.com/apps/skill-creator): -- Install: [github.com/apps/skill-creator](https://github.com/apps/skill-creator) -- Comment `/skill-creator analyze` on any issue -- Receives PR with generated skills +- 安装:[github.com/apps/skill-creator](https://github.com/apps/skill-creator) +- 在任何 Issue 上评论 `/skill-creator analyze` +- 接收包含生成的技能的 PR -## Related Commands +## 相关命令 -- `/instinct-import` - Import generated instincts -- `/instinct-status` - View learned instincts -- `/evolve` - Cluster instincts into skills/agents +- `/instinct-import` - 导入生成的直觉 +- `/instinct-status` - 查看已学习的直觉 +- `/evolve` - 将直觉聚类为技能/智能体 --- -*Part of [Everything Claude Code](https://github.com/affaan-m/everything-claude-code)* +*属于 [Everything Claude Code](https://github.com/affaan-m/everything-claude-code) 的一部分* diff --git a/commands/tdd.md b/commands/tdd.md index db9f10e..9f3d037 100644 --- a/commands/tdd.md +++ b/commands/tdd.md @@ -8,11 +8,11 @@ description: Enforce test-driven development workflow. Scaffold interfaces, gene ## 此命令的作用 -1. **搭建接口(Scaffold Interfaces)** - 首先定义类型/接口 -2. **先生成测试** - 编写失败的测试(红/RED) -3. **编写最小化实现代码** - 只编写刚好能通过测试的代码(绿/GREEN) -4. **重构(Refactor)** - 在保持测试通过的前提下优化代码(重构/REFACTOR) -5. **验证覆盖率** - 确保测试覆盖率达到 80% 以上 +1. **搭建接口(Scaffold Interfaces)** - 首先定义类型/接口 +2. **先生成测试** - 编写失败的测试(红/RED) +3. **编写最小化实现代码** - 只编写刚好能通过测试的代码(绿/GREEN) +4. **重构(Refactor)** - 在保持测试通过的前提下优化代码(重构/REFACTOR) +5. **验证覆盖率** - 确保测试覆盖率达到 80% 以上 ## 适用场景 @@ -27,13 +27,13 @@ description: Enforce test-driven development workflow. Scaffold interfaces, gene tdd-guide 智能体将: -1. 为输入/输出**定义接口** -2. **编写会失败(FAIL)的测试**(因为代码尚未存在) -3. **运行测试**并验证它们因预期的原因而失败 -4. **编写最小化实现**以使测试通过 -5. **运行测试**并验证它们通过 -6. 在保持测试通过的前提下**重构**代码 -7. **检查覆盖率**,如果低于 80% 则添加更多测试 +1. 为输入/输出**定义接口** +2. **编写会失败(FAIL)的测试**(因为代码尚未存在) +3. **运行测试**并验证它们因预期的原因而失败 +4. **编写最小化实现**以使测试通过 +5. **运行测试**并验证它们通过 +6. 在保持测试通过的前提下**重构**代码 +7. **检查覆盖率**,如果低于 80% 则添加更多测试 ## TDD 循环 diff --git a/docs/zh-TW/README.md b/docs/zh-TW/README.md index c4cea37..ad0adb6 100644 --- a/docs/zh-TW/README.md +++ b/docs/zh-TW/README.md @@ -7,21 +7,33 @@ ![Go](https://img.shields.io/badge/-Go-00ADD8?logo=go&logoColor=white) ![Markdown](https://img.shields.io/badge/-Markdown-000000?logo=markdown&logoColor=white) -**来自 Anthropic 黑客松冠军的完整 Claude Code 配置集。** +--- -经过 10 个月以上密集日常使用、打造真实产品所淬炼出的生产就绪智能体(Agents)、技能(Skills)、钩子(Hooks)、指令(Commands)、规则(Rules)和 MCP 配置。 +
+ +**🌐 Language / 语言 / 語言** + +[**English**](../../README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](README.md) + +
+ +--- + +**來自 Anthropic 黑客松冠軍的完整 Claude Code 設定集合。** + +經過 10 個月以上密集日常使用、打造真實產品所淬煉出的生產就緒代理程式、技能、鉤子、指令、規則和 MCP 設定。 --- ## 指南 -本仓库仅包含原始代码。指南会解释所有内容。 +本儲存庫僅包含原始程式碼。指南會解釋所有內容。 - - + +
-Everything Claude Code 简明指南 +Everything Claude Code 簡明指南 @@ -31,50 +43,90 @@
简明指南
配置、基础、理念。请先阅读此指南。
完整指南
Token 优化、记忆持久化、评估、并行处理。
簡明指南
設定、基礎、理念。請先閱讀此指南。
完整指南
權杖最佳化、記憶持久化、評估、平行處理。
-| 主题 | 学习内容 | +| 主題 | 學習內容 | |------|----------| -| Token 优化 | 模型选择、系统提示精简、后台进程 | -| 记忆持久化 | 自动跨会话(Session)保存/加载上下文的钩子(Hooks) | -| 持续学习 | 从会话中自动提取模式并转化为可重用技能(Skills) | -| 验证循环 | 检查点 vs 持续评估、评分器类型、pass@k 指标 | -| 并行处理 | Git worktrees、串联方法、何时扩展实例 | -| 子智能体协调 | 上下文问题、渐进式检索模式 | +| 權杖最佳化 | 模型選擇、系統提示精簡、背景程序 | +| 記憶持久化 | 自動跨工作階段儲存/載入上下文的鉤子 | +| 持續學習 | 從工作階段自動擷取模式並轉化為可重用技能 | +| 驗證迴圈 | 檢查點 vs 持續評估、評分器類型、pass@k 指標 | +| 平行處理 | Git worktrees、串聯方法、何時擴展實例 | +| 子代理程式協調 | 上下文問題、漸進式檢索模式 | --- -## 跨平台支持 +## 🚀 快速開始 -此插件现已完整支持 **Windows、macOS 和 Linux**。所有钩子和脚本已使用 Node.js 重写以获得最佳兼容性。 +在 2 分鐘內快速上手: -### 包管理器检测 - -插件会自动检测您偏好的包管理器(npm、pnpm、yarn 或 bun),优先级如下: - -1. **环境变量**:`CLAUDE_PACKAGE_MANAGER` -2. **项目配置**:`.claude/package-manager.json` -3. **package.json**:`packageManager` 字段 -4. **锁文件**:从 package-lock.json、yarn.lock、pnpm-lock.yaml 或 bun.lockb 检测 -5. **全局配置**:`~/.claude/package-manager.json` -6. **备选方案**:第一个可用的包管理器 - -设置您偏好的包管理器: +### 第一步:安裝外掛程式 ```bash -# 通过环境变量 +# 新增市集 +/plugin marketplace add affaan-m/everything-claude-code + +# 安裝外掛程式 +/plugin install everything-claude-code@everything-claude-code +``` + +### 第二步:安裝規則(必需) + +> ⚠️ **重要提示:** Claude Code 外掛程式無法自動分發 `rules`,需要手動安裝: + +```bash +# 首先複製儲存庫 +git clone https://github.com/affaan-m/everything-claude-code.git + +# 複製規則(應用於所有專案) +cp -r everything-claude-code/rules/* ~/.claude/rules/ +``` + +### 第三步:開始使用 + +```bash +# 嘗試一個指令 +/plan "新增使用者認證" + +# 查看可用指令 +/plugin list everything-claude-code@everything-claude-code +``` + +✨ **完成!** 您現在使用 15+ 代理程式、30+ 技能和 20+ 指令。 + +--- + +## 🌐 跨平台支援 + +此外掛程式現已完整支援 **Windows、macOS 和 Linux**。所有鉤子和腳本已使用 Node.js 重寫以獲得最佳相容性。 + +### 套件管理器偵測 + +外掛程式會自動偵測您偏好的套件管理器(npm、pnpm、yarn 或 bun),優先順序如下: + +1. **環境變數**:`CLAUDE_PACKAGE_MANAGER` +2. **專案設定**:`.claude/package-manager.json` +3. **package.json**:`packageManager` 欄位 +4. **鎖定檔案**:從 package-lock.json、yarn.lock、pnpm-lock.yaml 或 bun.lockb 偵測 +5. **全域設定**:`~/.claude/package-manager.json` +6. **備援方案**:第一個可用的套件管理器 + +設定您偏好的套件管理器: + +```bash +# 透過環境變數 export CLAUDE_PACKAGE_MANAGER=pnpm -# 通过全局配置 +# 透過全域設定 node scripts/setup-package-manager.js --global pnpm -# 通过项目配置 +# 透過專案設定 node scripts/setup-package-manager.js --project bun -# 检测当前配置 +# 偵測目前設定 node scripts/setup-package-manager.js --detect ``` @@ -82,143 +134,143 @@ node scripts/setup-package-manager.js --detect --- -## 内容概览 +## 📦 內容概覽 -本仓库是一个 **Claude Code 插件** - 可直接安装或手动复制组件。 +本儲存庫是一個 **Claude Code 外掛程式** - 可直接安裝或手動複製元件。 ``` everything-claude-code/ -|-- .claude-plugin/ # 插件和市场清单 -| |-- plugin.json # 插件元数据和组件路径 -| |-- marketplace.json # 用于 /plugin marketplace add 的市场目录 +|-- .claude-plugin/ # 外掛程式和市集清單 +| |-- plugin.json # 外掛程式中繼資料和元件路徑 +| |-- marketplace.json # 用於 /plugin marketplace add 的市集目錄 | -|-- agents/ # 用于委派任务的专门子智能体(Agents) -| |-- planner.md # 功能实现规划 -| |-- architect.md # 系统设计决策 -| |-- tdd-guide.md # 测试驱动开发 -| |-- code-reviewer.md # 质量与安全审查 -| |-- security-reviewer.md # 漏洞分析 +|-- agents/ # 用於委派任務的專門子代理程式 +| |-- planner.md # 功能實作規劃 +| |-- architect.md # 系統設計決策 +| |-- tdd-guide.md # 測試驅動開發 +| |-- code-reviewer.md # 品質與安全審查 +| |-- security-reviewer.md # 弱點分析 | |-- build-error-resolver.md -| |-- e2e-runner.md # Playwright E2E 测试 -| |-- refactor-cleaner.md # 无用代码清理 -| |-- doc-updater.md # 文档同步 -| |-- go-reviewer.md # Go 代码审查(新增) -| |-- go-build-resolver.md # Go 构建错误解决(新增) +| |-- e2e-runner.md # Playwright E2E 測試 +| |-- refactor-cleaner.md # 無用程式碼清理 +| |-- doc-updater.md # 文件同步 +| |-- go-reviewer.md # Go 程式碼審查(新增) +| |-- go-build-resolver.md # Go 建置錯誤解決(新增) | -|-- skills/ # 工作流(Workflow)定义和领域知识 -| |-- coding-standards/ # 编程语言最佳实践 -| |-- backend-patterns/ # API、数据库、缓存模式 +|-- skills/ # 工作流程定義和領域知識 +| |-- coding-standards/ # 程式語言最佳實務 +| |-- backend-patterns/ # API、資料庫、快取模式 | |-- frontend-patterns/ # React、Next.js 模式 -| |-- continuous-learning/ # 从会话中自动提取模式(完整指南) -| |-- continuous-learning-v2/ # 基于本能的学习与信心评分 -| |-- iterative-retrieval/ # 子代理的渐进式上下文精炼 -| |-- strategic-compact/ # 手动压缩建议(完整指南) -| |-- tdd-workflow/ # TDD 方法论 -| |-- security-review/ # 安全性检查清单 -| |-- eval-harness/ # 验证循环评估(完整指南) -| |-- verification-loop/ # 持续验证(完整指南) -| |-- golang-patterns/ # Go 惯用法和最佳实践(新增) -| |-- golang-testing/ # Go 测试模式、TDD、基准测试(新增) +| |-- continuous-learning/ # 從工作階段自動擷取模式(完整指南) +| |-- continuous-learning-v2/ # 基於本能的學習與信心評分 +| |-- iterative-retrieval/ # 子代理程式的漸進式上下文精煉 +| |-- strategic-compact/ # 手動壓縮建議(完整指南) +| |-- tdd-workflow/ # TDD 方法論 +| |-- security-review/ # 安全性檢查清單 +| |-- eval-harness/ # 驗證迴圈評估(完整指南) +| |-- verification-loop/ # 持續驗證(完整指南) +| |-- golang-patterns/ # Go 慣用語法和最佳實務(新增) +| |-- golang-testing/ # Go 測試模式、TDD、基準測試(新增) | -|-- commands/ # 快速执行的斜杠指令(Commands) -| |-- tdd.md # /tdd - 测试驱动开发 -| |-- plan.md # /plan - 实现规划 -| |-- e2e.md # /e2e - E2E 测试生成 -| |-- code-review.md # /code-review - 质量审查 -| |-- build-fix.md # /build-fix - 修复构建错误 -| |-- refactor-clean.md # /refactor-clean - 移除无用代码 -| |-- learn.md # /learn - 会话中提取模式(完整指南) -| |-- checkpoint.md # /checkpoint - 保存验证状态(完整指南) -| |-- verify.md # /verify - 执行验证循环(完整指南) -| |-- setup-pm.md # /setup-pm - 设置包管理器 -| |-- go-review.md # /go-review - Go 代码审查(新增) -| |-- go-test.md # /go-test - Go TDD 工作流(新增) -| |-- go-build.md # /go-build - 修复 Go 构建错误(新增) +|-- commands/ # 快速執行的斜線指令 +| |-- tdd.md # /tdd - 測試驅動開發 +| |-- plan.md # /plan - 實作規劃 +| |-- e2e.md # /e2e - E2E 測試生成 +| |-- code-review.md # /code-review - 品質審查 +| |-- build-fix.md # /build-fix - 修復建置錯誤 +| |-- refactor-clean.md # /refactor-clean - 移除無用程式碼 +| |-- learn.md # /learn - 工作階段中擷取模式(完整指南) +| |-- checkpoint.md # /checkpoint - 儲存驗證狀態(完整指南) +| |-- verify.md # /verify - 執行驗證迴圈(完整指南) +| |-- setup-pm.md # /setup-pm - 設定套件管理器 +| |-- go-review.md # /go-review - Go 程式碼審查(新增) +| |-- go-test.md # /go-test - Go TDD 工作流程(新增) +| |-- go-build.md # /go-build - 修復 Go 建置錯誤(新增) | -|-- rules/ # 必须遵守的准则(Rules)(复制到 ~/.claude/rules/) -| |-- security.md # 强制性安全检查 -| |-- coding-style.md # 不变性、文件组织 -| |-- testing.md # TDD、80% 覆盖率要求 +|-- rules/ # 必須遵守的準則(複製到 ~/.claude/rules/) +| |-- security.md # 強制性安全檢查 +| |-- coding-style.md # 不可變性、檔案組織 +| |-- testing.md # TDD、80% 覆蓋率要求 | |-- git-workflow.md # 提交格式、PR 流程 -| |-- agents.md # 何时委派给子智能体 -| |-- performance.md # 模型选择、上下文管理 +| |-- agents.md # 何時委派給子代理程式 +| |-- performance.md # 模型選擇、上下文管理 | -|-- hooks/ # 基于触发器的自动化钩子(Hooks) -| |-- hooks.json # 所有钩子配置(PreToolUse、PostToolUse、Stop 等) -| |-- memory-persistence/ # 会话生命周期钩子(完整指南) -| |-- strategic-compact/ # 压缩建议(完整指南) +|-- hooks/ # 基於觸發器的自動化 +| |-- hooks.json # 所有鉤子設定(PreToolUse、PostToolUse、Stop 等) +| |-- memory-persistence/ # 工作階段生命週期鉤子(完整指南) +| |-- strategic-compact/ # 壓縮建議(完整指南) | -|-- scripts/ # 跨平台 Node.js 脚本(新增) -| |-- lib/ # 共享工具 -| | |-- utils.js # 跨平台文件/路径/系统工具 -| | |-- package-manager.js # 包管理器检测与选择 -| |-- hooks/ # 钩子实现 -| | |-- session-start.js # 会话开始时加载上下文 -| | |-- session-end.js # 会话结束时保存状态 -| | |-- pre-compact.js # 压缩前状态保存 -| | |-- suggest-compact.js # 策略性压缩建议 -| | |-- evaluate-session.js # 从会话中提取模式 -| |-- setup-package-manager.js # 交互式包管理器设置 +|-- scripts/ # 跨平台 Node.js 腳本(新增) +| |-- lib/ # 共用工具 +| | |-- utils.js # 跨平台檔案/路徑/系統工具 +| | |-- package-manager.js # 套件管理器偵測與選擇 +| |-- hooks/ # 鉤子實作 +| | |-- session-start.js # 工作階段開始時載入上下文 +| | |-- session-end.js # 工作階段結束時儲存狀態 +| | |-- pre-compact.js # 壓縮前狀態儲存 +| | |-- suggest-compact.js # 策略性壓縮建議 +| | |-- evaluate-session.js # 從工作階段擷取模式 +| |-- setup-package-manager.js # 互動式套件管理器設定 | -|-- tests/ # 测试套件(新增) -| |-- lib/ # 库测试 -| |-- hooks/ # 钩子测试 -| |-- run-all.js # 执行所有测试 +|-- tests/ # 測試套件(新增) +| |-- lib/ # 函式庫測試 +| |-- hooks/ # 鉤子測試 +| |-- run-all.js # 執行所有測試 | -|-- contexts/ # 动态系统提示词(Prompt)注入上下文(完整指南) -| |-- dev.md # 开发模式上下文 -| |-- review.md # 代码审查模式上下文 +|-- contexts/ # 動態系統提示注入上下文(完整指南) +| |-- dev.md # 開發模式上下文 +| |-- review.md # 程式碼審查模式上下文 | |-- research.md # 研究/探索模式上下文 | -|-- examples/ # 示例配置和会话 -| |-- CLAUDE.md # 项目级配置示例 -| |-- user-CLAUDE.md # 用户级配置示例 +|-- examples/ # 範例設定和工作階段 +| |-- CLAUDE.md # 專案層級設定範例 +| |-- user-CLAUDE.md # 使用者層級設定範例 | -|-- mcp-configs/ # MCP 服务器配置 +|-- mcp-configs/ # MCP 伺服器設定 | |-- mcp-servers.json # GitHub、Supabase、Vercel、Railway 等 | -|-- marketplace.json # 自托管市场配置(用于 /plugin marketplace add) +|-- marketplace.json # 自託管市集設定(用於 /plugin marketplace add) ``` --- -## 生态系统工具 +## 🛠️ 生態系統工具 -### ecc.tools - 技能生成器 +### ecc.tools - 技能建立器 -从您的仓库自动生成 Claude Code 技能(Skills)。 +從您的儲存庫自動生成 Claude Code 技能。 -[安装 GitHub App](https://github.com/apps/skill-creator) | [ecc.tools](https://ecc.tools) +[安裝 GitHub App](https://github.com/apps/skill-creator) | [ecc.tools](https://ecc.tools) -分析您的仓库并创建: -- **SKILL.md 文件** - 可直接用于 Claude Code 的技能 -- **本能集合** - 用于 continuous-learning-v2 -- **模式提取** - 从您的提交历史学习 +分析您的儲存庫並建立: +- **SKILL.md 檔案** - 可直接用於 Claude Code 的技能 +- **本能集合** - 用於 continuous-learning-v2 +- **模式擷取** - 從您的提交歷史學習 ```bash -# 安装 GitHub App 后,技能会出现在: +# 安裝 GitHub App 後,技能會出現在: ~/.claude/skills/generated/ ``` -与 `continuous-learning-v2` 技能无缝整合以继承本能。 +與 `continuous-learning-v2` 技能無縫整合以繼承本能。 --- -## 安装 +## 📥 安裝 -### 选项 1:以插件(Plugin)安装(推荐) +### 選項 1:以外掛程式安裝(建議) -使用本仓库最简单的方式 - 安装为 Claude Code 插件: +使用本儲存庫最簡單的方式 - 安裝為 Claude Code 外掛程式: ```bash -# 将此仓库添加为市场 +# 將此儲存庫新增為市集 /plugin marketplace add affaan-m/everything-claude-code -# 安装插件 +# 安裝外掛程式 /plugin install everything-claude-code@everything-claude-code ``` -或直接添加到您的 `~/.claude/settings.json`: +或直接新增到您的 `~/.claude/settings.json`: ```json { @@ -236,48 +288,48 @@ everything-claude-code/ } ``` -这会让您立即访问所有指令、智能体、技能和钩子。 +這會讓您立即存取所有指令、代理程式、技能和鉤子。 --- -### 选项 2:手动安装 +### 🔧 選項 2:手動安裝 -如果您偏好手动控制安装内容: +如果您偏好手動控制安裝內容: ```bash -# 克隆仓库 +# 複製儲存庫 git clone https://github.com/affaan-m/everything-claude-code.git -# 将智能体复制到您的 Claude 配置 +# 將代理程式複製到您的 Claude 設定 cp everything-claude-code/agents/*.md ~/.claude/agents/ -# 复制规则 +# 複製規則 cp everything-claude-code/rules/*.md ~/.claude/rules/ -# 复制指令 +# 複製指令 cp everything-claude-code/commands/*.md ~/.claude/commands/ -# 复制技能 +# 複製技能 cp -r everything-claude-code/skills/* ~/.claude/skills/ ``` -#### 将钩子添加到 settings.json +#### 將鉤子新增到 settings.json -将 `hooks/hooks.json` 中的钩子复制到您的 `~/.claude/settings.json`。 +將 `hooks/hooks.json` 中的鉤子複製到您的 `~/.claude/settings.json`。 -#### 配置 MCP +#### 設定 MCP -将 `mcp-configs/mcp-servers.json` 中所需的 MCP 服务器配置复制到您的 `~/.claude.json`。 +將 `mcp-configs/mcp-servers.json` 中所需的 MCP 伺服器複製到您的 `~/.claude.json`。 -**重要:** 将 `YOUR_*_HERE` 占位符替换为您实际的 API 密钥。 +**重要:** 將 `YOUR_*_HERE` 佔位符替換為您實際的 API 金鑰。 --- -## 核心概念 +## 🎯 核心概念 -### 智能体(Agents) +### 代理程式(Agents) -子智能体以有限范围处理委派的任务。示例: +子代理程式以有限範圍處理委派的任務。範例: ```markdown --- @@ -292,7 +344,7 @@ You are a senior code reviewer... ### 技能(Skills) -技能是由指令或智能体调用的工作流定义: +技能是由指令或代理程式調用的工作流程定義: ```markdown # TDD Workflow @@ -304,13 +356,13 @@ You are a senior code reviewer... 5. Verify 80%+ coverage ``` -### 钩子(Hooks) +### 鉤子(Hooks) -钩子在工具(Tool)事件时触发。示例 - 警告 console.log: +鉤子在工具事件時觸發。範例 - 警告 console.log: ```json { - "matcher": "tool == \"Edit\" && tool_input.file_path matches \"\\.(ts|tsx|js|jsx)$\"", + "matcher": "tool == \"Edit\" && tool_input.file_path matches \"\\\\.(ts|tsx|js|jsx)$\"", "hooks": [{ "type": "command", "command": "#!/bin/bash\ngrep -n 'console\\.log' \"$file_path\" && echo '[Hook] Remove console.log' >&2" @@ -318,28 +370,28 @@ You are a senior code reviewer... } ``` -### 规则(Rules) +### 規則(Rules) -规则是必须遵守的准则。保持模块化: +規則是必須遵守的準則。保持模組化: ``` ~/.claude/rules/ - security.md # 禁止硬编码密钥 - coding-style.md # 不变性、文件组织 - testing.md # TDD、80% 覆盖率要求 + security.md # 禁止寫死密鑰 + coding-style.md # 不可變性、檔案限制 + testing.md # TDD、覆蓋率要求 ``` --- -## 执行测试 +## 🧪 執行測試 -插件包含完整的测试套件: +外掛程式包含完整的測試套件: ```bash -# 执行所有测试 +# 執行所有測試 node tests/run-all.js -# 执行个别测试文件 +# 執行個別測試檔案 node tests/lib/utils.test.js node tests/lib/package-manager.test.js node tests/hooks/hooks.test.js @@ -347,78 +399,78 @@ node tests/hooks/hooks.test.js --- -## 贡献 +## 🤝 貢獻 -**欢迎并鼓励贡献。** +**歡迎並鼓勵貢獻。** -本仓库旨在成为社区资源。如果您有: -- 实用的智能体或技能 -- 巧妙的钩子 -- 更好的 MCP 配置 -- 改进的规则 +本儲存庫旨在成為社群資源。如果您有: +- 實用的代理程式或技能 +- 巧妙的鉤子 +- 更好的 MCP 設定 +- 改進的規則 -请贡献!详见 [CONTRIBUTING.md](CONTRIBUTING.md) 的指南。 +請貢獻!詳見 [CONTRIBUTING.md](CONTRIBUTING.md) 的指南。 -### 贡献想法 +### 貢獻想法 -- 特定语言的技能(Python、Rust 模式)- Go 现已包含! -- 特定框架的配置(Django、Rails、Laravel) -- DevOps 智能体(Kubernetes、Terraform、AWS) -- 测试策略(不同框架) -- 特定领域知识(ML、数据工程、移动开发) +- 特定語言的技能(Python、Rust 模式)- Go 現已包含! +- 特定框架的設定(Django、Rails、Laravel) +- DevOps 代理程式(Kubernetes、Terraform、AWS) +- 測試策略(不同框架) +- 特定領域知識(ML、資料工程、行動開發) --- -## 背景 +## 📖 背景 -我从实验性推出就开始使用 Claude Code。2025 年 9 月与 [@DRodriguezFX](https://x.com/DRodriguezFX) 一起使用 Claude Code 打造 [zenith.chat](https://zenith.chat),赢得了 Anthropic x Forum Ventures 黑客松。 +我從實驗性推出就開始使用 Claude Code。2025 年 9 月與 [@DRodriguezFX](https://x.com/DRodriguezFX) 一起使用 Claude Code 打造 [zenith.chat](https://zenith.chat),贏得了 Anthropic x Forum Ventures 黑客松。 -这些配置已在多个生产应用程序中经过实战测试。 +這些設定已在多個生產應用程式中經過實戰測試。 --- -## 重要注意事项 +## ⚠️ 重要注意事項 -### 上下文窗口管理 +### 上下文視窗管理 -**关键:** 不要同时启用所有 MCP。启用过多工具会让您的 200k 上下文窗口缩减至 70k。 +**關鍵:** 不要同時啟用所有 MCP。啟用過多工具會讓您的 200k 上下文視窗縮減至 70k。 -经验法则: -- 设置 20-30 个 MCP -- 每个项目启用少于 10 个 -- 启用的工具少于 80 个 +經驗法則: +- 設定 20-30 個 MCP +- 每個專案啟用少於 10 個 +- 啟用的工具少於 80 個 -在项目配置中使用 `disabledMcpServers` 来禁用未使用的 MCP。 +在專案設定中使用 `disabledMcpServers` 來停用未使用的 MCP。 -### 自定义 +### 自訂 -这些配置适合我的工作流。您应该: -1. 从您认同的部分开始 -2. 根据您的技术栈修改 +這些設定適合我的工作流程。您應該: +1. 從您認同的部分開始 +2. 根據您的技術堆疊修改 3. 移除不需要的部分 4. 添加您自己的模式 --- -## Star 历史 +## 🌟 Star 歷史 [![Star History Chart](https://api.star-history.com/svg?repos=affaan-m/everything-claude-code&type=Date)](https://star-history.com/#affaan-m/everything-claude-code&Date) --- -## 链接 +## 🔗 連結 -- **简明指南(从这里开始):** [Everything Claude Code 简明指南](https://x.com/affaanmustafa/status/2012378465664745795) -- **完整指南(进阶):** [Everything Claude Code 完整指南](https://x.com/affaanmustafa/status/2014040193557471352) -- **关注:** [@affaanmustafa](https://x.com/affaanmustafa) +- **簡明指南(從這裡開始):** [Everything Claude Code 簡明指南](https://x.com/affaanmustafa/status/2012378465664745795) +- **完整指南(進階):** [Everything Claude Code 完整指南](https://x.com/affaanmustafa/status/2014040193557471352) +- **追蹤:** [@affaanmustafa](https://x.com/affaanmustafa) - **zenith.chat:** [zenith.chat](https://zenith.chat) --- -## 授权 +## 📄 授權 -MIT - 自由使用、依需求修改、如可能请回馈贡献。 +MIT - 自由使用、依需求修改、如可能請回饋貢獻。 --- -**如果有帮助请为本仓库点赞(Star)。阅读两份指南。打造伟大的作品。** +**如果有幫助請為本儲存庫加星。閱讀兩份指南。打造偉大的作品。** diff --git a/package-lock.json b/package-lock.json index b732d4e..db3ddf5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "everything-claude-code-zh", + "name": "everything-claude-code", "lockfileVersion": 3, "requires": true, "packages": { @@ -294,7 +294,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -600,7 +599,6 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -1932,7 +1930,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, diff --git a/skills/continuous-learning-v2/commands/evolve.md b/skills/continuous-learning-v2/commands/evolve.md deleted file mode 100644 index ec56f2b..0000000 --- a/skills/continuous-learning-v2/commands/evolve.md +++ /dev/null @@ -1,186 +0,0 @@ ---- -name: evolve -description: 将相关本能 (Instincts) 聚类为技能 (Skills)、命令 (Commands) 或智能体 (Agents) -command: /evolve -implementation: python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py evolve ---- - -# 演进 (Evolve) 命令 - -## 实现 - -```bash -python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py evolve [--generate] -``` - -分析本能 (Instincts) 并将相关的本能聚类为更高级别的结构: -- **命令 (Commands)**:当本能描述的是用户调用的操作时 -- **技能 (Skills)**:当本能描述的是自动触发的行为时 -- **智能体 (Agents)**:当本能描述的是复杂的、多步骤的过程时 - -## 用法 - -``` -/evolve # 分析所有本能并建议演进方案 -/evolve --domain testing # 仅演进测试领域 (testing domain) 中的本能 -/evolve --dry-run # 显示将要创建的内容而不实际执行 -/evolve --threshold 5 # 要求 5 个或更多相关本能才进行聚类 -``` - -## 演进规则 - -### → 命令 (Command,用户调用) -当本能描述用户会显式请求的操作时: -- 多个关于“当用户要求...”的本能 -- 带有“在创建新的 X 时”等触发器的本能 -- 遵循可重复序列的本能 - -示例: -- `new-table-step1`:“在添加数据库表时,创建迁移文件” -- `new-table-step2`:“在添加数据库表时,更新 schema” -- `new-table-step3`:“在添加数据库表时,重新生成类型” - -→ 创建:`/new-table` 命令 - -### → 技能 (Skill,自动触发) -当本能描述应该自动发生的行为时: -- 模式匹配触发器 -- 错误处理响应 -- 代码风格强制执行 - -示例: -- `prefer-functional`:“在编写函数时,优先使用函数式风格” -- `use-immutable`:“在修改状态时,使用不可变模式” -- `avoid-classes`:“在设计模块时,避免基于类的设计” - -→ 创建:`functional-patterns` 技能 - -### → 智能体 (Agent,需要深度/隔离) -当本能描述受益于隔离的复杂、多步骤过程时: -- 调试工作流 (Workflow) -- 重构序列 -- 研究任务 - -示例: -- `debug-step1`:“调试时,先检查日志” -- `debug-step2`:“调试时,隔离故障组件” -- `debug-step3`:“调试时,创建最小复现” -- `debug-step4`:“调试时,通过测试验证修复” - -→ 创建:`debugger` 智能体 - -## 执行步骤 - -1. 从 `~/.claude/homunculus/instincts/` 读取所有本能 -2. 按以下维度对本能进行分组: - - 领域相似性 - - 触发模式重叠 - - 动作序列关系 -3. 对于每个包含 3 个或更多相关本能的聚类: - - 确定演进类型(命令/技能/智能体) - - 生成相应的文件 - - 保存到 `~/.claude/homunculus/evolved/{commands,skills,agents}/` -4. 将演进后的结构链接回源本能 - -## 输出格式 - -``` -🧬 演进分析 (Evolve Analysis) -================== - -发现 3 个已准备好演进的聚类: - -## 聚类 1: 数据库迁移工作流 -本能: new-table-migration, update-schema, regenerate-types -类型: 命令 (Command) -置信度: 85% (基于 12 次观察) - -将创建: /new-table 命令 -文件: - - ~/.claude/homunculus/evolved/commands/new-table.md - -## 聚类 2: 函数式代码风格 -本能: prefer-functional, use-immutable, avoid-classes, pure-functions -类型: 技能 (Skill) -置信度: 78% (基于 8 次观察) - -将创建: functional-patterns 技能 -文件: - - ~/.claude/homunculus/evolved/skills/functional-patterns.md - -## 聚类 3: 调试过程 -本能: debug-check-logs, debug-isolate, debug-reproduce, debug-verify -类型: 智能体 (Agent) -置信度: 72% (基于 6 次观察) - -将创建: debugger 智能体 -文件: - - ~/.claude/homunculus/evolved/agents/debugger.md - ---- -运行 `/evolve --execute` 来创建这些文件。 -``` - -## 标志 (Flags) - -- `--execute`:实际创建演进后的结构(默认为预览) -- `--dry-run`:预览而不创建 -- `--domain `:仅演进指定领域中的本能 -- `--threshold `:形成聚类所需的最小本能数量(默认:3) -- `--type `:仅创建指定类型 - -## 生成文件格式 - -### 命令 (Command) -```markdown ---- -name: new-table -description: 创建带有迁移、schema 更新和类型生成的数据库新表 -command: /new-table -evolved_from: - - new-table-migration - - update-schema - - regenerate-types ---- - -# New Table 命令 - -[基于聚类本能生成的具体内容] - -## 步骤 -1. ... -2. ... -``` - -### 技能 (Skill) -```markdown ---- -name: functional-patterns -description: 强制执行函数式编程模式 -evolved_from: - - prefer-functional - - use-immutable - - avoid-classes ---- - -# Functional Patterns 技能 - -[基于聚类本能生成的具体内容] -``` - -### 智能体 (Agent) -```markdown ---- -name: debugger -description: 系统化调试智能体 -model: sonnet -evolved_from: - - debug-check-logs - - debug-isolate - - debug-reproduce ---- - -# Debugger 智能体 - -[基于聚类本能生成的具体内容] -``` diff --git a/skills/continuous-learning-v2/commands/instinct-export.md b/skills/continuous-learning-v2/commands/instinct-export.md deleted file mode 100644 index 441a959..0000000 --- a/skills/continuous-learning-v2/commands/instinct-export.md +++ /dev/null @@ -1,91 +0,0 @@ ---- -name: instinct-export -description: 导出直觉(Instincts)以便与团队成员或其他项目共享 -command: /instinct-export ---- - -# 直觉导出命令(Instinct Export Command) - -将直觉(Instincts)导出为可共享的格式。非常适用于: -- 与团队成员共享 -- 迁移到新机器 -- 贡献到项目规范(Conventions)中 - -## 使用方法 - -``` -/instinct-export # 导出所有个人直觉 -/instinct-export --domain testing # 仅导出测试(Testing)领域的直觉 -/instinct-export --min-confidence 0.7 # 仅导出高置信度的直觉 -/instinct-export --output team-instincts.yaml -``` - -## 执行逻辑 - -1. 从 `~/.claude/homunculus/instincts/personal/` 读取直觉数据 -2. 根据参数(Flags)进行过滤 -3. 脱敏敏感信息: - - 移除会话 ID(Session IDs) - - 移除文件路径(仅保留模式串/Patterns) - - 移除早于“上周”的时间戳 -4. 生成导出文件 - -## 输出格式 - -创建一个 YAML 文件: - -```yaml -# Instincts Export -# Generated: 2025-01-22 -# Source: personal -# Count: 12 instincts - -version: "2.0" -exported_by: "continuous-learning-v2" -export_date: "2025-01-22T10:30:00Z" - -instincts: - - id: prefer-functional-style - trigger: "when writing new functions" - action: "Use functional patterns over classes" - confidence: 0.8 - domain: code-style - observations: 8 - - - id: test-first-workflow - trigger: "when adding new functionality" - action: "Write test first, then implementation" - confidence: 0.9 - domain: testing - observations: 12 - - - id: grep-before-edit - trigger: "when modifying code" - action: "Search with Grep, confirm with Read, then Edit" - confidence: 0.7 - domain: workflow - observations: 6 -``` - -## 隐私说明 - -导出内容**包含**: -- ✅ 触发模式(Trigger patterns) -- ✅ 动作(Actions) -- ✅ 置信度评分(Confidence scores) -- ✅ 领域(Domains) -- ✅ 观测计数(Observation counts) - -导出内容**不包含**: -- ❌ 实际代码片段 -- ❌ 文件路径 -- ❌ 会话转录(Session transcripts) -- ❌ 个人身份标识符 - -## 参数(Flags) - -- `--domain `: 仅导出指定领域(Domain) -- `--min-confidence `: 最低置信度阈值(默认值:0.3) -- `--output `: 输出文件路径(默认值:instincts-export-YYYYMMDD.yaml) -- `--format `: 输出格式(默认值:yaml) -- `--include-evidence`: 包含证据(Evidence)文本(默认:排除) diff --git a/skills/continuous-learning-v2/commands/instinct-import.md b/skills/continuous-learning-v2/commands/instinct-import.md deleted file mode 100644 index 7157b13..0000000 --- a/skills/continuous-learning-v2/commands/instinct-import.md +++ /dev/null @@ -1,135 +0,0 @@ ---- -name: instinct-import -description: 从队友、技能生成器(Skill Creator)或其他来源导入直觉(Instincts) -command: /instinct-import -implementation: python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py import ---- - -# 直觉导入命令(Instinct Import Command) - -## 实现 - -```bash -python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py import [--dry-run] [--force] [--min-confidence 0.7] -``` - -从以下来源导入直觉(Instincts): -- 队友导出的文件 -- 技能生成器(Skill Creator)(仓库分析) -- 社区集合 -- 之前的机器备份 - -## 用法 - -``` -/instinct-import team-instincts.yaml -/instinct-import https://github.com/org/repo/instincts.yaml -/instinct-import --from-skill-creator acme/webapp -``` - -## 执行流程 - -1. 获取直觉文件(本地路径或 URL) -2. 解析并验证格式 -3. 检查是否与现有直觉重复 -4. 合并或添加新直觉 -5. 保存至 `~/.claude/homunculus/instincts/inherited/` - -## 导入过程示例 - -``` -📥 正在从 team-instincts.yaml 导入直觉: -================================================ - -发现 12 条待导入的直觉。 - -正在分析冲突... - -## 新直觉 (8) -这些将被添加: - ✓ use-zod-validation (置信度: 0.7) - ✓ prefer-named-exports (置信度: 0.65) - ✓ test-async-functions (置信度: 0.8) - ... - -## 重复直觉 (3) -已存在类似的直觉: - ⚠️ prefer-functional-style - 本地:0.8 置信度,12 个观测项 - 导入:0.7 置信度 - → 保留本地(置信度更高) - - ⚠️ test-first-workflow - 本地:0.75 置信度 - 导入:0.9 置信度 - → 更新为导入的内容(置信度更高) - -## 冲突直觉 (1) -这些与本地直觉相矛盾: - ❌ use-classes-for-services - 与 avoid-classes 冲突 - → 跳过(需要手动解决) - ---- -导入 8 个新项,更新 1 个,跳过 3 个? -``` - -## 合并策略(Merge Strategies) - -### 处理重复项 -当导入的直觉与现有直觉匹配时: -- **高置信度胜出**:保留置信度(Confidence)较高的一方 -- **合并证据**:累计观测项(Observation)计数 -- **更新时间戳**:标记为最近已验证 - -### 处理冲突 -当导入的直觉与现有直觉冲突时: -- **默认跳过**:不导入产生冲突的直觉 -- **标记待审查**:将两者都标记为需要关注 -- **手动解决**:由用户决定保留哪一个 - -## 来源追踪 - -导入的直觉会被标记以下字段: -```yaml -source: "inherited" -imported_from: "team-instincts.yaml" -imported_at: "2025-01-22T10:30:00Z" -original_source: "session-observation" # 或 "repo-analysis" -``` - -## 技能生成器(Skill Creator)集成 - -从技能生成器(Skill Creator)导入时: - -``` -/instinct-import --from-skill-creator acme/webapp -``` - -这将获取通过仓库分析生成的直觉: -- 来源:`repo-analysis` -- 较高的初始置信度(0.7+) -- 已链接到源仓库 - -## 参数标志(Flags) - -- `--dry-run`:预览而不执行导入 -- `--force`:即使存在冲突也强制导入 -- `--merge-strategy `:如何处理重复项 -- `--from-skill-creator `:从技能生成器(Skill Creator)分析结果导入 -- `--min-confidence `:仅导入置信度高于阈值的直觉 - -## 输出 - -导入完成后: -``` -✅ 导入完成! - -已添加:8 条直觉 -已更新:1 条直觉 -已跳过:3 条直觉(2 个重复,1 个冲突) - -新直觉已保存至:~/.claude/homunculus/instincts/inherited/ - -运行 /instinct-status 查看所有直觉。 -``` diff --git a/skills/continuous-learning-v2/commands/instinct-status.md b/skills/continuous-learning-v2/commands/instinct-status.md deleted file mode 100644 index f440700..0000000 --- a/skills/continuous-learning-v2/commands/instinct-status.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -name: instinct-status -description: 显示所有已学习的直觉(Instincts)及其置信度水平 -command: /instinct-status -implementation: python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py status ---- - -# Instinct Status 命令 - -按领域(Domain)分组显示所有已学习的直觉(Instincts)及其置信度得分。 - -## 实现 - -```bash -python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py status -``` - -## 用法 - -``` -/instinct-status -/instinct-status --domain code-style -/instinct-status --low-confidence -``` - -## 执行逻辑 - -1. 从 `~/.claude/homunculus/instincts/personal/` 读取所有个人直觉文件 -2. 从 `~/.claude/homunculus/instincts/inherited/` 读取继承的直觉 -3. 按领域分组显示,并附带置信度进度条 - -## 输出格式 - -``` -📊 直觉状态 (Instinct Status) -================== - -## 代码风格 (4 个直觉) - -### prefer-functional-style -触发条件 (Trigger):编写新函数时 -动作 (Action):优先使用函数式模式而非类 -置信度 (Confidence):████████░░ 80% -来源 (Source):session-observation | 最后更新:2025-01-22 - -### use-path-aliases -触发条件 (Trigger):导入模块时 -动作 (Action):使用 @/ 路径别名而非相对导入 -置信度 (Confidence):██████░░░░ 60% -来源 (Source):repo-analysis (github.com/acme/webapp) - -## 测试 (2 个直觉) - -### test-first-workflow -触发条件 (Trigger):添加新功能时 -动作 (Action):先写测试,再写实现 -置信度 (Confidence):█████████░ 90% -来源 (Source):session-observation - -## 工作流 (3 个直觉) - -### grep-before-edit -触发条件 (Trigger):修改代码时 -动作 (Action):先用 Grep 搜索,用 Read 确认,再进行编辑 (Edit) -置信度 (Confidence):███████░░░ 70% -来源 (Source):session-observation - ---- -总计:9 个直觉(4 个个人,5 个继承) -观察器 (Observer):运行中(上次分析:5 分钟前) -``` - -## 参数 (Flags) - -- `--domain `:按领域过滤(code-style、testing、git 等) -- `--low-confidence`:仅显示置信度 < 0.5 的直觉 -- `--high-confidence`:仅显示置信度 >= 0.7 的直觉 -- `--source `:按来源过滤(session-observation、repo-analysis、inherited) -- `--json`:以 JSON 格式输出,供程序化使用 diff --git a/skills/django-patterns/SKILL.md b/skills/django-patterns/SKILL.md new file mode 100644 index 0000000..fc1ad3b --- /dev/null +++ b/skills/django-patterns/SKILL.md @@ -0,0 +1,734 @@ +--- +name: django-patterns +description: Django 架构模式、使用 DRF 的 REST API 设计、ORM 最佳实践、缓存、信号(Signals)、中间件(Middleware)以及生产级 Django 应用。 +--- + +# Django 开发模式 + +适用于可扩展、可维护应用程序的生产级 Django 架构模式。 + +## 何时激活 + +- 构建 Django Web 应用程序时 +- 设计 Django REST Framework (DRF) API 时 +- 处理 Django ORM 和模型时 +* 设置 Django 项目结构时 +* 实现缓存(Caching)、信号(Signals)、中间件(Middleware)时 + +## 项目结构 + +### 推荐布局 + +``` +myproject/ +├── config/ +│ ├── __init__.py +│ ├── settings/ +│ │ ├── __init__.py +│ │ ├── base.py # 基础设置 +│ │ ├── development.py # 开发环境设置 +│ │ ├── production.py # 生产环境设置 +│ │ └── test.py # 测试环境设置 +│ ├── urls.py +│ ├── wsgi.py +│ └── asgi.py +├── manage.py +└── apps/ + ├── __init__.py + ├── users/ + │ ├── __init__.py + │ ├── models.py + │ ├── views.py + │ ├── serializers.py + │ ├── urls.py + │ ├── permissions.py + │ ├── filters.py + │ ├── services.py + │ └── tests/ + └── products/ + └── ... +``` + +### 分离设置模式(Split Settings Pattern) + +```python +# config/settings/base.py +from pathlib import Path + +BASE_DIR = Path(__file__).resolve().parent.parent.parent + +SECRET_KEY = env('DJANGO_SECRET_KEY') +DEBUG = False +ALLOWED_HOSTS = [] + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'rest_framework', + 'rest_framework.authtoken', + 'corsheaders', + # 本地应用 + 'apps.users', + 'apps.products', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'whitenoise.middleware.WhiteNoiseMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'corsheaders.middleware.CorsMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'config.urls' +WSGI_APPLICATION = 'config.wsgi.application' + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': env('DB_NAME'), + 'USER': env('DB_USER'), + 'PASSWORD': env('DB_PASSWORD'), + 'HOST': env('DB_HOST'), + 'PORT': env('DB_PORT', default='5432'), + } +} + +# config/settings/development.py +from .base import * + +DEBUG = True +ALLOWED_HOSTS = ['localhost', '127.0.0.1'] + +DATABASES['default']['NAME'] = 'myproject_dev' + +INSTALLED_APPS += ['debug_toolbar'] + +MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware'] + +EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' + +# config/settings/production.py +from .base import * + +DEBUG = False +ALLOWED_HOSTS = env.list('ALLOWED_HOSTS') +SECURE_SSL_REDIRECT = True +SESSION_COOKIE_SECURE = True +CSRF_COOKIE_SECURE = True +SECURE_HSTS_SECONDS = 31536000 +SECURE_HSTS_INCLUDE_SUBDOMAINS = True +SECURE_HSTS_PRELOAD = True + +# 日志配置 +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'handlers': { + 'file': { + 'level': 'WARNING', + 'class': 'logging.FileHandler', + 'filename': '/var/log/django/django.log', + }, + }, + 'loggers': { + 'django': { + 'handlers': ['file'], + 'level': 'WARNING', + 'propagate': True, + }, + }, +} +``` + +## 模型设计模式 + +### 模型最佳实践 + +```python +from django.db import models +from django.contrib.auth.models import AbstractUser +from django.core.validators import MinValueValidator, MaxValueValidator + +class User(AbstractUser): + """扩展 AbstractUser 的自定义用户模型。""" + email = models.EmailField(unique=True) + phone = models.CharField(max_length=20, blank=True) + birth_date = models.DateField(null=True, blank=True) + + USERNAME_FIELD = 'email' + REQUIRED_FIELDS = ['username'] + + class Meta: + db_table = 'users' + verbose_name = 'user' + verbose_name_plural = 'users' + ordering = ['-date_joined'] + + def __str__(self): + return self.email + + def get_full_name(self): + return f"{self.first_name} {self.last_name}".strip() + +class Product(models.Model): + """带有适当字段配置的产品模型。""" + name = models.CharField(max_length=200) + slug = models.SlugField(unique=True, max_length=250) + description = models.TextField(blank=True) + price = models.DecimalField( + max_digits=10, + decimal_places=2, + validators=[MinValueValidator(0)] + ) + stock = models.PositiveIntegerField(default=0) + is_active = models.BooleanField(default=True) + category = models.ForeignKey( + 'Category', + on_delete=models.CASCADE, + related_name='products' + ) + tags = models.ManyToManyField('Tag', blank=True, related_name='products') + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + db_table = 'products' + ordering = ['-created_at'] + indexes = [ + models.Index(fields=['slug']), + models.Index(fields=['-created_at']), + models.Index(fields=['category', 'is_active']), + ] + constraints = [ + models.CheckConstraint( + check=models.Q(price__gte=0), + name='price_non_negative' + ) + ] + + def __str__(self): + return self.name + + def save(self, *args, **kwargs): + if not self.slug: + self.slug = slugify(self.name) + super().save(*args, **kwargs) +``` + +### QuerySet 最佳实践 + +```python +from django.db import models + +class ProductQuerySet(models.QuerySet): + """Product 模型的自定义 QuerySet。""" + + def active(self): + """仅返回已激活的产品。""" + return self.filter(is_active=True) + + def with_category(self): + """使用 select_related 加载分类,避免 N+1 查询。""" + return self.select_related('category') + + def with_tags(self): + """使用 prefetch_related 预加载多对多关系的标签。""" + return self.prefetch_related('tags') + + def in_stock(self): + """返回库存 > 0 的产品。""" + return self.filter(stock__gt=0) + + def search(self, query): + """按名称或描述搜索产品。""" + return self.filter( + models.Q(name__icontains=query) | + models.Q(description__icontains=query) + ) + +class Product(models.Model): + # ... 字段 ... + + objects = ProductQuerySet.as_manager() # 使用自定义 QuerySet + +# 用法 +Product.objects.active().with_category().in_stock() +``` + +### 管理器(Manager)方法 + +```python +class ProductManager(models.Manager): + """用于复杂查询的自定义管理器。""" + + def get_or_none(self, **kwargs): + """返回对象,或者在不存在时返回 None 而不是抛出 DoesNotExist。""" + try: + return self.get(**kwargs) + except self.model.DoesNotExist: + return None + + def create_with_tags(self, name, price, tag_names): + """创建产品并关联标签。""" + product = self.create(name=name, price=price) + tags = [Tag.objects.get_or_create(name=name)[0] for name in tag_names] + product.tags.set(tags) + return product + + def bulk_update_stock(self, product_ids, quantity): + """批量更新多个产品的库存。""" + return self.filter(id__in=product_ids).update(stock=quantity) + +# 在模型中 +class Product(models.Model): + # ... 字段 ... + custom = ProductManager() +``` + +## Django REST Framework 模式 + +### 序列化器(Serializer)模式 + +```python +from rest_framework import serializers +from django.contrib.auth.password_validation import validate_password +from .models import Product, User + +class ProductSerializer(serializers.ModelSerializer): + """Product 模型的序列化器。""" + + category_name = serializers.CharField(source='category.name', read_only=True) + average_rating = serializers.FloatField(read_only=True) + discount_price = serializers.SerializerMethodField() + + class Meta: + model = Product + fields = [ + 'id', 'name', 'slug', 'description', 'price', + 'discount_price', 'stock', 'category_name', + 'average_rating', 'created_at' + ] + read_only_fields = ['id', 'slug', 'created_at'] + + def get_discount_price(self, obj): + """如果适用,计算折扣价。""" + if hasattr(obj, 'discount') and obj.discount: + return obj.price * (1 - obj.discount.percent / 100) + return obj.price + + def validate_price(self, value): + """确保价格非负。""" + if value < 0: + raise serializers.ValidationError("价格不能为负数。") + return value + +class ProductCreateSerializer(serializers.ModelSerializer): + """用于创建产品的序列化器。""" + + class Meta: + model = Product + fields = ['name', 'description', 'price', 'stock', 'category'] + + def validate(self, data): + """多字段的自定义校验。""" + if data['price'] > 10000 and data['stock'] > 100: + raise serializers.ValidationError( + "高价值产品不能有大量库存。" + ) + return data + +class UserRegistrationSerializer(serializers.ModelSerializer): + """用户注册序列化器。""" + + password = serializers.CharField( + write_only=True, + required=True, + validators=[validate_password], + style={'input_type': 'password'} + ) + password_confirm = serializers.CharField(write_only=True, style={'input_type': 'password'}) + + class Meta: + model = User + fields = ['email', 'username', 'password', 'password_confirm'] + + def validate(self, data): + """校验两次输入的密码是否一致。""" + if data['password'] != data['password_confirm']: + raise serializers.ValidationError({ + "password_confirm": "密码字段不匹配。" + }) + return data + + def create(self, validated_data): + """创建并保存加密后的密码。""" + validated_data.pop('password_confirm') + password = validated_data.pop('password') + user = User.objects.create(**validated_data) + user.set_password(password) + user.save() + return user +``` + +### 视图集(ViewSet)模式 + +```python +from rest_framework import viewsets, status, filters +from rest_framework.decorators import action +from rest_framework.response import Response +from rest_framework.permissions import IsAuthenticated, IsAdminUser +from django_filters.rest_framework import DjangoFilterBackend +from .models import Product +from .serializers import ProductSerializer, ProductCreateSerializer +from .permissions import IsOwnerOrReadOnly +from .filters import ProductFilter +from .filters import ProductFilter +from .services import ProductService + +class ProductViewSet(viewsets.ModelViewSet): + """Product 模型的视图集。""" + + queryset = Product.objects.select_related('category').prefetch_related('tags') + permission_classes = [IsAuthenticated, IsOwnerOrReadOnly] + filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] + filterset_class = ProductFilter + search_fields = ['name', 'description'] + ordering_fields = ['price', 'created_at', 'name'] + ordering = ['-created_at'] + + def get_serializer_class(self): + """根据操作(action)返回适当的序列化器类。""" + if self.action == 'create': + return ProductCreateSerializer + return ProductSerializer + + def perform_create(self, serializer): + """在保存时关联当前用户上下文。""" + serializer.save(created_by=self.request.user) + + @action(detail=False, methods=['get']) + def featured(self, request): + """返回推荐产品。""" + featured = self.queryset.filter(is_featured=True)[:10] + serializer = self.get_serializer(featured, many=True) + return Response(serializer.data) + + @action(detail=True, methods=['post']) + def purchase(self, request, pk=None): + """购买产品。""" + product = self.get_object() + service = ProductService() + result = service.purchase(product, request.user) + return Response(result, status=status.HTTP_201_CREATED) + + @action(detail=False, methods=['get'], permission_classes=[IsAuthenticated]) + def my_products(self, request): + """返回由当前用户创建的产品。""" + products = self.queryset.filter(created_by=request.user) + page = self.paginate_queryset(products) + serializer = self.get_serializer(page, many=True) + return self.get_paginated_response(serializer.data) +``` + +### 自定义操作 + +```python +from rest_framework.decorators import api_view, permission_classes +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def add_to_cart(request): + """将产品添加到用户购物车。""" + product_id = request.data.get('product_id') + quantity = request.data.get('quantity', 1) + + try: + product = Product.objects.get(id=product_id) + except Product.DoesNotExist: + return Response( + {'error': '产品未找到'}, + status=status.HTTP_404_NOT_FOUND + ) + + cart, _ = Cart.objects.get_or_create(user=request.user) + CartItem.objects.create( + cart=cart, + product=product, + quantity=quantity + ) + + return Response({'message': '已添加到购物车'}, status=status.HTTP_201_CREATED) +``` + +## 服务层模式(Service Layer Pattern) + +```python +# apps/orders/services.py +from typing import Optional +from django.db import transaction +from .models import Order, OrderItem + +class OrderService: + """订单相关业务逻辑的服务层。""" + + @staticmethod + @transaction.atomic + def create_order(user, cart: Cart) -> Order: + """从购物车创建订单。""" + order = Order.objects.create( + user=user, + total_price=cart.total_price + ) + + for item in cart.items.all(): + OrderItem.objects.create( + order=order, + product=item.product, + quantity=item.quantity, + price=item.product.price + ) + + # 清空购物车 + cart.items.all().delete() + + return order + + @staticmethod + def process_payment(order: Order, payment_data: dict) -> bool: + """处理订单支付。""" + # 与支付网关集成 + payment = PaymentGateway.charge( + amount=order.total_price, + token=payment_data['token'] + ) + + if payment.success: + order.status = Order.Status.PAID + order.save() + # 发送确认邮件 + OrderService.send_confirmation_email(order) + return True + + return False + + @staticmethod + def send_confirmation_email(order: Order): + """发送订单确认邮件。""" + # 邮件发送逻辑 + pass +``` + +## 缓存策略(Caching Strategies) + +### 视图级缓存 + +```python +from django.views.decorators.cache import cache_page +from django.utils.decorators import method_decorator + +@method_decorator(cache_page(60 * 15), name='dispatch') # 15 分钟 +class ProductListView(generic.ListView): + model = Product + template_name = 'products/list.html' + context_object_name = 'products' +``` + +### 模板片段缓存 + +```django +{% load cache %} +{% cache 500 sidebar %} + ... 耗时的侧边栏内容 ... +{% endcache %} +``` + +### 低级缓存 + +```python +from django.core.cache import cache + +def get_featured_products(): + """通过缓存获取推荐产品。""" + cache_key = 'featured_products' + products = cache.get(cache_key) + + if products is None: + products = list(Product.objects.filter(is_featured=True)) + cache.set(cache_key, products, timeout=60 * 15) # 15 分钟 + + return products +``` + +### QuerySet 缓存 + +```python +from django.core.cache import cache + +def get_popular_categories(): + cache_key = 'popular_categories' + categories = cache.get(cache_key) + + if categories is None: + categories = list(Category.objects.annotate( + product_count=Count('products') + ).filter(product_count__gt=10).order_by('-product_count')[:20]) + cache.set(cache_key, categories, timeout=60 * 60) # 1 小时 + + return categories +``` + +## 信号(Signals) + +### 信号模式 + +```python +# apps/users/signals.py +from django.db.models.signals import post_save +from django.dispatch import receiver +from django.contrib.auth import get_user_model +from .models import Profile + +User = get_user_model() + +@receiver(post_save, sender=User) +def create_user_profile(sender, instance, created, **kwargs): + """当用户创建时同步创建 Profile。""" + if created: + Profile.objects.create(user=instance) + +@receiver(post_save, sender=User) +def save_user_profile(sender, instance, **kwargs): + """当用户保存时同步保存 Profile。""" + instance.profile.save() + +# apps/users/apps.py +from django.apps import AppConfig + +class UsersConfig(AppConfigConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.users' + + def ready(self): + """在应用准备就绪时导入信号。""" + import apps.users.signals +``` + +## 中间件(Middleware) + +### 自定义中间件 + +```python +# middleware/active_user_middleware.py +import time +from django.utils.deprecation import MiddlewareMixin + +class ActiveUserMiddleware(MiddlewareMixin): + """用于跟踪活跃用户的中间件。""" + + def process_request(self, request): + """处理传入请求。""" + if request.user.is_authenticated: + # 更新最后活跃时间 + request.user.last_active = timezone.now() + request.user.save(update_fields=['last_active']) + +class RequestLoggingMiddleware(MiddlewareMixin): + """用于记录请求日志的中间件。""" + + def process_request(self, request): + """记录请求开始时间。""" + request.start_time = time.time() + + def process_response(self, request, response): + """记录请求耗时。""" + if hasattr(request, 'start_time'): + duration = time.time() - request.start_time + logger.info(f'{request.method} {request.path} - {response.status_code} - {duration:.3f}s') + return response +``` + +## 性能优化(Performance Optimization) + +### 防止 N+1 查询 + +```python +# 差劲 - N+1 查询 +products = Product.objects.all() +for product in products: + print(product.category.name) # 为每个产品单独进行一次查询 + +# 优秀 - 使用 select_related 进行单次查询 +products = Product.objects.select_related('category').all() +for product in products: + print(product.category.name) + +# 优秀 - 对多对多关系使用 prefetch_related +products = Product.objects.prefetch_related('tags').all() +for product in products: + for tag in product.tags.all(): + print(tag.name) +``` + +### 数据库索引 + +```python +class Product(models.Model): + name = models.CharField(max_length=200, db_index=True) + slug = models.SlugField(unique=True) + category = models.ForeignKey('Category', on_delete=models.CASCADE) + created_at = models.DateTimeField(auto_now_add=True) + + class Meta: + indexes = [ + models.Index(fields=['name']), + models.Index(fields=['-created_at']), + models.Index(fields=['category', 'created_at']), + ] +``` + +### 批量操作 + +```python +# 批量创建 +Product.objects.bulk_create([ + Product(name=f'Product {i}', price=10.00) + for i in range(1000) +]) + +# 批量更新 +products = Product.objects.all()[:100] +for product in products: + product.is_active = True +Product.objects.bulk_update(products, ['is_active']) + +# 批量删除 +Product.objects.filter(stock=0).delete() +``` + +## 快速参考 + +| 模式 | 描述 | +|---------|-------------| +| 分离设置(Split settings) | 区分开发/生产/测试环境配置 | +| 自定义 QuerySet | 可复用的查询方法 | +| 服务层(Service Layer) | 业务逻辑解耦 | +| 视图集(ViewSet) | REST API 端点封装 | +| 序列化器校验(Serializer validation) | 请求/响应数据的转换与验证 | +| select_related | 外键关系优化 | +| prefetch_related | 多对多关系优化 | +| 缓存优先(Cache first) | 对耗时操作进行缓存 | +| 信号(Signals) | 事件驱动的操作 | +| 中间件(Middleware) | 请求/响应拦截处理 | + +记住:Django 提供了许多捷径,但对于生产级应用,架构和组织结构比简洁的代码更重要。请为可维护性而构建。 diff --git a/skills/django-security/SKILL.md b/skills/django-security/SKILL.md new file mode 100644 index 0000000..3c6907d --- /dev/null +++ b/skills/django-security/SKILL.md @@ -0,0 +1,592 @@ +--- +name: django-security +description: Django 安全最佳实践,涵盖身份认证、授权、CSRF 防护、SQL 注入预防、XSS 预防以及安全的部署配置。 +--- + +# Django 安全最佳实践 + +针对 Django 应用程序的全面安全指南,旨在抵御常见的漏洞。 + +## 何时激活 + +- 设置 Django 身份认证(Authentication)和授权(Authorization)时 +- 实现用户权限和角色时 +- 配置生产环境安全设置时 +- 审查 Django 应用程序的安全问题时 +- 将 Django 应用程序部署到生产环境时 + +## 核心安全设置 + +### 生产环境设置配置 + +```python +# settings/production.py +import os + +DEBUG = False # 关键:切勿在生产环境中使用 True + +ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',') + +# 安全头部(Security headers) +SECURE_SSL_REDIRECT = True +SESSION_COOKIE_SECURE = True +CSRF_COOKIE_SECURE = True +SECURE_HSTS_SECONDS = 31536000 # 1 年 +SECURE_HSTS_INCLUDE_SUBDOMAINS = True +SECURE_HSTS_PRELOAD = True +SECURE_CONTENT_TYPE_NOSNIFF = True +SECURE_BROWSER_XSS_FILTER = True +X_FRAME_OPTIONS = 'DENY' + +# HTTPS 与 Cookie +SESSION_COOKIE_HTTPONLY = True +CSRF_COOKIE_HTTPONLY = True +SESSION_COOKIE_SAMESITE = 'Lax' +CSRF_COOKIE_SAMESITE = 'Lax' + +# 密钥(必须通过环境变量设置) +SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY') +if not SECRET_KEY: + raise ImproperlyConfigured('DJANGO_SECRET_KEY environment variable is required') + +# 密码校验 +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + 'OPTIONS': { + 'min_length': 12, + } + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] +``` + +## 身份认证(Authentication) + +### 自定义用户模型(Custom User Model) + +```python +# apps/users/models.py +from django.contrib.auth.models import AbstractUser +from django.db import models + +class User(AbstractUser): + """为了更好的安全性而自定义的用户模型。""" + + email = models.EmailField(unique=True) + phone = models.CharField(max_length=20, blank=True) + + USERNAME_FIELD = 'email' # 使用邮箱作为用户名 + REQUIRED_FIELDS = ['username'] + + class Meta: + db_table = 'users' + verbose_name = 'User' + verbose_name_plural = 'Users' + + def __str__(self): + return self.email + +# settings/base.py +AUTH_USER_MODEL = 'users.User' +``` + +### 密码哈希(Password Hashing) + +```python +# Django 默认使用 PBKDF2。为了更强的安全性: +PASSWORD_HASHERS = [ + 'django.contrib.auth.hashers.Argon2PasswordHasher', + 'django.contrib.auth.hashers.PBKDF2PasswordHasher', + 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', + 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher', +] +``` + +### 会话管理(Session Management) + +```python +# 会话配置 +SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 或 'db' +SESSION_CACHE_ALIAS = 'default' +SESSION_COOKIE_AGE = 3600 * 24 * 7 # 1 周 +SESSION_SAVE_EVERY_REQUEST = False +SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 更好的用户体验,但安全性略低 +``` + +## 授权(Authorization) + +### 权限(Permissions) + +```python +# models.py +from django.db import models +from django.contrib.auth.models import Permission + +class Post(models.Model): + title = models.CharField(max_length=200) + content = models.TextField() + author = models.ForeignKey(User, on_delete=models.CASCADE) + + class Meta: + permissions = [ + ('can_publish', '可以发布帖子'), + ('can_edit_others', '可以编辑他人的帖子'), + ] + + def user_can_edit(self, user): + """检查用户是否可以编辑此帖子。""" + return self.author == user or user.has_perm('app.can_edit_others') + +# views.py +from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin +from django.views.generic import UpdateView + +class PostUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): + model = Post + permission_required = 'app.can_edit_others' + raise_exception = True # 返回 403 而不是重定向 + + def get_queryset(self): + """仅允许用户编辑自己的帖子。""" + return Post.objects.filter(author=self.request.user) +``` + +### 自定义权限 + +```python +# permissions.py +from rest_framework import permissions + +class IsOwnerOrReadOnly(permissions.BasePermission): + """仅允许所有者编辑对象。""" + + def has_object_permission(self, request, view, obj): + # 允许任何请求的读取权限 + if request.method in permissions.SAFE_METHODS: + return True + + # 仅所有者拥有写入权限 + return obj.author == request.user + +class IsAdminOrReadOnly(permissions.BasePermission): + """允许管理员执行任何操作,其他人只读。""" + + def has_permission(self, request, view): + if request.method in permissions.SAFE_METHODS: + return True + return request.user and request.user.is_staff + +class IsVerifiedUser(permissions.BasePermission): + """仅允许已验证的用户。""" + + def has_permission(self, request, view): + return request.user and request.user.is_authenticated and request.user.is_verified +``` + +### 基于角色的访问控制(RBAC) + +```python +# models.py +from django.contrib.auth.models import AbstractUser, Group + +class User(AbstractUser): + ROLE_CHOICES = [ + ('admin', '管理员'), + ('moderator', '版主'), + ('user', '普通用户'), + ] + role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='user') + + def is_admin(self): + return self.role == 'admin' or self.is_superuser + + def is_moderator(self): + return self.role in ['admin', 'moderator'] + +# 混入类(Mixins) +class AdminRequiredMixin: + """要求管理员角色的混入类。""" + + def dispatch(self, request, *args, **kwargs): + if not request.user.is_authenticated or not request.user.is_admin(): + from django.core.exceptions import PermissionDenied + raise PermissionDenied + return super().dispatch(request, *args, **kwargs) +``` + +## SQL 注入防护 + +### Django ORM 保护 + +```python +# 推荐:Django ORM 自动转义参数 +def get_user(username): + return User.objects.get(username=username) # 安全 + +# 推荐:在 raw() 中使用参数 +def search_users(query): + return User.objects.raw('SELECT * FROM users WHERE username = %s', [query]) + +# 错误:切勿直接插值用户输入 +def get_user_bad(username): + return User.objects.raw(f'SELECT * FROM users WHERE username = {username}') # 存在漏洞! + +# 推荐:使用带有正确转义的 filter +def get_users_by_email(email): + return User.objects.filter(email__iexact=email) # 安全 + +# 推荐:对复杂查询使用 Q 对象 +from django.db.models import Q +def search_users_complex(query): + return User.objects.filter( + Q(username__icontains=query) | + Q(email__icontains=query) + ) # 安全 +``` + +### 使用 raw() 时的额外安全措施 + +```python +# 如果必须使用原生 SQL,请务必使用参数 +User.objects.raw( + 'SELECT * FROM users WHERE email = %s AND status = %s', + [user_input_email, status] +) +``` + +## XSS 防护 + +### 模板转义 + +```django +{# Django 默认自动转义变量 - 安全 #} +{{ user_input }} {# 已转义的 HTML #} + +{# 仅对受信任的内容显式标记为 safe #} +{{ trusted_html|safe }} {# 未转义 #} + +{# 使用模板过滤器以获得安全的 HTML #} +{{ user_input|escape }} {# 与默认值相同 #} +{{ user_input|striptags }} {# 移除所有 HTML 标签 #} + +{# JavaScript 转义 #} + +``` + +### 安全字符串处理 + +```python +from django.utils.safestring import mark_safe +from django.utils.html import escape + +# 错误:在没有转义的情况下,切勿将用户输入标记为 safe +def render_bad(user_input): + return mark_safe(user_input) # 存在漏洞! + +# 推荐:先转义,然后标记为 safe +def render_good(user_input): + return mark_safe(escape(user_input)) + +# 推荐:对包含变量的 HTML 使用 format_html +from django.utils.html import format_html + +def greet_user(username): + return format_html('{}', escape(username)) +``` + +### HTTP 头部 + +```python +# settings.py +SECURE_CONTENT_TYPE_NOSNIFF = True # 防止 MIME 嗅探 +SECURE_BROWSER_XSS_FILTER = True # 启用 XSS 过滤器 +X_FRAME_OPTIONS = 'DENY' # 防止点击劫持 + +# 自定义中间件 +from django.conf import settings + +class SecurityHeaderMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + response = self.get_response(request) + response['X-Content-Type-Options'] = 'nosniff' + response['X-Frame-Options'] = 'DENY' + response['X-XSS-Protection'] = '1; mode=block' + response['Content-Security-Policy'] = "default-src 'self'" + return response +``` + +## CSRF 防护 + +### 默认 CSRF 防护 + +```python +# settings.py - CSRF 默认已启用 +CSRF_COOKIE_SECURE = True # 仅通过 HTTPS 发送 +CSRF_COOKIE_HTTPONLY = True # 防止 JavaScript 访问 +CSRF_COOKIE_SAMESITE = 'Lax' # 在某些情况下防止 CSRF +CSRF_TRUSTED_ORIGINS = ['https://example.com'] # 受信任的域 + +# 模板用法 +
+ {% csrf_token %} + {{ form.as_p }} + +
+ +# AJAX 请求 +function getCookie(name) { + let cookieValue = null; + if (document.cookie && document.cookie !== '') { + const cookies = document.cookie.split(';'); + for (let i = 0; i < cookies.length; i++) { + const cookie = cookies[i].trim(); + if (cookie.substring(0, name.length + 1) === (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; +} + +fetch('/api/endpoint/', { + method: 'POST', + headers: { + 'X-CSRFToken': getCookie('csrftoken'), + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data) +}); +``` + +### 豁免视图(请谨慎使用) + +```python +from django.views.decorators.csrf import csrf_exempt + +@csrf_exempt # 仅在绝对必要时使用! +def webhook_view(request): + # 来自外部服务的 Webhook + pass +``` + +## 文件上传安全 + +### 文件验证 + +```python +import os +from django.core.exceptions import ValidationError + +def validate_file_extension(value): + """验证文件扩展名。""" + ext = os.path.splitext(value.name)[1] + valid_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.pdf'] + if not ext.lower() in valid_extensions: + raise ValidationError('不支持的文件扩展名。') + +def validate_file_size(value): + """验证文件大小(最大 5MB)。""" + filesize = value.size + if filesize > 5 * 1024 * 1024: + raise ValidationError('文件过大。最大限制为 5MB。') + +# models.py +class Document(models.Model): + file = models.FileField( + upload_to='documents/', + validators=[validate_file_extension, validate_file_size] + ) +``` + +### 安全文件存储 + +```python +# settings.py +MEDIA_ROOT = '/var/www/media/' +MEDIA_URL = '/media/' + +# 生产环境中使用独立的媒体文件域名 +MEDIA_DOMAIN = 'https://media.example.com' + +# 不要直接提供用户上传的文件 +# 对静态文件使用 whitenoise 或 CDN +# 对媒体文件使用独立服务器或 S3 +``` + +## API 安全 + +### 速率限制(Rate Limiting) + +```python +# settings.py +REST_FRAMEWORK = { + 'DEFAULT_THROTTLE_CLASSES': [ + 'rest_framework.throttling.AnonRateThrottle', + 'rest_framework.throttling.UserRateThrottle' + ], + 'DEFAULT_THROTTLE_RATES': { + 'anon': '100/day', + 'user': '1000/day', + 'upload': '10/hour', + } +} + +# 自定义节流(Throttle) +from rest_framework.throttling import UserRateThrottle + +class BurstRateThrottle(UserRateThrottle): + scope = 'burst' + rate = '60/min' + +class SustainedRateThrottle(UserRateThrottle): + scope = 'sustained' + rate = '1000/day' +``` + +### API 身份认证 + +```python +# settings.py +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework.authentication.TokenAuthentication', + 'rest_framework.authentication.SessionAuthentication', + 'rest_framework_simplejwt.authentication.JWTAuthentication', + ], + 'DEFAULT_PERMISSION_CLASSES': [ + 'rest_framework.permissions.IsAuthenticated', + ], +} + +# views.py +from rest_framework.decorators import api_view, permission_classes +from rest_framework.permissions import IsAuthenticated + +@api_view(['GET', 'POST']) +@permission_classes([IsAuthenticated]) +def protected_view(request): + return Response({'message': 'You are authenticated'}) +``` + +## 安全头部(Security Headers) + +### 内容安全策略(CSP) + +```python +# settings.py +CSP_DEFAULT_SRC = "'self'" +CSP_SCRIPT_SRC = "'self' https://cdn.example.com" +CSP_STYLE_SRC = "'self' 'unsafe-inline'" +CSP_IMG_SRC = "'self' data: https:" +CSP_CONNECT_SRC = "'self' https://api.example.com" + +# 中间件 +class CSPMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + response = self.get_response(request) + response['Content-Security-Policy'] = ( + f"default-src {CSP_DEFAULT_SRC}; " + f"script-src {CSP_SCRIPT_SRC}; " + f"style-src {CSP_STYLE_SRC}; " + f"img-src {CSP_IMG_SRC}; " + f"connect-src {CSP_CONNECT_SRC}" + ) + return response +``` + +## 环境变量 + +### 管理密钥 + +```python +# 使用 python-decouple 或 django-environ +import environ + +env = environ.Env( + # 设置类型转换、默认值 + DEBUG=(bool, False) +) + +# 读取 .env 文件 +environ.Env.read_env() + +SECRET_KEY = env('DJANGO_SECRET_KEY') +DATABASE_URL = env('DATABASE_URL') +ALLOWED_HOSTS = env.list('ALLOWED_HOSTS') + +# .env 文件(切勿提交此文件) +DEBUG=False +SECRET_KEY=your-secret-key-here +DATABASE_URL=postgresql://user:password@localhost:5432/dbname +ALLOWED_HOSTS=example.com,www.example.com +``` + +## 记录安全事件 + +```python +# settings.py +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'handlers': { + 'file': { + 'level': 'WARNING', + 'class': 'logging.FileHandler', + 'filename': '/var/log/django/security.log', + }, + 'console': { + 'level': 'INFO', + 'class': 'logging.StreamHandler', + }, + }, + 'loggers': { + 'django.security': { + 'handlers': ['file', 'console'], + 'level': 'WARNING', + 'propagate': True, + }, + 'django.request': { + 'handlers': ['file'], + 'level': 'ERROR', + 'propagate': False, + }, + }, +} +``` + +## 快速安全自检表 + +| 检查项 | 描述 | +|-------|-------------| +| `DEBUG = False` | 切勿在生产环境中开启 DEBUG | +| 仅限 HTTPS | 强制使用 SSL,启用安全 Cookie | +| 强密钥 | 为 SECRET_KEY 使用环境变量 | +| 密码校验 | 启用所有密码验证器 | +| CSRF 防护 | 默认已启用,请勿禁用 | +| XSS 防护 | Django 自动转义,请勿对用户输入使用 `|safe` | +| SQL 注入 | 使用 ORM,切勿在查询中拼接字符串 | +| 文件上传 | 验证文件类型和大小 | +| 速率限制 | 对 API 端点进行节流 | +| 安全头部 | 配置 CSP, X-Frame-Options, HSTS | +| 日志记录 | 记录安全事件 | +| 更新 | 保持 Django 及其依赖项为最新版本 | + +记住:安全是一个持续的过程,而不是一个产品。请定期审查并更新你的安全实践。 diff --git a/skills/django-tdd/SKILL.md b/skills/django-tdd/SKILL.md new file mode 100644 index 0000000..7b5de79 --- /dev/null +++ b/skills/django-tdd/SKILL.md @@ -0,0 +1,728 @@ +--- +name: django-tdd +description: 使用 pytest-django、测试驱动开发(TDD)方法论、factory_boy、Mock 模拟、覆盖率统计以及 Django REST Framework API 测试的 Django 测试策略。 +--- + +# Django TDD 测试指南 + +使用 pytest、factory_boy 和 Django REST Framework 对 Django 应用进行测试驱动开发(TDD)。 + +## 何时激活 + +- 编写新的 Django 应用程序时 +- 实现 Django REST Framework API 时 +- 测试 Django 模型(Models)、视图(Views)和序列化器(Serializers)时 +- 为 Django 项目搭建测试基础设施时 + +## Django 的 TDD 工作流 + +### 红-绿-重构循环(Red-Green-Refactor Cycle) + +```python +# 步骤 1:红色(RED) - 编写失败的测试 +def test_user_creation(): + user = User.objects.create_user(email='test@example.com', password='testpass123') + assert user.email == 'test@example.com' + assert user.check_password('testpass123') + assert not user.is_staff + +# 步骤 2:绿色(GREEN) - 编写代码使测试通过 +# 创建 User 模型或工厂(Factory) + +# 步骤 3:重构(REFACTOR) - 在保持测试通过的前提下优化代码 +``` + +## 环境搭建 + +### pytest 配置 + +```ini +# pytest.ini +[pytest] +DJANGO_SETTINGS_MODULE = config.settings.test +testpaths = tests +python_files = test_*.py +python_classes = Test* +python_functions = test_* +addopts = + --reuse-db + --nomigrations + --cov=apps + --cov-report=html + --cov-report=term-missing + --strict-markers +markers = + slow: 标记为耗时较长的测试 + integration: 标记为集成测试 +``` + +### 测试设置(Settings) + +```python +# config/settings/test.py +from .base import * + +DEBUG = True +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': ':memory:', + } +} + +# 禁用迁移以提升速度 +class DisableMigrations: + def __contains__(self, item): + return True + + def __getitem__(self, item): + return None + +MIGRATION_MODULES = DisableMigrations() + +# 使用更快的密码哈希算法 +PASSWORD_HASHERS = [ + 'django.contrib.auth.hashers.MD5PasswordHasher', +] + +# 邮件后端配置 +EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' + +# Celery 始终同步执行 +CELERY_TASK_ALWAYS_EAGER = True +CELERY_TASK_EAGER_PROPAGATES = True +``` + +### conftest.py + +```python +# tests/conftest.py +import pytest +from django.utils import timezone +from django.contrib.auth import get_user_model + +User = get_user_model() + +@pytest.fixture(autouse=True) +def timezone_settings(settings): + """确保时区一致。""" + settings.TIME_ZONE = 'UTC' + +@pytest.fixture +def user(db): + """创建测试用户。""" + return User.objects.create_user( + email='test@example.com', + password='testpass123', + username='testuser' + ) + +@pytest.fixture +def admin_user(db): + """创建管理员用户。""" + return User.objects.create_superuser( + email='admin@example.com', + password='adminpass123', + username='admin' + ) + +@pytest.fixture +def authenticated_client(client, user): + """返回已认证的客户端。""" + client.force_login(user) + return client + +@pytest.fixture +def api_client(): + """返回 DRF API 客户端。""" + from rest_framework.test import APIClient + return APIClient() + +@pytest.fixture +def authenticated_api_client(api_client, user): + """返回已认证的 API 客户端。""" + api_client.force_authenticate(user=user) + return api_client +``` + +## Factory Boy + +### 工厂配置 + +```python +# tests/factories.py +import factory +from factory import fuzzy +from datetime import datetime, timedelta +from django.contrib.auth import get_user_model +from apps.products.models import Product, Category + +User = get_user_model() + +class UserFactory(factory.django.DjangoModelFactory): + """User 模型的工厂类。""" + + class Meta: + model = User + + email = factory.Sequence(lambda n: f"user{n}@example.com") + username = factory.Sequence(lambda n: f"user{n}") + password = factory.PostGenerationMethodCall('set_password', 'testpass123') + first_name = factory.Faker('first_name') + last_name = factory.Faker('last_name') + is_active = True + +class CategoryFactory(factory.django.DjangoModelFactory): + """Category 模型的工厂类。""" + + class Meta: + model = Category + + name = factory.Faker('word') + slug = factory.LazyAttribute(lambda obj: obj.name.lower()) + description = factory.Faker('text') + +class ProductFactory(factory.django.DjangoModelFactory): + """Product 模型的工厂类。""" + + class Meta: + model = Product + + name = factory.Faker('sentence', nb_words=3) + slug = factory.LazyAttribute(lambda obj: obj.name.lower().replace(' ', '-')) + description = factory.Faker('text') + price = fuzzy.FuzzyDecimal(10.00, 1000.00, 2) + stock = fuzzy.FuzzyInteger(0, 100) + is_active = True + category = factory.SubFactory(CategoryFactory) + created_by = factory.SubFactory(UserFactory) + + @factory.post_generation + def tags(self, create, extracted, **kwargs): + """为产品添加标签。""" + if not create: + return + if extracted: + for tag in extracted: + self.tags.add(tag) +``` + +### 使用工厂 + +```python +# tests/test_models.py +import pytest +from tests.factories import ProductFactory, UserFactory + +def test_product_creation(): + """测试使用工厂创建产品。""" + product = ProductFactory(price=100.00, stock=50) + assert product.price == 100.00 + assert product.stock == 50 + assert product.is_active is True + +def test_product_with_tags(): + """测试带有标签的产品。""" + tags = [TagFactory(name='electronics'), TagFactory(name='new')] + product = ProductFactory(tags=tags) + assert product.tags.count() == 2 + +def test_multiple_products(): + """测试创建多个产品。""" + products = ProductFactory.create_batch(10) + assert len(products) == 10 +``` + +## 模型测试(Model Testing) + +### 模型测试用例 + +```python +# tests/test_models.py +import pytest +from django.core.exceptions import ValidationError +from tests.factories import UserFactory, ProductFactory + +class TestUserModel: + """测试 User 模型。""" + + def test_create_user(self, db): + """测试创建普通用户。""" + user = UserFactory(email='test@example.com') + assert user.email == 'test@example.com' + assert user.check_password('testpass123') + assert not user.is_staff + assert not user.is_superuser + + def test_create_superuser(self, db): + """测试创建超级用户。""" + user = UserFactory( + email='admin@example.com', + is_staff=True, + is_superuser=True + ) + assert user.is_staff + assert user.is_superuser + + def test_user_str(self, db): + """测试用户字符串表示形式。""" + user = UserFactory(email='test@example.com') + assert str(user) == 'test@example.com' + +class TestProductModel: + """测试 Product 模型。""" + + def test_product_creation(self, db): + """测试创建产品。""" + product = ProductFactory() + assert product.id is not None + assert product.is_active is True + assert product.created_at is not None + + def test_product_slug_generation(self, db): + """测试 Slug 自动生成。""" + product = ProductFactory(name='Test Product') + assert product.slug == 'test-product' + + def test_product_price_validation(self, db): + """测试价格不能为负数。""" + product = ProductFactory(price=-10) + with pytest.raises(ValidationError): + product.full_clean() + + def test_product_manager_active(self, db): + """测试 active 管理器方法。""" + ProductFactory.create_batch(5, is_active=True) + ProductFactory.create_batch(3, is_active=False) + + active_count = Product.objects.active().count() + assert active_count == 5 + + def test_product_stock_management(self, db): + """测试库存管理。""" + product = ProductFactory(stock=10) + product.reduce_stock(5) + product.refresh_from_db() + assert product.stock == 5 + + with pytest.raises(ValueError): + product.reduce_stock(10) # 库存不足 +``` + +## 视图测试(View Testing) + +### Django 视图测试 + +```python +# tests/test_views.py +import pytest +from django.urls import reverse +from tests.factories import ProductFactory, UserFactory + +class TestProductViews: + """测试产品视图。""" + + def test_product_list(self, client, db): + """测试产品列表视图。""" + ProductFactory.create_batch(10) + + response = client.get(reverse('products:list')) + + assert response.status_code == 200 + assert len(response.context['products']) == 10 + + def test_product_detail(self, client, db): + """测试产品详情视图。""" + product = ProductFactory() + + response = client.get(reverse('products:detail', kwargs={'slug': product.slug})) + + assert response.status_code == 200 + assert response.context['product'] == product + + def test_product_create_requires_login(self, client, db): + """测试创建产品需要登录认证。""" + response = client.get(reverse('products:create')) + + assert response.status_code == 302 + assert response.url.startswith('/accounts/login/') + + def test_product_create_authenticated(self, authenticated_client, db): + """测试以已认证用户身份创建产品。""" + response = authenticated_client.get(reverse('products:create')) + + assert response.status_code == 200 + + def test_product_create_post(self, authenticated_client, db, category): + """测试通过 POST 请求创建产品。""" + data = { + 'name': 'Test Product', + 'description': 'A test product', + 'price': '99.99', + 'stock': 10, + 'category': category.id, + } + + response = authenticated_client.post(reverse('products:create'), data) + + assert response.status_code == 302 + assert Product.objects.filter(name='Test Product').exists() +``` + +## DRF API 测试 + +### 序列化器测试 + +```python +# tests/test_serializers.py +import pytest +from rest_framework.exceptions import ValidationError +from apps.products.serializers import ProductSerializer +from tests.factories import ProductFactory + +class TestProductSerializer: + """测试 ProductSerializer。""" + + def test_serialize_product(self, db): + """测试序列化产品对象。""" + product = ProductFactory() + serializer = ProductSerializer(product) + + data = serializer.data + + assert data['id'] == product.id + assert data['name'] == product.name + assert data['price'] == str(product.price) + + def test_deserialize_product(self, db): + """测试反序列化产品数据。""" + data = { + 'name': 'Test Product', + 'description': 'Test description', + 'price': '99.99', + 'stock': 10, + 'category': 1, + } + + serializer = ProductSerializer(data=data) + + assert serializer.is_valid() + product = serializer.save() + + assert product.name == 'Test Product' + assert float(product.price) == 99.99 + + def test_price_validation(self, db): + """测试价格验证。""" + data = { + 'name': 'Test Product', + 'price': '-10.00', + 'stock': 10, + } + + serializer = ProductSerializer(data=data) + + assert not serializer.is_valid() + assert 'price' in serializer.errors + + def test_stock_validation(self, db): + """测试库存不能为负数。""" + data = { + 'name': 'Test Product', + 'price': '99.99', + 'stock': -5, + } + + serializer = ProductSerializer(data=data) + + assert not serializer.is_valid() + assert 'stock' in serializer.errors +``` + +### API ViewSet 测试 + +```python +# tests/test_api.py +import pytest +from rest_framework.test import APIClient +from rest_framework import status +from django.urls import reverse +from tests.factories import ProductFactory, UserFactory + +class TestProductAPI: + """测试产品 API 接口。""" + + @pytest.fixture + def api_client(self): + """返回 API 客户端。""" + return APIClient() + + def test_list_products(self, api_client, db): + """测试列出产品。""" + ProductFactory.create_batch(10) + + url = reverse('api:product-list') + response = api_client.get(url) + + assert response.status_code == status.HTTP_200_OK + assert response.data['count'] == 10 + + def test_retrieve_product(self, api_client, db): + """测试获取单个产品详情。""" + product = ProductFactory() + + url = reverse('api:product-detail', kwargs={'pk': product.id}) + response = api_client.get(url) + + assert response.status_code == status.HTTP_200_OK + assert response.data['id'] == product.id + + def test_create_product_unauthorized(self, api_client, db): + """测试未授权时创建产品。""" + url = reverse('api:product-list') + data = {'name': 'Test Product', 'price': '99.99'} + + response = api_client.post(url, data) + + assert response.status_code == status.HTTP_401_UNAUTHORIZED + + def test_create_product_authorized(self, authenticated_api_client, db): + """测试以已认证用户身份创建产品。""" + url = reverse('api:product-list') + data = { + 'name': 'Test Product', + 'description': 'Test', + 'price': '99.99', + 'stock': 10, + } + + response = authenticated_api_client.post(url, data) + + assert response.status_code == status.HTTP_201_CREATED + assert response.data['name'] == 'Test Product' + + def test_update_product(self, authenticated_api_client, db): + """测试更新产品。""" + product = ProductFactory(created_by=authenticated_api_client.user) + + url = reverse('api:product-detail', kwargs={'pk': product.id}) + data = {'name': 'Updated Product'} + + response = authenticated_api_client.patch(url, data) + + assert response.status_code == status.HTTP_200_OK + assert response.data['name'] == 'Updated Product' + + def test_delete_product(self, authenticated_api_client, db): + """测试删除产品。""" + product = ProductFactory(created_by=authenticated_api_client.user) + + url = reverse('api:product-detail', kwargs={'pk': product.id}) + response = authenticated_api_client.delete(url) + + assert response.status_code == status.HTTP_204_NO_CONTENT + + def test_filter_products_by_price(self, api_client, db): + """测试按价格过滤产品。""" + ProductFactory(price=50) + ProductFactory(price=150) + + url = reverse('api:product-list') + response = api_client.get(url, {'price_min': 100}) + + assert response.status_code == status.HTTP_200_OK + assert response.data['count'] == 1 + + def test_search_products(self, api_client, db): + """测试搜索产品。""" + ProductFactory(name='Apple iPhone') + ProductFactory(name='Samsung Galaxy') + + url = reverse('api:product-list') + response = api_client.get(url, {'search': 'Apple'}) + + assert response.status_code == status.HTTP_200_OK + assert response.data['count'] == 1 +``` + +## Mock 模拟与补丁(Mocking and Patching) + +### 模拟外部服务 + +```python +# tests/test_views.py +from unittest.mock import patch, Mock +import pytest + +class TestPaymentView: + """使用模拟支付网关测试支付视图。""" + + @patch('apps.payments.services.stripe') + def test_successful_payment(self, mock_stripe, client, user, product): + """使用模拟的 Stripe 测试成功支付。""" + # 配置模拟对象 + mock_stripe.Charge.create.return_value = { + 'id': 'ch_123', + 'status': 'succeeded', + 'amount': 9999, + } + + client.force_login(user) + response = client.post(reverse('payments:process'), { + 'product_id': product.id, + 'token': 'tok_visa', + }) + + assert response.status_code == 302 + mock_stripe.Charge.create.assert_called_once() + + @patch('apps.payments.services.stripe') + def test_failed_payment(self, mock_stripe, client, user, product): + """测试支付失败。""" + mock_stripe.Charge.create.side_effect = Exception('Card declined') + + client.force_login(user) + response = client.post(reverse('payments:process'), { + 'product_id': product.id, + 'token': 'tok_visa', + }) + + assert response.status_code == 302 + assert 'error' in response.url +``` + +### 模拟邮件发送 + +```python +# tests/test_email.py +from django.core import mail +from django.test import override_settings + +@override_settings(EMAIL_BACKEND='django.core.mail.backends.locmem.EmailBackend') +def test_order_confirmation_email(db, order): + """测试订单确认邮件。""" + order.send_confirmation_email() + + assert len(mail.outbox) == 1 + assert order.user.email in mail.outbox[0].to + assert 'Order Confirmation' in mail.outbox[0].subject +``` + +## 集成测试(Integration Testing) + +### 全流程测试 + +```python +# tests/test_integration.py +import pytest +from django.urls import reverse +from tests.factories import UserFactory, ProductFactory + +class TestCheckoutFlow: + """测试完整的结账流程。""" + + def test_guest_to_purchase_flow(self, client, db): + """测试从游客到购买完成的完整流程。""" + # 步骤 1:注册 + response = client.post(reverse('users:register'), { + 'email': 'test@example.com', + 'password': 'testpass123', + 'password_confirm': 'testpass123', + }) + assert response.status_code == 302 + + # 步骤 2:登录 + response = client.post(reverse('users:login'), { + 'email': 'test@example.com', + 'password': 'testpass123', + }) + assert response.status_code == 302 + + # 步骤 3:浏览产品 + product = ProductFactory(price=100) + response = client.get(reverse('products:detail', kwargs={'slug': product.slug})) + assert response.status_code == 200 + + # 步骤 4:添加到购物车 + response = client.post(reverse('cart:add'), { + 'product_id': product.id, + 'quantity': 1, + }) + assert response.status_code == 302 + + # 步骤 5:结账 + response = client.get(reverse('checkout:review')) + assert response.status_code == 200 + assert product.name in response.content.decode() + + # 步骤 6:完成购买 + with patch('apps.checkout.services.process_payment') as mock_payment: + mock_payment.return_value = True + response = client.post(reverse('checkout:complete')) + + assert response.status_code == 302 + assert Order.objects.filter(user__email='test@example.com').exists() +``` + +## 测试最佳实践 + +### 推荐做法(DO) + +- **使用工厂(Factories)**:避免手动创建对象。 +- **每个测试一个断言**:保持测试专注。 +- **描述性测试名称**:如 `test_user_cannot_delete_others_post`。 +- **测试边界情况**:空输入、None 值、边界条件。 +- **模拟(Mock)外部服务**:不要依赖外部 API。 +- **使用 Fixtures**:消除重复代码。 +- **测试权限控制**:确保授权逻辑正常工作。 +- **保持测试运行迅速**:使用 `--reuse-db` 和 `--nomigrations`。 + +### 避免做法(DON'T) + +- **不要测试 Django 内部机制**:相信 Django 自身已通过测试。 +- **不要测试第三方库代码**:相信库作者的测试。 +- **不要忽略失败的测试**:所有测试都必须通过。 +- **不要让测试产生依赖**:测试应该可以以任何顺序运行。 +- **不要过度模拟**:仅对外部依赖项进行 Mock。 +- **不要测试私有方法**:测试公共接口。 +- **不要使用生产数据库**:始终使用专用测试数据库。 + +## 覆盖率(Coverage) + +### 覆盖率配置 + +```bash +# 运行带有覆盖率统计的测试 +pytest --cov=apps --cov-report=html --cov-report=term-missing + +# 生成并查看 HTML 报告 +open htmlcov/index.html +``` + +### 覆盖率目标 + +| 组件 | 目标覆盖率 | +|-----------|-----------------| +| 模型 (Models) | 90%+ | +| 序列化器 (Serializers) | 85%+ | +| 视图 (Views) | 80%+ | +| 服务层 (Services) | 90%+ | +| 工具类 (Utilities) | 80%+ | +| 总体 (Overall) | 80%+ | + +## 快速参考 + +| 模式 | 用法 | +|---------|-------| +| `@pytest.mark.django_db` | 启用数据库访问 | +| `client` | Django 测试客户端 | +| `api_client` | DRF API 客户端 | +| `factory.create_batch(n)` | 创建多个对象 | +| `patch('module.function')` | 模拟外部依赖 | +| `override_settings` | 临时更改设置 | +| `force_authenticate()` | 在测试中绕过认证 | +| `assertRedirects` | 检查重定向 | +| `assertTemplateUsed` | 验证模板使用情况 | +| `mail.outbox` | 检查已发送的邮件 | + +请记住:测试即文档。良好的测试能够解释代码的预期工作方式。保持测试简单、易读且易于维护。 diff --git a/skills/django-verification/SKILL.md b/skills/django-verification/SKILL.md new file mode 100644 index 0000000..4a12e9f --- /dev/null +++ b/skills/django-verification/SKILL.md @@ -0,0 +1,460 @@ +--- +name: django-verification +description: Django 项目的验证循环(Verification loop):包含数据库迁移、代码检查、带覆盖率的测试、安全扫描,以及在发布或 PR 前的部署就绪检查。 +--- + +# Django 验证循环(Verification Loop) + +在提交 PR 之前、重大变更之后以及预部署(Pre-deploy)之前运行,以确保 Django 应用程序的质量和安全性。 + +## 阶段 1:环境检查 (Phase 1: Environment Check) + +```bash +# 验证 Python 版本 +python --version # 应符合项目要求 + +# 检查虚拟环境 +which python +pip list --outdated + +# 验证环境变量 +python -c "import os; import environ; print('DJANGO_SECRET_KEY set' if os.environ.get('DJANGO_SECRET_KEY') else 'MISSING: DJANGO_SECRET_KEY')" +``` + +如果环境配置错误,请停止并修复。 + +## 阶段 2:代码质量与格式化 (Phase 2: Code Quality & Formatting) + +```bash +# 类型检查 +mypy . --config-file pyproject.toml + +# 使用 ruff 进行 Lint 检查 +ruff check . --fix + +# 使用 black 进行格式化 +black . --check +black . # 自动修复 + +# 导入语句排序 +isort . --check-only +isort . # 自动修复 + +# Django 特有的检查 +python manage.py check --deploy +``` + +常见问题: +- 公共函数缺失类型提示(Type hints) +- 违反 PEP 8 格式规范 +- 未排序的导入语句 +- 生产环境配置中遗留了调试设置 + +## 阶段 3:数据库迁移 (Phase 3: Migrations) + +```bash +# 检查是否存在未应用的迁移 +python manage.py showmigrations + +# 创建缺失的迁移 +python manage.py makemigrations --check + +# 模拟迁移应用(Dry-run) +python manage.py migrate --plan + +# 应用迁移(测试环境) +python manage.py migrate + +# 检查迁移冲突 +python manage.py makemigrations --merge # 仅在存在冲突时运行 +``` + +报告内容: +- 待处理迁移的数量 +- 任何迁移冲突 +- 存在模型变更但未生成迁移 + +## 阶段 4:测试与覆盖率 (Phase 4: Tests + Coverage) + +```bash +# 使用 pytest 运行所有测试 +pytest --cov=apps --cov-report=html --cov-report=term-missing --reuse-db + +# 运行特定应用的测试 +pytest apps/users/tests/ + +# 使用标记运行 +pytest -m "not slow" # 跳过慢速测试 +pytest -m integration # 仅运行集成测试 + +# 覆盖率报告 +open htmlcov/index.html +``` + +报告内容: +- 测试统计:X 通过,Y 失败,Z 跳过 +- 总体覆盖率:XX% +- 各应用的覆盖率明细 + +覆盖率目标: + +| 组件 | 目标 | +|-----------|--------| +| 模型 (Models) | 90%+ | +| 序列化器 (Serializers) | 85%+ | +| 视图 (Views) | 80%+ | +| 服务 (Services) | 90%+ | +| 总体 | 80%+ | + +## 阶段 5:安全扫描 (Phase 5: Security Scan) + +```bash +# 依赖项漏洞扫描 +pip-audit +safety check --full-report + +# Django 安全检查 +python manage.py check --deploy + +# Bandit 安全 Linter +bandit -r . -f json -o bandit-report.json + +# 密钥扫描(如果安装了 gitleaks) +gitleaks detect --source . --verbose + +# 环境变量检查 +python -c "from django.core.exceptions import ImproperlyConfigured; from django.conf import settings; settings.DEBUG" +``` + +报告内容: +- 发现的有漏洞的依赖项 +- 安全配置问题 +- 检测到的硬编码密钥 +- DEBUG 模式状态(在生产环境中应为 False) + +## 阶段 6:Django 管理命令 (Phase 6: Django Management Commands) + +```bash +# 检查模型问题 +python manage.py check + +# 收集静态文件 +python manage.py collectstatic --noinput --clear + +# 创建超级用户(如果测试需要) +echo "from apps.users.models import User; User.objects.create_superuser('admin@example.com', 'admin')" | python manage.py shell + +# 数据库完整性检查 +python manage.py check --database default + +# 缓存验证(如果使用 Redis) +python -c "from django.core.cache import cache; cache.set('test', 'value', 10); print(cache.get('test'))" +``` + +## 阶段 7:性能检查 (Phase 7: Performance Checks) + +```bash +# Django Debug Toolbar 输出(检查 N+1 查询) +# 在 DEBUG=True 的开发模式下运行并访问页面 +# 在 SQL 面板中查找重复查询 + +# 查询计数分析 +django-admin debugsqlshell # 如果安装了 django-debug-sqlshell + +# 检查缺失的索引 +python manage.py shell << EOF +from django.db import connection +with connection.cursor() as cursor: + cursor.execute("SELECT table_name, index_name FROM information_schema.statistics WHERE table_schema = 'public'") + print(cursor.fetchall()) +EOF +``` + +报告内容: +- 每个页面的查询数量(典型页面应 < 50) +- 缺失的数据库索引 +- 检测到的重复查询 + +## 阶段 8:静态资源 (Phase 8: Static Assets) + +```bash +# 检查 npm 依赖项(如果使用 npm) +npm audit +npm audit fix + +# 构建静态文件(如果使用 webpack/vite) +npm run build + +# 验证静态文件 +ls -la staticfiles/ +python manage.py findstatic css/style.css +``` + +## 阶段 9:配置审查 (Phase 9: Configuration Review) + +```python +# 在 Python shell 中运行以验证设置 +python manage.py shell << EOF +from django.conf import settings +import os + +# 关键检查项 +checks = { + 'DEBUG is False': not settings.DEBUG, + 'SECRET_KEY set': bool(settings.SECRET_KEY and len(settings.SECRET_KEY) > 30), + 'ALLOWED_HOSTS set': len(settings.ALLOWED_HOSTS) > 0, + 'HTTPS enabled': getattr(settings, 'SECURE_SSL_REDIRECT', False), + 'HSTS enabled': getattr(settings, 'SECURE_HSTS_SECONDS', 0) > 0, + 'Database configured': settings.DATABASES['default']['ENGINE'] != 'django.db.backends.sqlite3', +} + +for check, result in checks.items(): + status = '✓' if result else '✗' + print(f"{status} {check}") +EOF +``` + +## 阶段 10:日志配置 (Phase 10: Logging Configuration) + +```bash +# 测试日志输出 +python manage.py shell << EOF +import logging +logger = logging.getLogger('django') +logger.warning('Test warning message') +logger.error('Test error message') +EOF + +# 检查日志文件(如果已配置) +tail -f /var/log/django/django.log +``` + +## 阶段 11:API 文档 (Phase 11: API Documentation) + +```bash +# 生成 Schema +python manage.py generateschema --format openapi-json > schema.json + +# 验证 Schema +# 检查 schema.json 是否为有效的 JSON +python -c "import json; json.load(open('schema.json'))" + +# 访问 Swagger UI(如果使用 drf-yasg) +# 在浏览器中访问 http://localhost:8000/swagger/ +``` + +## 阶段 12:差异审查 (Phase 12: Diff Review) + +```bash +# 显示 diff 统计信息 +git diff --stat + +# 显示实际变更 +git diff + +# 显示已变更的文件 +git diff --name-only + +# 检查常见问题 +git diff | grep -i "todo\|fixme\|hack\|xxx" +git diff | grep "print(" # 调试语句 +git diff | grep "DEBUG = True" # 调试模式 +git diff | grep "import pdb" # 调试器 +``` + +检查清单: +- 无调试语句(print, pdb, breakpoint()) +- 关键代码中无 TODO/FIXME 注释 +- 无硬编码的密钥或凭据 +- 模型变更已包含数据库迁移 +- 配置变更已记录文档 +- 外部调用已包含错误处理 +- 视需要包含事务管理(Transaction management) + +## 输出模板 (Output Template) + +``` +DJANGO 验证报告 +========================== + +阶段 1:环境检查 (Phase 1: Environment Check) + ✓ Python 3.11.5 + ✓ 虚拟环境已激活 + ✓ 所有环境变量已设置 + +阶段 2:代码质量 (Phase 2: Code Quality) + ✓ mypy: 无类型错误 + ✗ ruff: 发现 3 个问题(已自动修复) + ✓ black: 无格式化问题 + ✓ isort: 导入语句已排序 + ✓ manage.py check: 无问题 + +阶段 3:数据库迁移 (Phase 3: Migrations) + ✓ 无未应用的迁移 + ✓ 无迁移冲突 + ✓ 所有模型均有迁移 + +阶段 4:测试与覆盖率 (Phase 4: Tests + Coverage) + 测试:247 通过,0 失败,5 跳过 + 覆盖率: + 总体:87% + users: 92% + products: 89% + orders: 85% + payments: 91% + +阶段 5:安全扫描 (Phase 5: Security Scan) + ✗ pip-audit: 发现 2 个漏洞(需要修复) + ✓ safety check: 无问题 + ✓ bandit: 无安全问题 + ✓ 未检测到密钥 + ✓ DEBUG = False + +阶段 6:Django 命令 (Phase 6: Django Commands) + ✓ collectstatic 已完成 + ✓ 数据库完整性正常 + ✓ 缓存后端可连接 + +阶段 7:性能 (Phase 7: Performance) + ✓ 未检测到 N+1 查询 + ✓ 数据库索引已配置 + ✓ 查询计数在可接受范围内 + +阶段 8:静态资源 (Phase 8: Static Assets) + ✓ npm audit: 无漏洞 + ✓ 资源构建成功 + ✓ 静态文件已收集 + +阶段 9:配置审查 (Phase 9: Configuration) + ✓ DEBUG = False + ✓ SECRET_KEY 已配置 + ✓ ALLOWED_HOSTS 已设置 + ✓ HTTPS 已启用 + ✓ HSTS 已启用 + ✓ 数据库已配置 + +阶段 10:日志 (Phase 10: Logging) + ✓ 日志已配置 + ✓ 日志文件可写 + +阶段 11:API 文档 (Phase 11: API Documentation) + ✓ Schema 已生成 + ✓ Swagger UI 可访问 + +阶段 12:差异审查 (Phase 12: Diff Review) + 文件变更:12 + +450, -120 行 + ✓ 无调试语句 + ✓ 无硬编码密钥 + ✓ 已包含迁移 + +建议:⚠️ 在部署前修复 pip-audit 漏洞 + +后续步骤: +1. 更新有漏洞的依赖项 +2. 重新运行安全扫描 +3. 部署到预发布环境进行最终测试 +``` + +## 预部署检查清单 (Pre-Deployment Checklist) + +- [ ] 所有测试均通过 +- [ ] 覆盖率 ≥ 80% +- [ ] 无安全漏洞 +- [ ] 无未应用的迁移 +- [ ] 生产环境设置中 DEBUG = False +- [ ] SECRET_KEY 已正确配置 +- [ ] ALLOWED_HOSTS 已正确设置 +- [ ] 数据库备份已启用 +- [ ] 静态文件已收集并提供服务 +- [ ] 日志已配置并正常工作 +- [ ] 错误监控(Sentry 等)已配置 +- [ ] CDN 已配置(如适用) +- [ ] Redis/缓存后端已配置 +- [ ] Celery worker 正在运行(如适用) +- [ ] HTTPS/SSL 已配置 +- [ ] 环境变量已记录文档 + +## 持续集成 (Continuous Integration) + +### GitHub Actions 示例 + +```yaml +# .github/workflows/django-verification.yml +name: Django Verification + +on: [push, pull_request] + +jobs: + verify: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:14 + env: + POSTGRES_PASSWORD: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Cache pip + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + + - name: Install dependencies + run: | + pip install -r requirements.txt + pip install ruff black mypy pytest pytest-django pytest-cov bandit safety pip-audit + + - name: Code quality checks + run: | + ruff check . + black . --check + isort . --check-only + mypy . + + - name: Security scan + run: | + bandit -r . -f json -o bandit-report.json + safety check --full-report + pip-audit + + - name: Run tests + env: + DATABASE_URL: postgres://postgres:postgres@localhost:5432/test + DJANGO_SECRET_KEY: test-secret-key + run: | + pytest --cov=apps --cov-report=xml --cov-report=term-missing + + - name: Upload coverage + uses: codecov/codecov-action@v3 +``` + +## 快速参考 (Quick Reference) + +| 检查项 | 命令 | +|-------|---------| +| 环境 (Environment) | `python --version` | +| 类型检查 (Type checking) | `mypy .` | +| 代码检查 (Linting) | `ruff check .` | +| 格式化 (Formatting) | `black . --check` | +| 数据库迁移 (Migrations) | `python manage.py makemigrations --check` | +| 测试 (Tests) | `pytest --cov=apps` | +| 安全 (Security) | `pip-audit && bandit -r .` | +| Django 检查 (Django check) | `python manage.py check --deploy` | +| 收集静态资源 (Collectstatic) | `python manage.py collectstatic --noinput` | +| 差异统计 (Diff stats) | `git diff --stat` | + +记住:自动化验证可以捕捉常见问题,但不能取代手动代码审查和在预发布环境(Staging environment)中的测试。 diff --git a/skills/golang-patterns/SKILL.md b/skills/golang-patterns/SKILL.md index 8029762..d97ee0c 100644 --- a/skills/golang-patterns/SKILL.md +++ b/skills/golang-patterns/SKILL.md @@ -672,4 +672,4 @@ func (c *Counter) Increment() { c.n++ } // 指针接收者 **记住**:Go 代码应当以一种“最乏味”的方式呈现——它是可预测的、一致的且易于理解的。如有疑问,请保持简单。 -``` +``` \ No newline at end of file diff --git a/skills/java-coding-standards/SKILL.md b/skills/java-coding-standards/SKILL.md index 9a03a41..d8b266f 100644 --- a/skills/java-coding-standards/SKILL.md +++ b/skills/java-coding-standards/SKILL.md @@ -1,91 +1,91 @@ --- name: java-coding-standards -description: Java coding standards for Spring Boot services: naming, immutability, Optional usage, streams, exceptions, generics, and project layout. +description: Spring Boot 服务的 Java 编码规范:命名、不可变性(Immutability)、Optional 使用、流(Streams)、异常处理、泛型(Generics)和项目布局。 --- -# Java Coding Standards +# Java 编码规范 -Standards for readable, maintainable Java (17+) code in Spring Boot services. +适用于 Spring Boot 服务中易读、可维护的 Java (17+) 代码规范。 -## Core Principles +## 核心原则 -- Prefer clarity over cleverness -- Immutable by default; minimize shared mutable state -- Fail fast with meaningful exceptions -- Consistent naming and package structure +- 清晰胜过奇巧 +- 默认不可变(Immutable);尽量减少共享的可变状态 +- 快速失败并抛出有意义的异常 +- 保持一致的命名和包结构 -## Naming +## 命名 ```java -// ✅ Classes/Records: PascalCase +// ✅ 类(Classes)/ 记录(Records):大驼峰式(PascalCase) public class MarketService {} public record Money(BigDecimal amount, Currency currency) {} -// ✅ Methods/fields: camelCase +// ✅ 方法/字段:小驼峰式(camelCase) private final MarketRepository marketRepository; public Market findBySlug(String slug) {} -// ✅ Constants: UPPER_SNAKE_CASE +// ✅ 常量:大写下划线命名式(UPPER_SNAKE_CASE) private static final int MAX_PAGE_SIZE = 100; ``` -## Immutability +## 不可变性(Immutability) ```java -// ✅ Favor records and final fields +// ✅ 优先使用记录(Records)和 final 字段 public record MarketDto(Long id, String name, MarketStatus status) {} public class Market { private final Long id; private final String name; - // getters only, no setters + // 仅提供 getter,不提供 setter } ``` -## Optional Usage +## Optional 使用 ```java -// ✅ Return Optional from find* methods +// ✅ find* 方法返回 Optional Optional market = marketRepository.findBySlug(slug); -// ✅ Map/flatMap instead of get() +// ✅ 使用 map/flatMap 而不是 get() return market .map(MarketResponse::from) .orElseThrow(() -> new EntityNotFoundException("Market not found")); ``` -## Streams Best Practices +## 流(Streams)最佳实践 ```java -// ✅ Use streams for transformations, keep pipelines short +// ✅ 使用流进行转换,保持流水线简短 List names = markets.stream() .map(Market::name) .filter(Objects::nonNull) .toList(); -// ❌ Avoid complex nested streams; prefer loops for clarity +// ❌ 避免复杂的嵌套流;为了清晰起见,优先使用循环 ``` -## Exceptions +## 异常处理 -- Use unchecked exceptions for domain errors; wrap technical exceptions with context -- Create domain-specific exceptions (e.g., `MarketNotFoundException`) -- Avoid broad `catch (Exception ex)` unless rethrowing/logging centrally +- 领域错误使用非受检异常(Unchecked Exceptions);为技术异常包装上下文信息 +- 创建领域特定的异常(例如 `MarketNotFoundException`) +- 避免宽泛的 `catch (Exception ex)`,除非是进行集中式重抛(Rethrow)或日志记录 ```java throw new MarketNotFoundException(slug); ``` -## Generics and Type Safety +## 泛型与类型安全 -- Avoid raw types; declare generic parameters -- Prefer bounded generics for reusable utilities +- 避免原始类型(Raw Types);声明泛型参数 +- 可复用工具类优先使用有界泛型(Bounded Generics) ```java public Map indexById(Collection items) { ... } ``` -## Project Structure (Maven/Gradle) +## 项目结构 (Maven/Gradle) ``` src/main/java/com/example/app/ @@ -98,25 +98,25 @@ src/main/java/com/example/app/ util/ src/main/resources/ application.yml -src/test/java/... (mirrors main) +src/test/java/... (结构与 main 保持镜像) ``` -## Formatting and Style +## 格式与风格 -- Use 2 or 4 spaces consistently (project standard) -- One public top-level type per file -- Keep methods short and focused; extract helpers -- Order members: constants, fields, constructors, public methods, protected, private +- 一致地使用 2 或 4 个空格(遵循项目标准) +- 每个文件仅包含一个公共顶层类型 +- 保持方法简短且聚焦;提取辅助方法(Helper methods) +- 成员排序:常量、字段、构造函数、公共方法、受保护方法、私有方法 -## Code Smells to Avoid +## 需避免的代码坏味道(Code Smells) -- Long parameter lists → use DTO/builders -- Deep nesting → early returns -- Magic numbers → named constants -- Static mutable state → prefer dependency injection -- Silent catch blocks → log and act or rethrow +- 长参数列表 → 使用 DTO/构建器(Builders) +- 深层嵌套 → 尽早返回(Early returns) +- 魔法数字 → 具名常量 +- 静态可变状态 → 优先使用依赖注入(Dependency Injection) +- 静默的 catch 块 → 记录日志并处理或重抛 -## Logging +## 日志记录 ```java private static final Logger log = LoggerFactory.getLogger(MarketService.class); @@ -124,15 +124,15 @@ log.info("fetch_market slug={}", slug); log.error("failed_fetch_market slug={}", slug, ex); ``` -## Null Handling +## 空值处理(Null Handling) -- Accept `@Nullable` only when unavoidable; otherwise use `@NonNull` -- Use Bean Validation (`@NotNull`, `@NotBlank`) on inputs +- 仅在不可避免时接受 `@Nullable`;否则使用 `@NonNull` +- 在输入上使用 Bean 校验(`@NotNull`, `@NotBlank`) -## Testing Expectations +## 测试预期 -- JUnit 5 + AssertJ for fluent assertions -- Mockito for mocking; avoid partial mocks where possible -- Favor deterministic tests; no hidden sleeps +- 使用 JUnit 5 + AssertJ 进行流式断言(Fluent Assertions) +- 使用 Mockito 进行模拟(Mocking);尽可能避免部分模拟(Partial Mocks) +- 倾向于确定性测试;不要包含隐藏的 sleep 操作 -**Remember**: Keep code intentional, typed, and observable. Optimize for maintainability over micro-optimizations unless proven necessary. +**记住**:保持代码的意图清晰、类型安全且具有可观测性。除非证明有必要,否则应优先考虑可维护性而非微优化。 diff --git a/skills/jpa-patterns/SKILL.md b/skills/jpa-patterns/SKILL.md index 2bf3213..217541c 100644 --- a/skills/jpa-patterns/SKILL.md +++ b/skills/jpa-patterns/SKILL.md @@ -1,13 +1,13 @@ --- name: jpa-patterns -description: JPA/Hibernate patterns for entity design, relationships, query optimization, transactions, auditing, indexing, pagination, and pooling in Spring Boot. +description: Spring Boot 中用于实体设计、关联关系、查询优化、事务、审计、索引、分页和连接池的 JPA/Hibernate 模式。 --- -# JPA/Hibernate Patterns +# JPA/Hibernate 模式 -Use for data modeling, repositories, and performance tuning in Spring Boot. +用于 Spring Boot 中的数据建模、存储层(Repositories)开发和性能调优。 -## Entity Design +## 实体设计(Entity Design) ```java @Entity @@ -33,29 +33,29 @@ public class MarketEntity { } ``` -Enable auditing: +启用审计(Auditing): ```java @Configuration @EnableJpaAuditing class JpaConfig {} ``` -## Relationships and N+1 Prevention +## 关联关系与 N+1 问题预防 ```java @OneToMany(mappedBy = "market", cascade = CascadeType.ALL, orphanRemoval = true) private List positions = new ArrayList<>(); ``` -- Default to lazy loading; use `JOIN FETCH` in queries when needed -- Avoid `EAGER` on collections; use DTO projections for read paths +- 默认使用懒加载(Lazy loading);必要时在查询中使用 `JOIN FETCH` +- 避免在集合上使用立即加载(`EAGER`);对于读取路径,优先使用 DTO 投影(Projections) ```java @Query("select m from MarketEntity m left join fetch m.positions where m.id = :id") Optional findWithPositions(@Param("id") Long id); ``` -## Repository Patterns +## 存储层模式(Repository Patterns) ```java public interface MarketRepository extends JpaRepository { @@ -66,7 +66,7 @@ public interface MarketRepository extends JpaRepository { } ``` -- Use projections for lightweight queries: +- 使用投影(Projections)进行轻量级查询: ```java public interface MarketSummary { Long getId(); @@ -76,11 +76,11 @@ public interface MarketSummary { Page findAllBy(Pageable pageable); ``` -## Transactions +## 事务(Transactions) -- Annotate service methods with `@Transactional` -- Use `@Transactional(readOnly = true)` for read paths to optimize -- Choose propagation carefully; avoid long-running transactions +- 使用 `@Transactional` 注解 Service 方法 +- 在读取路径上使用 `@Transactional(readOnly = true)` 进行优化 +- 谨慎选择传播行为(Propagation);避免长事务 ```java @Transactional @@ -92,25 +92,25 @@ public Market updateStatus(Long id, MarketStatus status) { } ``` -## Pagination +## 分页(Pagination) ```java PageRequest page = PageRequest.of(pageNumber, pageSize, Sort.by("createdAt").descending()); Page markets = repo.findByStatus(MarketStatus.ACTIVE, page); ``` -For cursor-like pagination, include `id > :lastId` in JPQL with ordering. +对于游标式分页(Cursor-like pagination),请在 JPQL 中包含 `id > :lastId` 并配合排序。 -## Indexing and Performance +## 索引与性能 -- Add indexes for common filters (`status`, `slug`, foreign keys) -- Use composite indexes matching query patterns (`status, created_at`) -- Avoid `select *`; project only needed columns -- Batch writes with `saveAll` and `hibernate.jdbc.batch_size` +- 为常用过滤器(`status`、`slug`、外键)添加索引(Indexing) +- 使用匹配查询模式的复合索引(Composite indexes,如 `status, created_at`) +- 避免使用 `select *`;仅投影所需的列 +- 使用 `saveAll` 并配置 `hibernate.jdbc.batch_size` 进行批量写入 -## Connection Pooling (HikariCP) +## 连接池(Connection Pooling - HikariCP) -Recommended properties: +推荐属性: ``` spring.datasource.hikari.maximum-pool-size=20 spring.datasource.hikari.minimum-idle=5 @@ -118,24 +118,24 @@ spring.datasource.hikari.connection-timeout=30000 spring.datasource.hikari.validation-timeout=5000 ``` -For PostgreSQL LOB handling, add: +对于 PostgreSQL 的 LOB 处理,请添加: ``` spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true ``` -## Caching +## 缓存(Caching) -- 1st-level cache is per EntityManager; avoid keeping entities across transactions -- For read-heavy entities, consider second-level cache cautiously; validate eviction strategy +- 一级缓存(1st-level cache)是基于 EntityManager 的;避免跨事务保留实体 +- 对于读多写少的实体,谨慎考虑二级缓存(2nd-level cache);验证逐出策略(Eviction strategy) -## Migrations +## 数据迁移(Migrations) -- Use Flyway or Liquibase; never rely on Hibernate auto DDL in production -- Keep migrations idempotent and additive; avoid dropping columns without plan +- 使用 Flyway 或 Liquibase;在生产环境中绝不要依赖 Hibernate 的自动 DDL +- 保持迁移是幂等(Idempotent)且具有增量性的;避免在没有计划的情况下删除列 -## Testing Data Access +## 测试数据访问 -- Prefer `@DataJpaTest` with Testcontainers to mirror production -- Assert SQL efficiency using logs: set `logging.level.org.hibernate.SQL=DEBUG` and `logging.level.org.hibernate.orm.jdbc.bind=TRACE` for parameter values +- 优先使用 `@DataJpaTest` 配合 Testcontainers 来模拟生产环境 +- 使用日志断言 SQL 效率:设置 `logging.level.org.hibernate.SQL=DEBUG` 以及 `logging.level.org.hibernate.orm.jdbc.bind=TRACE` 以查看参数值 -**Remember**: Keep entities lean, queries intentional, and transactions short. Prevent N+1 with fetch strategies and projections, and index for your read/write paths. +**记住**:保持实体精简、查询意图明确且事务简短。通过抓取策略(Fetch strategies)和投影(Projections)来防止 N+1 问题,并针对读/写路径建立索引。 diff --git a/skills/python-patterns/SKILL.md b/skills/python-patterns/SKILL.md new file mode 100644 index 0000000..a1c05b3 --- /dev/null +++ b/skills/python-patterns/SKILL.md @@ -0,0 +1,751 @@ +--- +name: python-patterns +description: Pythonic idioms, PEP 8 standards, type hints, and best practices for building robust, efficient, and maintainable Python applications. +--- + +# Python 开发模式 (Python Development Patterns) + +构建健壮、高效且可维护的应用程序的 Pythonic 惯用模式和最佳实践。 + +## 激活时机 (When to Activate) + +- 编写新的 Python 代码时 +- 进行 Python 代码审查(Review)时 +- 重构现有的 Python 代码时 +- 设计 Python 包(Package)或模块(Module)时 + +## 核心原则 + +### 1. 可读性至上 (Readability Counts) + +Python 优先考虑可读性。代码应当直观且易于理解。 + +```python +# Good: 清晰且易读 +def get_active_users(users: list[User]) -> list[User]: + """仅从提供的列表中返回活跃用户。""" + return [user for user in users if user.is_active] + + +# Bad: 巧妙但令人困惑 +def get_active_users(u): + return [x for x in u if x.a] +``` + +### 2. 显式优于隐式 (Explicit is Better Than Implicit) + +避免“魔法”行为;清晰地表达代码的功能。 + +```python +# Good: 显式配置 +import logging + +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) + +# Bad: 隐藏的副作用 +import some_module +some_module.setup() # 这到底做了什么? +``` + +### 3. EAFP 模式 - 请求宽恕比请求许可更容易 (Easier to Ask Forgiveness Than Permission) + +Python 倾向于使用异常处理而非预先检查条件。 + +```python +# Good: EAFP 风格 +def get_value(dictionary: dict, key: str) -> Any: + try: + return dictionary[key] + except KeyError: + return default_value + +# Bad: LBYL (Look Before You Leap,三思而后行) 风格 +def get_value(dictionary: dict, key: str) -> Any: + if key in dictionary: + return dictionary[key] + else: + return default_value +``` + +## 类型提示 (Type Hints) + +### 基础类型注解 + +```python +from typing import Optional, List, Dict, Any + +def process_user( + user_id: str, + data: Dict[str, Any], + active: bool = True +) -> Optional[User]: + """处理用户并返回更新后的 User 或 None。""" + if not active: + return None + return User(user_id, data) +``` + +### 现代类型提示 (Python 3.9+) + +```python +# Python 3.9+ - 使用内置类型 +def process_items(items: list[str]) -> dict[str, int]: + return {item: len(item) for item in items} + +# Python 3.8 及更早版本 - 使用 typing 模块 +from typing import List, Dict + +def process_items(items: List[str]) -> Dict[str, int]: + return {item: len(item) for item in items} +``` + +### 类型别名 (Type Aliases) 和 TypeVar + +```python +from typing import TypeVar, Union + +# 复杂类型的类型别名 +JSON = Union[dict[str, Any], list[Any], str, int, float, bool, None] + +def parse_json(data: str) -> JSON: + return json.loads(data) + +# 泛型 +T = TypeVar('T') + +def first(items: list[T]) -> T | None: + """返回第一项,如果列表为空则返回 None。""" + return items[0] if items else None +``` + +### 基于协议 (Protocol) 的鸭子类型 (Duck Typing) + +```python +from typing import Protocol + +class Renderable(Protocol): + def render(self) -> str: + """将对象渲染为字符串。""" + +def render_all(items: list[Renderable]) -> str: + """渲染所有实现了 Renderable 协议的项目。""" + return "\n".join(item.render() for item in items) +``` + +## 异常处理模式 (Error Handling Patterns) + +### 特定异常处理 + +```python +# Good: 捕获特定的异常 +def load_config(path: str) -> Config: + try: + with open(path) as f: + return Config.from_json(f.read()) + except FileNotFoundError as e: + raise ConfigError(f"Config file not found: {path}") from e + except json.JSONDecodeError as e: + raise ConfigError(f"Invalid JSON in config: {path}") from e + +# Bad: 空异常捕获 +def load_config(path: str) -> Config: + try: + with open(path) as f: + return Config.from_json(f.read()) + except: + return None # 静默失败! +``` + +### 异常链 (Exception Chaining) + +```python +def process_data(data: str) -> Result: + try: + parsed = json.loads(data) + except json.JSONDecodeError as e: + # 使用异常链以保留堆栈跟踪 (traceback) + raise ValueError(f"Failed to parse data: {data}") from e +``` + +### 自定义异常层次结构 + +```python +class AppError(Exception): + """所有应用程序错误的基类。""" + pass + +class ValidationError(AppError): + """当输入验证失败时引发。""" + pass + +class NotFoundError(AppError): + """当请求的资源未找到时引发。""" + pass + +# 使用示例 +def get_user(user_id: str) -> User: + user = db.find_user(user_id) + if not user: + raise NotFoundError(f"User not found: {user_id}") + return user +``` + +## 上下文管理器 (Context Managers) + +### 资源管理 + +```python +# Good: 使用上下文管理器 +def process_file(path: str) -> str: + with open(path, 'r') as f: + return f.read() + +# Bad: 手动资源管理 +def process_file(path: str) -> str: + f = open(path, 'r') + try: + return f.read() + finally: + f.close() +``` + +### 自定义上下文管理器 + +```python +from contextlib import contextmanager + +@contextmanager +def timer(name: str): + """用于对代码块计时的上下文管理器。""" + start = time.perf_counter() + yield + elapsed = time.perf_counter() - start + print(f"{name} took {elapsed:.4f} seconds") + +# 使用示例 +with timer("data processing"): + process_large_dataset() +``` + +### 上下文管理器类 + +```python +class DatabaseTransaction: + def __init__(self, connection): + self.connection = connection + + def __enter__(self): + self.connection.begin_transaction() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if exc_type is None: + self.connection.commit() + else: + self.connection.rollback() + return False # 不要抑制异常 + +# 使用示例 +with DatabaseTransaction(conn): + user = conn.create_user(user_data) + conn.create_profile(user.id, profile_data) +``` + +## 推导式 (Comprehensions) 与生成器 (Generators) + +### 列表推导式 (List Comprehensions) + +```python +# Good: 用于简单转换的列表推导式 +names = [user.name for user in users if user.is_active] + +# Bad: 手动循环 +names = [] +for user in users: + if user.is_active: + names.append(user.name) + +# 复杂的推导式应当展开 +# Bad: 太过复杂 +result = [x * 2 for x in items if x > 0 if x % 2 == 0] + +# Good: 使用生成器函数 +def filter_and_transform(items: Iterable[int]) -> list[int]: + result = [] + for x in items: + if x > 0 and x % 2 == 0: + result.append(x * 2) + return result +``` + +### 生成器表达式 (Generator Expressions) + +```python +# Good: 用于延迟求值的生成器 +total = sum(x * x for x in range(1_000_000)) + +# Bad: 创建了巨大的中间列表 +total = sum([x * x for x in range(1_000_000)]) +``` + +### 生成器函数 (Generator Functions) + +```python +def read_large_file(path: str) -> Iterator[str]: + """逐行读取大文件。""" + with open(path) as f: + for line in f: + yield line.strip() + +# 使用示例 +for line in read_large_file("huge.txt"): + process(line) +``` + +## 数据类 (Data Classes) 与命名元组 (Named Tuples) + +### 数据类 (Data Classes) + +```python +from dataclasses import dataclass, field +from datetime import datetime + +@dataclass +class User: + """具有自动生成 __init__、__repr__ 和 __eq__ 的用户实体。""" + id: str + name: str + email: str + created_at: datetime = field(default_factory=datetime.now) + is_active: bool = True + +# 使用示例 +user = User( + id="123", + name="Alice", + email="alice@example.com" +) +``` + +### 带验证的数据类 + +```python +@dataclass +class User: + email: str + age: int + + def __post_init__(self): + # 验证电子邮件格式 + if "@" not in self.email: + raise ValueError(f"Invalid email: {self.email}") + # 验证年龄范围 + if self.age < 0 or self.age > 150: + raise ValueError(f"Invalid age: {self.age}") +``` + +### 命名元组 (Named Tuples) + +```python +from typing import NamedTuple + +class Point(NamedTuple): + """不可变的二维点。""" + x: float + y: float + + def distance(self, other: 'Point') -> float: + return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5 + +# 使用示例 +p1 = Point(0, 0) +p2 = Point(3, 4) +print(p1.distance(p2)) # 5.0 +``` + +## 装饰器 (Decorators) + +### 函数装饰器 + +```python +import functools +import time + +def timer(func: Callable) -> Callable: + """用于对函数执行进行计时的装饰器。""" + @functools.wraps(func) + def wrapper(*args, **kwargs): + start = time.perf_counter() + result = func(*args, **kwargs) + elapsed = time.perf_counter() - start + print(f"{func.__name__} took {elapsed:.4f}s") + return result + return wrapper + +@timer +def slow_function(): + time.sleep(1) + +# slow_function() 输出: slow_function took 1.0012s +``` + +### 参数化装饰器 + +```python +def repeat(times: int): + """用于多次重复执行函数的装饰器。""" + def decorator(func: Callable) -> Callable: + @functools.wraps(func) + def wrapper(*args, **kwargs): + results = [] + for _ in range(times): + results.append(func(*args, **kwargs)) + return results + return wrapper + return decorator + +@repeat(times=3) +def greet(name: str) -> str: + return f"Hello, {name}!" + +# greet("Alice") 返回 ["Hello, Alice!", "Hello, Alice!", "Hello, Alice!"] +``` + +### 基于类的装饰器 + +```python +class CountCalls: + """统计函数被调用次数的装饰器。""" + def __init__(self, func: Callable): + functools.update_wrapper(self, func) + self.func = func + self.count = 0 + + def __call__(self, *args, **kwargs): + self.count += 1 + print(f"{self.func.__name__} has been called {self.count} times") + return self.func(*args, **kwargs) + +@CountCalls +def process(): + pass + +# 每次调用 process() 都会打印调用计数 +``` + +## 并发模式 (Concurrency Patterns) + +### 线程 (Threading) 处理 I/O 密集型任务 + +```python +import concurrent.futures +import threading + +def fetch_url(url: str) -> str: + """获取 URL (I/O 密集型操作)。""" + import urllib.request + with urllib.request.urlopen(url) as response: + return response.read().decode() + +def fetch_all_urls(urls: list[str]) -> dict[str, str]: + """使用线程并发地获取多个 URL。""" + with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor: + future_to_url = {executor.submit(fetch_url, url): url for url in urls} + results = {} + for future in concurrent.futures.as_completed(future_to_url): + url = future_to_url[future] + try: + results[url] = future.result() + except Exception as e: + results[url] = f"Error: {e}" + return results +``` + +### 多进程 (Multiprocessing) 处理 CPU 密集型任务 + +```python +def process_data(data: list[int]) -> int: + """CPU 密集型计算。""" + return sum(x ** 2 for x in data) + +def process_all(datasets: list[list[int]]) -> list[int]: + """使用多个进程处理多个数据集。""" + with concurrent.futures.ProcessPoolExecutor() as executor: + results = list(executor.map(process_data, datasets)) + return results +``` + +### Async/Await 处理并发 I/O + +```python +import asyncio + +async def fetch_async(url: str) -> str: + """异步获取 URL。""" + import aiohttp + async with aiohttp.ClientSession() as session: + async with session.get(url) as response: + return await response.text() + +async def fetch_all(urls: list[str]) -> dict[str, str]: + """并发地获取多个 URL。""" + tasks = [fetch_async(url) for url in urls] + results = await asyncio.gather(*tasks, return_exceptions=True) + return dict(zip(urls, results)) +``` + +## 包组织 (Package Organization) + +### 标准项目布局 + +``` +myproject/ +├── src/ +│ └── mypackage/ +│ ├── __init__.py +│ ├── main.py +│ ├── api/ +│ │ ├── __init__.py +│ │ └── routes.py +│ ├── models/ +│ │ ├── __init__.py +│ │ └── user.py +│ └── utils/ +│ ├── __init__.py +│ └── helpers.py +├── tests/ +│ ├── __init__.py +│ ├── conftest.py +│ ├── test_api.py +│ └── test_models.py +├── pyproject.toml +├── README.md +└── .gitignore +``` + +### 导入规范 + +```python +# Good: 导入顺序 - 标准库、第三方库、本地库 +import os +import sys +from pathlib import Path + +import requests +from fastapi import FastAPI + +from mypackage.models import User +from mypackage.utils import format_name + +# Good: 使用 isort 自动进行导入排序 +# pip install isort +``` + +### 用于包导出的 __init__.py + +```python +# mypackage/__init__.py +"""mypackage - 一个 Python 包示例。""" + +__version__ = "1.0.0" + +# 在包级别导出主要的类/函数 +from mypackage.models import User, Post +from mypackage.utils import format_name + +__all__ = ["User", "Post", "format_name"] +``` + +## 内存与性能 + +### 使用 __slots__ 提高内存效率 + +```python +# Bad: 常规类使用 __dict__ (占用更多内存) +class Point: + def __init__(self, x: float, y: float): + self.x = x + self.y = y + +# Good: __slots__ 减少内存使用 +class Point: + __slots__ = ['x', 'y'] + + def __init__(self, x: float, y: float): + self.x = x + self.y = y +``` + +### 用于大数据的生成器 + +```python +# Bad: 在内存中返回完整列表 +def read_lines(path: str) -> list[str]: + with open(path) as f: + return [line.strip() for line in f] + +# Good: 一次产出一行 +def read_lines(path: str) -> Iterator[str]: + with open(path) as f: + for line in f: + yield line.strip() +``` + +### 避免在循环中进行字符串拼接 + +```python +# Bad: 由于字符串不可变性,复杂度为 O(n²) +result = "" +for item in items: + result += str(item) + +# Good: 使用 join,复杂度为 O(n) +result = "".join(str(item) for item in items) + +# Good: 使用 StringIO 进行构建 +from io import StringIO + +buffer = StringIO() +for item in items: + buffer.write(str(item)) +result = buffer.getvalue() +``` + +## Python 工具链集成 + +### 常用命令 + +```bash +# 代码格式化 +black . +isort . + +# 静态检查 (Linting) +ruff check . +pylint mypackage/ + +# 类型检查 +mypy . + +# 测试 +pytest --cov=mypackage --cov-report=html + +# 安全扫描 +bandit -r . + +# 依赖管理 +pip-audit +safety check +``` + +### pyproject.toml 配置 + +```toml +[project] +name = "mypackage" +version = "1.0.0" +requires-python = ">=3.9" +dependencies = [ + "requests>=2.31.0", + "pydantic>=2.0.0", +] + +[project.optional-dependencies] +dev = [ + "pytest>=7.4.0", + "pytest-cov>=4.1.0", + "black>=23.0.0", + "ruff>=0.1.0", + "mypy>=1.5.0", +] + +[tool.black] +line-length = 88 +target-version = ['py39'] + +[tool.ruff] +line-length = 88 +select = ["E", "F", "I", "N", "W"] + +[tool.mypy] +python_version = "3.9" +warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = true + +[tool.pytest.ini_options] +testpaths = ["tests"] +addopts = "--cov=mypackage --cov-report=term-missing" +``` + +## 快速参考:Python 惯用法 (Python Idioms) + +| 惯用法 | 描述 | +|-------|-------------| +| EAFP | 请求宽恕比请求许可更容易 (Easier to Ask Forgiveness than Permission) | +| 上下文管理器 (Context managers) | 使用 `with` 进行资源管理 | +| 列表推导式 (List comprehensions) | 用于简单转换 | +| 生成器 (Generators) | 用于延迟求值和大型数据集 | +| 类型提示 (Type hints) | 为函数签名添加注解 | +| 数据类 (Dataclasses) | 用于带有自动生成方法的各种数据容器 | +| `__slots__` | 用于内存优化 | +| f-strings | 用于字符串格式化 (Python 3.6+) | +| `pathlib.Path` | 用于路径操作 (Python 3.4+) | +| `enumerate` | 在循环中获取索引-元素对 | + +## 应避免的反模式 (Anti-Patterns) + +```python +# Bad: 可变默认参数 +def append_to(item, items=[]): + items.append(item) + return items + +# Good: 使用 None 并创建新列表 +def append_to(item, items=None): + if items is None: + items = [] + items.append(item) + return items + +# Bad: 使用 type() 检查类型 +if type(obj) == list: + process(obj) + +# Good: 使用 isinstance +if isinstance(obj, list): + process(obj) + +# Bad: 使用 == 与 None 比较 +if value == None: + process() + +# Good: 使用 is +if value is None: + process() + +# Bad: from module import * +from os.path import * + +# Good: 显式导入 +from os.path import join, exists + +# Bad: 空异常捕获 +try: + risky_operation() +except: + pass + +# Good: 特定异常 +try: + risky_operation() +except SpecificError as e: + logger.error(f"Operation failed: {e}") +``` + +__记住__:Python 代码应当是易读的、显式的,并遵循最小惊讶原则。如有疑问,请优先考虑清晰度而非巧妙性。 + +``` \ No newline at end of file diff --git a/skills/python-testing/SKILL.md b/skills/python-testing/SKILL.md new file mode 100644 index 0000000..89bfd9a --- /dev/null +++ b/skills/python-testing/SKILL.md @@ -0,0 +1,815 @@ +--- +name: python-testing +description: 使用 pytest、TDD 方法论、fixtures、mocking、参数化和代码覆盖率要求的 Python 测试策略。 +--- + +# Python 测试模式(Python Testing Patterns) + +使用 pytest、测试驱动开发(TDD)方法论及最佳实践的 Python 应用程序全面测试策略。 + +## 何时激活 + +- 编写新的 Python 代码时(遵循 TDD:红、绿、重构) +- 为 Python 项目设计测试套件时 +- 审查 Python 测试覆盖率时 +- 搭建测试基础设施时 + +## 核心测试理念 + +### 测试驱动开发(TDD) + +始终遵循 TDD 循环: + +1. **红(RED)**:为期望的行为编写一个失败的测试 +2. **绿(GREEN)**:编写最少量的代码使测试通过 +3. **重构(REFACTOR)**:在保持测试通过的前提下优化代码 + +```python +# 步骤 1:编写失败的测试 (RED) +def test_add_numbers(): + result = add(2, 3) + assert result == 5 + +# 步骤 2:编写最小实现 (GREEN) +def add(a, b): + return a + b + +# 步骤 3:根据需要进行重构 (REFACTOR) +``` + +### 覆盖率要求 + +- **目标**:80% 以上的代码覆盖率 +- **关键路径**:必须达到 100% 覆盖率 +- 使用 `pytest --cov` 来衡量覆盖率 + +```bash +pytest --cov=mypackage --cov-report=term-missing --cov-report=html +``` + +## pytest 基础 + +### 基本测试结构 + +```python +import pytest + +def test_addition(): + """测试基础加法。""" + assert 2 + 2 == 4 + +def test_string_uppercase(): + """测试字符串大写转换。""" + text = "hello" + assert text.upper() == "HELLO" + +def test_list_append(): + """测试列表追加。""" + items = [1, 2, 3] + items.append(4) + assert 4 in items + assert len(items) == 4 +``` + +### 断言(Assertions) + +```python +# 相等性 +assert result == expected + +# 不等性 +assert result != unexpected + +# 真值 +assert result # Truthy +assert not result # Falsy +assert result is True # 精确为 True +assert result is False # 精确为 False +assert result is None # 精确为 None + +# 成员资格 +assert item in collection +assert item not in collection + +# 比较 +assert result > 0 +assert 0 <= result <= 100 + +# 类型检查 +assert isinstance(result, str) + +# 异常测试(推荐做法) +with pytest.raises(ValueError): + raise ValueError("error message") + +# 检查异常消息 +with pytest.raises(ValueError, match="invalid input"): + raise ValueError("invalid input provided") + +# 检查异常属性 +with pytest.raises(ValueError) as exc_info: + raise ValueError("error message") +assert str(exc_info.value) == "error message" +``` + +## Fixtures + +### 基础 Fixture 用法 + +```python +import pytest + +@pytest.fixture +def sample_data(): + """提供示例数据的 Fixture。""" + return {"name": "Alice", "age": 30} + +def test_sample_data(sample_data): + """使用 fixture 的测试。""" + assert sample_data["name"] == "Alice" + assert sample_data["age"] == 30 +``` + +### 带有设置(Setup)和清理(Teardown)的 Fixture + +```python +@pytest.fixture +def database(): + """带有设置和清理逻辑的 Fixture。""" + # 设置 (Setup) + db = Database(":memory:") + db.create_tables() + db.insert_test_data() + + yield db # 提供给测试使用 + + # 清理 (Teardown) + db.close() + +def test_database_query(database): + """测试数据库操作。""" + result = database.query("SELECT * FROM users") + assert len(result) > 0 +``` + +### Fixture 作用域(Scopes) + +```python +# 函数级作用域 (默认) - 每个测试运行一次 +@pytest.fixture +def temp_file(): + with open("temp.txt", "w") as f: + yield f + os.remove("temp.txt") + +# 模块级作用域 - 每个模块运行一次 +@pytest.fixture(scope="module") +def module_db(): + db = Database(":memory:") + db.create_tables() + yield db + db.close() + +# 会话级作用域 - 整个测试会话运行一次 +@pytest.fixture(scope="session") +def shared_resource(): + resource = ExpensiveResource() + yield resource + resource.cleanup() +``` + +### 带参数的 Fixture + +```python +@pytest.fixture(params=[1, 2, 3]) +def number(request): + """参数化 Fixture。""" + return request.param + +def test_numbers(number): + """测试将运行 3 次,每个参数一次。""" + assert number > 0 +``` + +### 使用多个 Fixture + +```python +@pytest.fixture +def user(): + return User(id=1, name="Alice") + +@pytest.fixture +def admin(): + return User(id=2, name="Admin", role="admin") + +def test_user_admin_interaction(user, admin): + """同时使用多个 fixture 的测试。""" + assert admin.can_manage(user) +``` + +### 自动使用(Autouse) Fixture + +```python +@pytest.fixture(autouse=True) +def reset_config(): + """在每个测试前自动运行。""" + Config.reset() + yield + Config.cleanup() + +def test_without_fixture_call(): + # reset_config 会自动运行 + assert Config.get_setting("debug") is False +``` + +### 用于共享 Fixture 的 conftest.py + +```python +# tests/conftest.py +import pytest + +@pytest.fixture +def client(): + """供所有测试共享的 fixture。""" + app = create_app(testing=True) + with app.test_client() as client: + yield client + +@pytest.fixture +def auth_headers(client): + """为 API 测试生成认证头。""" + response = client.post("/api/login", json={ + "username": "test", + "password": "test" + }) + token = response.json["token"] + return {"Authorization": f"Bearer {token}"} +``` + +## 参数化(Parametrization) + +### 基础参数化 + +```python +@pytest.mark.parametrize("input,expected", [ + ("hello", "HELLO"), + ("world", "WORLD"), + ("PyThOn", "PYTHON"), +]) +def test_uppercase(input, expected): + """测试将使用不同的输入运行 3 次。""" + assert input.upper() == expected +``` + +### 多个参数 + +```python +@pytest.mark.parametrize("a,b,expected", [ + (2, 3, 5), + (0, 0, 0), + (-1, 1, 0), + (100, 200, 300), +]) +def test_add(a, b, expected): + """使用多组输入测试加法。""" + assert add(a, b) == expected +``` + +### 带 ID 的参数化 + +```python +@pytest.mark.parametrize("input,expected", [ + ("valid@email.com", True), + ("invalid", False), + ("@no-domain.com", False), +], ids=["valid-email", "missing-at", "missing-domain"]) +def test_email_validation(input, expected): + """通过可读的测试 ID 测试电子邮件验证。""" + assert is_valid_email(input) is expected +``` + +### 参数化 Fixtures + +```python +@pytest.fixture(params=["sqlite", "postgresql", "mysql"]) +def db(request): + """针对多个数据库后端进行测试。""" + if request.param == "sqlite": + return Database(":memory:") + elif request.param == "postgresql": + return Database("postgresql://localhost/test") + elif request.param == "mysql": + return Database("mysql://localhost/test") + +def test_database_operations(db): + """测试将运行 3 次,每个数据库一次。""" + result = db.query("SELECT 1") + assert result is not None +``` + +## 标记(Markers)与测试选择 + +### 自定义标记 + +```python +# 标记慢速测试 +@pytest.mark.slow +def test_slow_operation(): + time.sleep(5) + +# 标记集成测试 +@pytest.mark.integration +def test_api_integration(): + response = requests.get("https://api.example.com") + assert response.status_code == 200 + +# 标记单元测试 +@pytest.mark.unit +def test_unit_logic(): + assert calculate(2, 3) == 5 +``` + +### 运行特定测试 + +```bash +# 仅运行非慢速测试 +pytest -m "not slow" + +# 仅运行集成测试 +pytest -m integration + +# 运行集成测试或慢速测试 +pytest -m "integration or slow" + +# 运行标记为单元测试且非慢速的测试 +pytest -m "unit and not slow" +``` + +### 在 pytest.ini 中配置标记 + +```ini +[pytest] +markers = + slow: 将测试标记为慢速 + integration: 将测试标记为集成测试 + unit: 将测试标记为单元测试 + django: 将测试标记为需要 Django 环境 +``` + +## Mocking 与 Patching + +### Mock 函数 + +```python +from unittest.mock import patch, Mock + +@patch("mypackage.external_api_call") +def test_with_mock(api_call_mock): + """使用 mock 的外部 API 进行测试。""" + api_call_mock.return_value = {"status": "success"} + + result = my_function() + + api_call_mock.assert_called_once() + assert result["status"] == "success" +``` + +### Mock 返回值 + +```python +@patch("mypackage.Database.connect") +def test_database_connection(connect_mock): + """使用 mock 的数据库连接进行测试。""" + connect_mock.return_value = MockConnection() + + db = Database() + db.connect() + + connect_mock.assert_called_once_with("localhost") +``` + +### Mock 异常 + +```python +@patch("mypackage.api_call") +def test_api_error_handling(api_call_mock): + """使用 mock 异常测试错误处理。""" + api_call_mock.side_effect = ConnectionError("Network error") + + with pytest.raises(ConnectionError): + api_call() + + api_call_mock.assert_called_once() +``` + +### Mock 上下文管理器(Context Managers) + +```python +@patch("builtins.open", new_callable=mock_open) +def test_file_reading(mock_file): + """使用 mock 的 open 测试文件读取。""" + mock_file.return_value.read.return_value = "file content" + + result = read_file("test.txt") + + mock_file.assert_called_once_with("test.txt", "r") + assert result == "file content" +``` + +### 使用 Autospec + +```python +@patch("mypackage.DBConnection", autospec=True) +def test_autospec(db_mock): + """使用 autospec 捕获 API 滥用。""" + db = db_mock.return_value + db.query("SELECT * FROM users") + + # 如果 DBConnection 没有 query 方法,此处将失败 + db_mock.assert_called_once() +``` + +### Mock 类实例 + +```python +class TestUserService: + @patch("mypackage.UserRepository") + def test_create_user(self, repo_mock): + """使用 mock 的仓库进行用户创建测试。""" + repo_mock.return_value.save.return_value = User(id=1, name="Alice") + + service = UserService(repo_mock.return_value) + user = service.create_user(name="Alice") + + assert user.name == "Alice" + repo_mock.return_value.save.assert_called_once() +``` + +### Mock 属性(Property) + +```python +@pytest.fixture +def mock_config(): + """创建一个带有属性的 mock。""" + config = Mock() + type(config).debug = PropertyMock(return_value=True) + type(config).api_key = PropertyMock(return_value="test-key") + return config + +def test_with_mock_config(mock_config): + """使用 mock 配置属性进行测试。""" + assert mock_config.debug is True + assert mock_config.api_key == "test-key" +``` + +## 测试异步代码 + +### 使用 pytest-asyncio 进行异步测试 + +```python +import pytest + +@pytest.mark.asyncio +async def test_async_function(): + """测试异步函数。""" + result = await async_add(2, 3) + assert result == 5 + +@pytest.mark.asyncio +async def test_async_with_fixture(async_client): + """在异步 fixture 下进行异步测试。""" + response = await async_client.get("/api/users") + assert response.status_code == 200 +``` + +### 异步 Fixture + +```python +@pytest.fixture +async def async_client(): + """提供异步测试客户端的异步 Fixture。""" + app = create_app() + async with app.test_client() as client: + yield client + +@pytest.mark.asyncio +async def test_api_endpoint(async_client): + """使用异步 fixture 进行测试。""" + response = await async_client.get("/api/data") + assert response.status_code == 200 +``` + +### Mock 异步函数 + +```python +@pytest.mark.asyncio +@patch("mypackage.async_api_call") +async def test_async_mock(api_call_mock): + """使用 mock 测试异步函数。""" + api_call_mock.return_value = {"status": "ok"} + + result = await my_async_function() + + api_call_mock.assert_awaited_once() + assert result["status"] == "ok" +``` + +## 测试异常 + +### 测试预期的异常 + +```python +def test_divide_by_zero(): + """测试除以零是否抛出 ZeroDivisionError。""" + with pytest.raises(ZeroDivisionError): + divide(10, 0) + +def test_custom_exception(): + """使用消息测试自定义异常。""" + with pytest.raises(ValueError, match="invalid input"): + validate_input("invalid") +``` + +### 测试异常属性 + +```python +def test_exception_with_details(): + """测试带有自定义属性的异常。""" + with pytest.raises(CustomError) as exc_info: + raise CustomError("error", code=400) + + assert exc_info.value.code == 400 + assert "error" in str(exc_info.value) +``` + +## 测试副作用(Side Effects) + +### 测试文件操作 + +```python +import tempfile +import os + +def test_file_processing(): + """使用临时文件测试文件处理。""" + with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f: + f.write("test content") + temp_path = f.name + + try: + result = process_file(temp_path) + assert result == "processed: test content" + finally: + os.unlink(temp_path) +``` + +### 使用 pytest 的 tmp_path Fixture 进行测试 + +```python +def test_with_tmp_path(tmp_path): + """使用 pytest 内置的临时路径 fixture 进行测试。""" + test_file = tmp_path / "test.txt" + test_file.write_text("hello world") + + result = process_file(str(test_file)) + assert result == "hello world" + # tmp_path 会自动清理 +``` + +### 使用 tmpdir Fixture 进行测试 + +```python +def test_with_tmpdir(tmpdir): + """使用 pytest 的 tmpdir fixture 进行测试。""" + test_file = tmpdir.join("test.txt") + test_file.write("data") + + result = process_file(str(test_file)) + assert result == "data" +``` + +## 测试组织 + +### 目录结构 + +``` +tests/ +├── conftest.py # 共享的 fixture +├── __init__.py +├── unit/ # 单元测试 +│ ├── __init__.py +│ ├── test_models.py +│ ├── test_utils.py +│ └── test_services.py +├── integration/ # 集成测试 +│ ├── __init__.py +│ ├── test_api.py +│ └── test_database.py +└── e2e/ # 端到端测试 + ├── __init__.py + └── test_user_flow.py +``` + +### 测试类 + +```python +class TestUserService: + """在类中组织相关的测试。""" + + @pytest.fixture(autouse=True) + def setup(self): + """在该类的每个测试运行前执行设置。""" + self.service = UserService() + + def test_create_user(self): + """测试用户创建。""" + user = self.service.create_user("Alice") + assert user.name == "Alice" + + def test_delete_user(self): + """测试用户删除。""" + user = User(id=1, name="Bob") + self.service.delete_user(user) + assert not self.service.user_exists(1) +``` + +## 最佳实践 + +### 应该做(DO) + +- **遵循 TDD**:先写测试再写代码(红-绿-重构) +- **只测试一件事**:每个测试应该只验证一种行为 +- **使用描述性的名称**:如 `test_user_login_with_invalid_credentials_fails` +- **使用 Fixtures**:通过 fixture 消除重复代码 +- **Mock 外部依赖**:不要依赖外部服务 +- **测试边缘情况**:空输入、None 值、边界条件 +- **以 80% 以上的覆盖率为目标**:优先覆盖关键路径 +- **保持测试运行迅速**:使用标记区分慢速测试 + +### 不该做(DON'T) + +- **不要测试实现细节**:测试行为而非内部实现 +- **不要在测试中使用复杂的条件判断**:保持测试逻辑简单 +- **不要忽视失败的测试**:所有测试必须通过 +- **不要测试第三方代码**:相信库本身是工作的 +- **不要在测试间共享状态**:测试应该是相互独立的 +- **不要在测试中捕获异常**:使用 `pytest.raises` +- **不要使用 print 语句**:使用断言和 pytest 的输出机制 +- **不要编写过于脆弱的测试**:避免过度特异化的 mock + +## 常见模式 + +### 测试 API 端点 (FastAPI/Flask) + +```python +@pytest.fixture +def client(): + app = create_app(testing=True) + return app.test_client() + +def test_get_user(client): + response = client.get("/api/users/1") + assert response.status_code == 200 + assert response.json["id"] == 1 + +def test_create_user(client): + response = client.post("/api/users", json={ + "name": "Alice", + "email": "alice@example.com" + }) + assert response.status_code == 201 + assert response.json["name"] == "Alice" +``` + +### 测试数据库操作 + +```python +@pytest.fixture +def db_session(): + """创建测试数据库会话。""" + session = Session(bind=engine) + session.begin_nested() + yield session + session.rollback() + session.close() + +def test_create_user(db_session): + user = User(name="Alice", email="alice@example.com") + db_session.add(user) + db_session.commit() + + retrieved = db_session.query(User).filter_by(name="Alice").first() + assert retrieved.email == "alice@example.com" +``` + +### 测试类方法 + +```python +class TestCalculator: + @pytest.fixture + def calculator(self): + return Calculator() + + def test_add(self, calculator): + assert calculator.add(2, 3) == 5 + + def test_divide_by_zero(self, calculator): + with pytest.raises(ZeroDivisionError): + calculator.divide(10, 0) +``` + +## pytest 配置 + +### pytest.ini + +```ini +[pytest] +testpaths = tests +python_files = test_*.py +python_classes = Test* +python_functions = test_* +addopts = + --strict-markers + --disable-warnings + --cov=mypackage + --cov-report=term-missing + --cov-report=html +markers = + slow: 将测试标记为慢速 + integration: 将测试标记为集成测试 + unit: 将测试标记为单元测试 +``` + +### pyproject.toml + +```toml +[tool.pytest.ini_options] +testpaths = ["tests"] +python_files = ["test_*.py"] +python_classes = ["Test*"] +python_functions = ["test_*"] +addopts = [ + "--strict-markers", + "--cov=mypackage", + "--cov-report=term-missing", + "--cov-report=html", +] +markers = [ + "slow: 将测试标记为慢速", + "integration: 将测试标记为集成测试", + "unit: 将测试标记为单元测试", +] +``` + +## 运行测试 + +```bash +# 运行所有测试 +pytest + +# 运行特定文件 +pytest tests/test_utils.py + +# 运行特定测试函数 +pytest tests/test_utils.py::test_function + +# 运行并输出详细结果 +pytest -v + +# 运行并生成覆盖率报告 +pytest --cov=mypackage --cov-report=html + +# 仅运行非慢速测试 +pytest -m "not slow" + +# 运行并在第一次失败时停止 +pytest -x + +# 运行并在发生 N 次失败后停止 +pytest --maxfail=3 + +# 运行上次失败的测试 +pytest --lf + +# 运行匹配模式的测试 +pytest -k "test_user" + +# 失败时启动调试器 +pytest --pdb +``` + +## 快速参考 + +| 模式 | 用法 | +|---------|-------| +| `pytest.raises()` | 测试预期的异常 | +| `@pytest.fixture()` | 创建可重用的测试 fixture | +| `@pytest.mark.parametrize()` | 使用多组输入运行测试 | +| `@pytest.mark.slow` | 标记慢速测试 | +| `pytest -m "not slow"` | 跳过慢速测试 | +| `@patch()` | Mock 函数和类 | +| `tmp_path` fixture | 自动创建临时目录 | +| `pytest --cov` | 生成覆盖率报告 | +| `assert` | 简单且可读的断言 | + +**请记住**:测试代码也是代码。保持它们整洁、可读且可维护。好的测试能捕获 Bug;伟大的测试能防止 Bug 产生。 diff --git a/skills/springboot-patterns/SKILL.md b/skills/springboot-patterns/SKILL.md index 2270dc9..dbcd50d 100644 --- a/skills/springboot-patterns/SKILL.md +++ b/skills/springboot-patterns/SKILL.md @@ -1,13 +1,13 @@ --- name: springboot-patterns -description: Spring Boot architecture patterns, REST API design, layered services, data access, caching, async processing, and logging. Use for Java Spring Boot backend work. +description: Spring Boot 架构模式、REST API 设计、分层服务、数据访问、缓存、异步处理和日志记录。适用于 Java Spring Boot 后端开发工作。 --- -# Spring Boot Development Patterns +# Spring Boot 开发模式 (Spring Boot Development Patterns) -Spring Boot architecture and API patterns for scalable, production-grade services. +适用于可扩展、生产级服务的 Spring Boot 架构与 API 模式。 -## REST API Structure +## REST API 结构 ```java @RestController @@ -36,7 +36,7 @@ class MarketController { } ``` -## Repository Pattern (Spring Data JPA) +## 仓储模式 (Repository Pattern - Spring Data JPA) ```java public interface MarketRepository extends JpaRepository { @@ -45,7 +45,7 @@ public interface MarketRepository extends JpaRepository { } ``` -## Service Layer with Transactions +## 带事务的服务层 (Service Layer with Transactions) ```java @Service @@ -65,7 +65,7 @@ public class MarketService { } ``` -## DTOs and Validation +## DTO 与校验 (DTOs and Validation) ```java public record CreateMarketRequest( @@ -81,7 +81,7 @@ public record MarketResponse(Long id, String name, MarketStatus status) { } ``` -## Exception Handling +## 异常处理 (Exception Handling) ```java @ControllerAdvice @@ -101,16 +101,16 @@ class GlobalExceptionHandler { @ExceptionHandler(Exception.class) ResponseEntity handleGeneric(Exception ex) { - // Log unexpected errors with stack traces + // 记录带有堆栈跟踪的非预期错误 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(ApiError.of("Internal server error")); } } ``` -## Caching +## 缓存 (Caching) -Requires `@EnableCaching` on a configuration class. +需要在配置类上添加 `@EnableCaching`。 ```java @Service @@ -133,22 +133,22 @@ public class MarketCacheService { } ``` -## Async Processing +## 异步处理 (Async Processing) -Requires `@EnableAsync` on a configuration class. +需要在配置类上添加 `@EnableAsync`。 ```java @Service public class NotificationService { @Async public CompletableFuture sendAsync(Notification notification) { - // send email/SMS + // 发送 邮件/短信 return CompletableFuture.completedFuture(null); } } ``` -## Logging (SLF4J) +## 日志记录 (Logging - SLF4J) ```java @Service @@ -158,7 +158,7 @@ public class ReportService { public Report generate(Long marketId) { log.info("generate_report marketId={}", marketId); try { - // logic + // 业务逻辑 } catch (Exception ex) { log.error("generate_report_failed marketId={}", marketId, ex); throw ex; @@ -168,7 +168,7 @@ public class ReportService { } ``` -## Middleware / Filters +## 中间件 / 过滤器 (Middleware / Filters) ```java @Component @@ -190,14 +190,14 @@ public class RequestLoggingFilter extends OncePerRequestFilter { } ``` -## Pagination and Sorting +## 分页与排序 (Pagination and Sorting) ```java PageRequest page = PageRequest.of(pageNumber, pageSize, Sort.by("createdAt").descending()); Page results = marketService.list(page); ``` -## Error-Resilient External Calls +## 容错性外部调用 (Error-Resilient External Calls) ```java public T withRetry(Supplier supplier, int maxRetries) { @@ -221,19 +221,16 @@ public T withRetry(Supplier supplier, int maxRetries) { } ``` -## Rate Limiting (Filter + Bucket4j) +## 限流 (Filter + Bucket4j) -**Security Note**: The `X-Forwarded-For` header is untrusted by default because clients can spoof it. -Only use forwarded headers when: -1. Your app is behind a trusted reverse proxy (nginx, AWS ALB, etc.) -2. You have registered `ForwardedHeaderFilter` as a bean -3. You have configured `server.forward-headers-strategy=NATIVE` or `FRAMEWORK` in application properties -4. Your proxy is configured to overwrite (not append to) the `X-Forwarded-For` header +**安全注意事项**:`X-Forwarded-For` 请求头默认是不可信的,因为客户端可以伪造它。 +仅在以下情况下使用转发请求头: +1. 你的应用位于受信任的反向代理(nginx、AWS ALB 等)之后 +2. 你已将 `ForwardedHeaderFilter` 注册为 Bean +3. 你在 application 属性中配置了 `server.forward-headers-strategy=NATIVE` 或 `FRAMEWORK` +4. 你的代理配置为覆盖(而非追加)`X-Forwarded-For` 请求头 -When `ForwardedHeaderFilter` is properly configured, `request.getRemoteAddr()` will automatically -return the correct client IP from the forwarded headers. Without this configuration, use -`request.getRemoteAddr()` directly—it returns the immediate connection IP, which is the only -trustworthy value. +当 `ForwardedHeaderFilter` 配置正确时,`request.getRemoteAddr()` 将自动从转发头中返回正确的客户端 IP。如果没有此配置,请直接使用 `request.getRemoteAddr()`——它返回直接连接的 IP,这是唯一可信的值。 ```java @Component @@ -241,32 +238,31 @@ public class RateLimitFilter extends OncePerRequestFilter { private final Map buckets = new ConcurrentHashMap<>(); /* - * SECURITY: This filter uses request.getRemoteAddr() to identify clients for rate limiting. + * 安全提示:此过滤器使用 request.getRemoteAddr() 来识别用于限流的客户端。 * - * If your application is behind a reverse proxy (nginx, AWS ALB, etc.), you MUST configure - * Spring to handle forwarded headers properly for accurate client IP detection: + * 如果你的应用位于反向代理(nginx、AWS ALB 等)之后,你必须配置 Spring + * 正确处理转发头,以便准确检测客户端 IP: * - * 1. Set server.forward-headers-strategy=NATIVE (for cloud platforms) or FRAMEWORK in - * application.properties/yaml - * 2. If using FRAMEWORK strategy, register ForwardedHeaderFilter: + * 1. 在 application.properties/yaml 中设置 server.forward-headers-strategy=NATIVE + * (适用于云平台) 或 FRAMEWORK + * 2. 如果使用 FRAMEWORK 策略,请注册 ForwardedHeaderFilter: * * @Bean * ForwardedHeaderFilter forwardedHeaderFilter() { * return new ForwardedHeaderFilter(); * } * - * 3. Ensure your proxy overwrites (not appends) the X-Forwarded-For header to prevent spoofing - * 4. Configure server.tomcat.remoteip.trusted-proxies or equivalent for your container + * 3. 确保你的代理覆盖(而不是追加)X-Forwarded-For 请求头以防止伪造 + * 4. 为你的容器配置 server.tomcat.remoteip.trusted-proxies 或等效配置 * - * Without this configuration, request.getRemoteAddr() returns the proxy IP, not the client IP. - * Do NOT read X-Forwarded-For directly—it is trivially spoofable without trusted proxy handling. + * 如果没有这些配置,request.getRemoteAddr() 将返回代理服务器的 IP,而非客户端 IP。 + * 不要直接读取 X-Forwarded-For —— 在没有受信任代理处理的情况下,它是极易伪造的。 */ @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - // Use getRemoteAddr() which returns the correct client IP when ForwardedHeaderFilter - // is configured, or the direct connection IP otherwise. Never trust X-Forwarded-For - // headers directly without proper proxy configuration. + // 使用 getRemoteAddr(),它在配置了 ForwardedHeaderFilter 时返回正确的客户端 IP, + // 否则返回直接连接的 IP。在没有正确代理配置的情况下,切勿直接信任 X-Forwarded-For 头。 String clientIp = request.getRemoteAddr(); Bucket bucket = buckets.computeIfAbsent(clientIp, @@ -283,22 +279,22 @@ public class RateLimitFilter extends OncePerRequestFilter { } ``` -## Background Jobs +## 后台任务 (Background Jobs) -Use Spring’s `@Scheduled` or integrate with queues (e.g., Kafka, SQS, RabbitMQ). Keep handlers idempotent and observable. +使用 Spring 的 `@Scheduled` 或集成队列(如 Kafka、SQS、RabbitMQ)。保持处理程序具有幂等性和可观测性。 -## Observability +## 可观测性 (Observability) -- Structured logging (JSON) via Logback encoder -- Metrics: Micrometer + Prometheus/OTel -- Tracing: Micrometer Tracing with OpenTelemetry or Brave backend +- 通过 Logback encoder 实现结构化日志(JSON) +- 指标(Metrics):Micrometer + Prometheus/OTel +- 链路追踪(Tracing):使用 OpenTelemetry 或 Brave 后端的 Micrometer Tracing -## Production Defaults +## 生产环境默认实践 (Production Defaults) -- Prefer constructor injection, avoid field injection -- Enable `spring.mvc.problemdetails.enabled=true` for RFC 7807 errors (Spring Boot 3+) -- Configure HikariCP pool sizes for workload, set timeouts -- Use `@Transactional(readOnly = true)` for queries -- Enforce null-safety via `@NonNull` and `Optional` where appropriate +- 优先使用构造函数注入,避免字段注入 +- 为 RFC 7807 错误启用 `spring.mvc.problemdetails.enabled=true` (Spring Boot 3+) +- 根据工作负载配置 HikariCP 连接池大小并设置超时 +- 为查询使用 `@Transactional(readOnly = true)` +- 通过 `@NonNull` 和 `Optional` 在适当时强制执行空安全(null-safety) -**Remember**: Keep controllers thin, services focused, repositories simple, and errors handled centrally. Optimize for maintainability and testability. +**切记**:保持控制器(Controller)薄、服务(Service)专注、仓储(Repository)简单,并集中处理错误。针对可维护性和可测试性进行优化。 diff --git a/skills/springboot-security/SKILL.md b/skills/springboot-security/SKILL.md index f9dc6a2..5b33b6f 100644 --- a/skills/springboot-security/SKILL.md +++ b/skills/springboot-security/SKILL.md @@ -1,17 +1,17 @@ --- name: springboot-security -description: Spring Security best practices for authn/authz, validation, CSRF, secrets, headers, rate limiting, and dependency security in Java Spring Boot services. +description: Spring Boot 服务中关于身份认证/授权(authn/authz)、校验、CSRF、密钥管理、响应头、限流及依赖安全的 Spring Security 最佳实践。 --- -# Spring Boot Security Review +# Spring Boot 安全审查 -Use when adding auth, handling input, creating endpoints, or dealing with secrets. +在添加认证、处理输入、创建端点或处理密钥时使用。 -## Authentication +## 身份认证(Authentication) -- Prefer stateless JWT or opaque tokens with revocation list -- Use `httpOnly`, `Secure`, `SameSite=Strict` cookies for sessions -- Validate tokens with `OncePerRequestFilter` or resource server +- 优先使用无状态 JWT 或带有撤回列表(Revocation List)的不透明令牌(Opaque Tokens) +- 为会话(Session)使用 `httpOnly`、`Secure`、`SameSite=Strict` 的 Cookie +- 使用 `OncePerRequestFilter` 或资源服务器验证令牌 ```java @Component @@ -36,27 +36,27 @@ public class JwtAuthFilter extends OncePerRequestFilter { } ``` -## Authorization +## 授权(Authorization) -- Enable method security: `@EnableMethodSecurity` -- Use `@PreAuthorize("hasRole('ADMIN')")` or `@PreAuthorize("@authz.canEdit(#id)")` -- Deny by default; expose only required scopes +- 启用方法级安全:`@EnableMethodSecurity` +- 使用 `@PreAuthorize("hasRole('ADMIN')")` 或 `@PreAuthorize("@authz.canEdit(#id)")` +- 默认拒绝(Deny by default);仅暴露必要的权限范围(Scopes) -## Input Validation +## 输入校验(Input Validation) -- Use Bean Validation with `@Valid` on controllers -- Apply constraints on DTOs: `@NotBlank`, `@Email`, `@Size`, custom validators -- Sanitize any HTML with a whitelist before rendering +- 在控制器(Controller)上配合使用 Bean Validation 与 `@Valid` +- 在 DTO 上应用约束:`@NotBlank`、`@Email`、`@Size` 以及自定义校验器 +- 在渲染之前,通过白名单对任何 HTML 进行净化(Sanitize) -## SQL Injection Prevention +## 防止 SQL 注入 -- Use Spring Data repositories or parameterized queries -- For native queries, use `:param` bindings; never concatenate strings +- 使用 Spring Data 存储库(Repositories)或参数化查询 +- 对于原生查询,使用 `:param` 绑定;严禁拼接字符串 -## CSRF Protection +## CSRF 防护 -- For browser session apps, keep CSRF enabled; include token in forms/headers -- For pure APIs with Bearer tokens, disable CSRF and rely on stateless auth +- 对于基于浏览器会话的应用,保持启用 CSRF;在表单/请求头中包含令牌 +- 对于使用 Bearer 令牌的纯 API,禁用 CSRF 并依赖无状态认证 ```java http @@ -64,13 +64,13 @@ http .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); ``` -## Secrets Management +## 密钥管理(Secrets Management) -- No secrets in source; load from env or vault -- Keep `application.yml` free of credentials; use placeholders -- Rotate tokens and DB credentials regularly +- 源代码中不保留密钥;从环境变量或 Vault 加载 +- 确保 `application.yml` 中没有凭据;使用占位符 +- 定期轮换令牌和数据库凭据 -## Security Headers +## 安全响应头(Security Headers) ```java http @@ -79,41 +79,41 @@ http .policyDirectives("default-src 'self'")) .frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin) .xssProtection(Customizer.withDefaults()) - .referrerPolicy(rp -> rp.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.NO_REFERRER))); + .referrerPolicy(rp -> rp.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.NO_REFER_ER))); ``` -## Rate Limiting +## 限流(Rate Limiting) -- Apply Bucket4j or gateway-level limits on expensive endpoints -- Log and alert on bursts; return 429 with retry hints +- 在高开销端点上应用 Bucket4j 或网关级限流 +- 对突发流量进行日志记录并告警;返回 429 状态码及重试提示 -## Dependency Security +## 依赖安全(Dependency Security) -- Run OWASP Dependency Check / Snyk in CI -- Keep Spring Boot and Spring Security on supported versions -- Fail builds on known CVEs +- 在 CI 中运行 OWASP Dependency Check / Snyk +- 保持 Spring Boot 和 Spring Security 处于受支持的版本 +- 发现已知 CVE 时构建失败 -## Logging and PII +## 日志与个人敏感信息(PII) -- Never log secrets, tokens, passwords, or full PAN data -- Redact sensitive fields; use structured JSON logging +- 严禁在日志中记录密钥、令牌、密码或完整的银行卡号(PAN)数据 +- 脱敏敏感字段;使用结构化 JSON 日志记录 -## File Uploads +## 文件上传 -- Validate size, content type, and extension -- Store outside web root; scan if required +- 校验大小、内容类型(Content Type)和扩展名 +- 存储在 Web 根目录之外;必要时进行扫描 -## Checklist Before Release +## 发布前自检清单 -- [ ] Auth tokens validated and expired correctly -- [ ] Authorization guards on every sensitive path -- [ ] All inputs validated and sanitized -- [ ] No string-concatenated SQL -- [ ] CSRF posture correct for app type -- [ ] Secrets externalized; none committed -- [ ] Security headers configured -- [ ] Rate limiting on APIs -- [ ] Dependencies scanned and up to date -- [ ] Logs free of sensitive data +- [ ] 身份认证令牌已正确验证并配置过期时间 +- [ ] 每个敏感路径都有授权保护 +- [ ] 所有输入均已校验并净化 +- [ ] 没有字符串拼接的 SQL +- [ ] CSRF 配置符合应用类型 +- [ ] 密钥已外部化;未提交任何密钥 +- [ ] 安全响应头已配置 +- [ ] API 已配置限流 +- [ ] 依赖已扫描且为最新 +- [ ] 日志中不包含敏感数据 -**Remember**: Deny by default, validate inputs, least privilege, and secure-by-configuration first. +**记住**:默认拒绝、校验输入、最小权限原则,以及配置优先的安全性。 diff --git a/skills/springboot-tdd/SKILL.md b/skills/springboot-tdd/SKILL.md index daaa990..5d959dc 100644 --- a/skills/springboot-tdd/SKILL.md +++ b/skills/springboot-tdd/SKILL.md @@ -1,26 +1,26 @@ --- name: springboot-tdd -description: Test-driven development for Spring Boot using JUnit 5, Mockito, MockMvc, Testcontainers, and JaCoCo. Use when adding features, fixing bugs, or refactoring. +description: 使用 JUnit 5、Mockito、MockMvc、Testcontainers 和 JaCoCo 进行 Spring Boot 的测试驱动开发(TDD)。在添加功能、修复 Bug 或进行重构时使用。 --- -# Spring Boot TDD Workflow +# Spring Boot 测试驱动开发(TDD)工作流 -TDD guidance for Spring Boot services with 80%+ coverage (unit + integration). +针对 Spring Boot 服务的 TDD 指南,要求 80% 以上的覆盖率(单元测试 + 集成测试)。 -## When to Use +## 适用场景 -- New features or endpoints -- Bug fixes or refactors -- Adding data access logic or security rules +- 开发新功能或端点(Endpoints) +- 修复 Bug 或进行代码重构 +- 添加数据访问逻辑或安全规则 -## Workflow +## 工作流 -1) Write tests first (they should fail) -2) Implement minimal code to pass -3) Refactor with tests green -4) Enforce coverage (JaCoCo) +1) 先写测试(测试应当失败) +2) 实现最少量的代码以使测试通过 +3) 在测试通过(Green)的前提下进行重构 +4) 强制执行覆盖率检查(JaCoCo) -## Unit Tests (JUnit 5 + Mockito) +## 单元测试(JUnit 5 + Mockito) ```java @ExtendWith(MockitoExtension.class) @@ -41,12 +41,12 @@ class MarketServiceTest { } ``` -Patterns: -- Arrange-Act-Assert -- Avoid partial mocks; prefer explicit stubbing -- Use `@ParameterizedTest` for variants +模式: +- Arrange-Act-Assert(准备-执行-断言) +- 避免部分打桩(Partial Mocks);优先使用显式桩函数(Stubbing) +- 使用 `@ParameterizedTest` 处理多种变体场景 -## Web Layer Tests (MockMvc) +## Web 层测试(MockMvc) ```java @WebMvcTest(MarketController.class) @@ -65,7 +65,7 @@ class MarketControllerTest { } ``` -## Integration Tests (SpringBootTest) +## 集成测试(SpringBootTest) ```java @SpringBootTest @@ -86,7 +86,7 @@ class MarketIntegrationTest { } ``` -## Persistence Tests (DataJpaTest) +## 持久层测试(DataJpaTest) ```java @DataJpaTest @@ -109,12 +109,12 @@ class MarketRepositoryTest { ## Testcontainers -- Use reusable containers for Postgres/Redis to mirror production -- Wire via `@DynamicPropertySource` to inject JDBC URLs into Spring context +- 使用可重用的容器(如 Postgres/Redis)来模拟生产环境 +- 通过 `@DynamicPropertySource` 进行连接,将 JDBC URL 注入到 Spring 上下文中 -## Coverage (JaCoCo) +## 覆盖率(JaCoCo) -Maven snippet: +Maven 配置片段: ```xml org.jacoco @@ -133,13 +133,13 @@ Maven snippet: ``` -## Assertions +## 断言(Assertions) -- Prefer AssertJ (`assertThat`) for readability -- For JSON responses, use `jsonPath` -- For exceptions: `assertThatThrownBy(...)` +- 为了提高可读性,优先选择 AssertJ (`assertThat`) +- 对于 JSON 响应,使用 `jsonPath` +- 对于异常测试:`assertThatThrownBy(...)` -## Test Data Builders +## 测试数据构建器(Test Data Builders) ```java class MarketBuilder { @@ -149,9 +149,9 @@ class MarketBuilder { } ``` -## CI Commands +## CI 命令 -- Maven: `mvn -T 4 test` or `mvn verify` -- Gradle: `./gradlew test jacocoTestReport` +- Maven:`mvn -T 4 test` 或 `mvn verify` +- Gradle:`./gradlew test jacocoTestReport` -**Remember**: Keep tests fast, isolated, and deterministic. Test behavior, not implementation details. +**记住**:保持测试快速、隔离且具有确定性。测试的是行为,而非实现细节。 diff --git a/skills/springboot-verification/SKILL.md b/skills/springboot-verification/SKILL.md index 909e90a..5c73473 100644 --- a/skills/springboot-verification/SKILL.md +++ b/skills/springboot-verification/SKILL.md @@ -1,100 +1,100 @@ --- name: springboot-verification -description: Verification loop for Spring Boot projects: build, static analysis, tests with coverage, security scans, and diff review before release or PR. +description: Spring Boot 项目的验证循环(Verification loop):包含构建、静态分析、带覆盖率的测试、安全扫描,以及在发布或 PR 前的差异评审(diff review)。 --- -# Spring Boot Verification Loop +# Spring Boot 验证循环(Verification Loop) -Run before PRs, after major changes, and pre-deploy. +在提交 PR 前、发生重大变更后以及预部署阶段运行此流程。 -## Phase 1: Build +## 阶段 1:构建(Build) ```bash mvn -T 4 clean verify -DskipTests -# or +# 或者 ./gradlew clean assemble -x test ``` -If build fails, stop and fix. +如果构建失败,请停止并修复。 -## Phase 2: Static Analysis +## 阶段 2:静态分析(Static Analysis) -Maven (common plugins): +Maven(常用插件): ```bash mvn -T 4 spotbugs:check pmd:check checkstyle:check ``` -Gradle (if configured): +Gradle(如果已配置): ```bash ./gradlew checkstyleMain pmdMain spotbugsMain ``` -## Phase 3: Tests + Coverage +## 阶段 3:测试 + 覆盖率(Tests + Coverage) ```bash mvn -T 4 test -mvn jacoco:report # verify 80%+ coverage -# or +mvn jacoco:report # 验证 80% 以上的覆盖率 +# 或者 ./gradlew test jacocoTestReport ``` -Report: -- Total tests, passed/failed -- Coverage % (lines/branches) +报告指标: +- 测试总数、通过/失败数量 +- 覆盖率 %(行/分支) -## Phase 4: Security Scan +## 阶段 4:安全扫描(Security Scan) ```bash -# Dependency CVEs +# 依赖项 CVE 漏洞扫描 mvn org.owasp:dependency-check-maven:check -# or +# 或者 ./gradlew dependencyCheckAnalyze -# Secrets (git) -git secrets --scan # if configured +# 密钥(Secrets)扫描 (git) +git secrets --scan # 如果已配置 ``` -## Phase 5: Lint/Format (optional gate) +## 阶段 5:代码规范/格式化(Lint/Format,可选阈值) ```bash -mvn spotless:apply # if using Spotless plugin +mvn spotless:apply # 如果使用了 Spotless 插件 ./gradlew spotlessApply ``` -## Phase 6: Diff Review +## 阶段 6:差异评审(Diff Review) ```bash git diff --stat git diff ``` -Checklist: -- No debugging logs left (`System.out`, `log.debug` without guards) -- Meaningful errors and HTTP statuses -- Transactions and validation present where needed -- Config changes documented +自查清单(Checklist): +- 未残留调试日志(如 `System.out`,或缺少防护检查的 `log.debug`) +- 错误信息和 HTTP 状态码具有明确语义 +- 在必要处已包含事务(Transactions)和校验(Validation) +- 配置变更已记录在文档中 -## Output Template +## 输出模版(Output Template) ``` -VERIFICATION REPORT +验证报告 (VERIFICATION REPORT) =================== -Build: [PASS/FAIL] -Static: [PASS/FAIL] (spotbugs/pmd/checkstyle) -Tests: [PASS/FAIL] (X/Y passed, Z% coverage) -Security: [PASS/FAIL] (CVE findings: N) -Diff: [X files changed] +构建 (Build): [通过/失败] +静态分析 (Static): [通过/失败] (spotbugs/pmd/checkstyle) +测试 (Tests): [通过/失败] (通过 X/Y,覆盖率 Z%) +安全 (Security): [通过/失败] (CVE 发现数量: N) +差异 (Diff): [X 个文件已变更] -Overall: [READY / NOT READY] +结论 (Overall): [就绪 / 未就绪] -Issues to Fix: +待修复问题: 1. ... 2. ... ``` -## Continuous Mode +## 持续模式(Continuous Mode) -- Re-run phases on significant changes or every 30–60 minutes in long sessions -- Keep a short loop: `mvn -T 4 test` + spotbugs for quick feedback +- 在发生显著变更时,或在长会话中每 30–60 分钟重新运行各阶段。 +- 保持短反馈循环:运行 `mvn -T 4 test` + spotbugs 以获得快速反馈。 -**Remember**: Fast feedback beats late surprises. Keep the gate strict—treat warnings as defects in production systems. +**记住**:快速反馈优于后期惊讶。保持严格的准入门槛——在生产系统中,将警告(Warnings)视为缺陷(Defects)。 diff --git a/translation_workdir/cache/translation_db.json b/translation_workdir/cache/translation_db.json index 28081c5..797863f 100644 --- a/translation_workdir/cache/translation_db.json +++ b/translation_workdir/cache/translation_db.json @@ -4,8 +4,8 @@ "content": "# Claude Code \u5168\u65b9\u4f4d\u901f\u67e5\u624b\u518c\n\n![\u9875\u7709\uff1aAnthropic \u9ed1\u5ba2\u677e\u83b7\u80dc\u8005 - Claude Code \u63d0\u793a\u4e0e\u6280\u5de7](./assets/images/shortform/00-header.png)\n\n---\n\n**\u81ea 2 \u6708\u4efd\u5b9e\u9a8c\u6027\u63a8\u51fa\u4ee5\u6765\uff0c\u6211\u4e00\u76f4\u662f Claude Code \u7684\u5fe0\u5b9e\u7528\u6237\u3002\u6211\u4e0e [@DRodriguezFX](https://x.com/DRodriguezFX) \u5408\u4f5c\uff0c\u5b8c\u5168\u4f7f\u7528 Claude Code \u5f00\u53d1\u4e86 [zenith.chat](https://zenith.chat)\uff0c\u5e76\u8d62\u5f97\u4e86 Anthropic x Forum Ventures \u9ed1\u5ba2\u677e\u3002**\n\n\u4ee5\u4e0b\u662f\u6211\u5728 10 \u4e2a\u6708\u7684\u65e5\u5e38\u4f7f\u7528\u540e\u603b\u7ed3\u51fa\u7684\u5b8c\u6574\u914d\u7f6e\uff1a\u6280\u80fd\uff08Skills\uff09\u3001\u751f\u547d\u5468\u671f\u94a9\u5b50\uff08Hooks\uff09\u3001\u5b50\u667a\u80fd\u4f53\uff08Subagents\uff09\u3001\u6a21\u578b\u4e0a\u4e0b\u6587\u534f\u8bae\uff08MCPs\uff09\u3001\u63d2\u4ef6\uff08Plugins\uff09\u4ee5\u53ca\u771f\u6b63\u884c\u4e4b\u6709\u6548\u7684\u5b9e\u8df5\u65b9\u6848\u3002\n\n---\n\n## \u6280\u80fd\uff08Skills\uff09\u4e0e\u547d\u4ee4\uff08Commands\uff09\n\n\u6280\u80fd\uff08Skills\uff09\u7c7b\u4f3c\u4e8e\u89c4\u5219\uff0c\u4f46\u88ab\u9650\u5b9a\u5728\u7279\u5b9a\u7684\u4f5c\u7528\u57df\u548c\u5de5\u4f5c\u6d41\u4e2d\u3002\u5f53\u4f60\u9700\u8981\u6267\u884c\u7279\u5b9a\u5de5\u4f5c\u6d41\u65f6\uff0c\u5b83\u4eec\u662f\u63d0\u793a\u8bcd\uff08Prompts\uff09\u7684\u5feb\u6377\u65b9\u5f0f\u3002\n\n\u5728\u4f7f\u7528 Opus 4.5 \u8fdb\u884c\u957f\u65f6\u95f4\u7f16\u7801\u540e\uff0c\u60f3\u8981\u6e05\u7406\u5e9f\u5f03\u4ee3\u7801\u548c\u6563\u843d\u7684 .md \u6587\u4ef6\uff1f\u8fd0\u884c `/refactor-clean`\u3002\u9700\u8981\u6d4b\u8bd5\uff1f\u4f7f\u7528 `/tdd`\u3001`/e2e`\u3001`/test-coverage`\u3002\u6280\u80fd\u8fd8\u53ef\u4ee5\u5305\u542b\u4ee3\u7801\u6620\u5c04\uff08Codemaps\uff09\u2014\u2014\u8fd9\u662f\u4e00\u79cd\u8ba9 Claude \u5feb\u901f\u5bfc\u822a\u4ee3\u7801\u5e93\u7684\u65b9\u6cd5\uff0c\u800c\u4e0d\u4f1a\u5728\u63a2\u7d22\u8fc7\u7a0b\u4e2d\u6d6a\u8d39\u4e0a\u4e0b\u6587\uff08Context\uff09\u3002\n\n![\u7ec8\u7aef\u663e\u793a\u94fe\u5f0f\u547d\u4ee4](./assets/images/shortform/02-chaining-commands.jpeg)\n*\u94fe\u5f0f\u8c03\u7528\u591a\u4e2a\u547d\u4ee4*\n\n\u547d\u4ee4\uff08Commands\uff09\u662f\u901a\u8fc7\u659c\u6760\u547d\u4ee4\uff08Slash Commands\uff09\u6267\u884c\u7684\u6280\u80fd\u3002\u5b83\u4eec\u5728\u529f\u80fd\u4e0a\u6709\u91cd\u53e0\uff0c\u4f46\u5b58\u50a8\u65b9\u5f0f\u4e0d\u540c\uff1a\n\n- **\u6280\u80fd (Skills)**: `~/.claude/skills/` - \u66f4\u5e7f\u6cdb\u7684\u5de5\u4f5c\u6d41\u5b9a\u4e49\n- **\u547d\u4ee4 (Commands)**: `~/.claude/commands/` - \u5feb\u901f\u53ef\u6267\u884c\u7684\u63d0\u793a\u8bcd\n\n```bash\n# \u793a\u4f8b\u6280\u80fd\u7ed3\u6784\n~/.claude/skills/\n pmx-guidelines.md # \u9879\u76ee\u7279\u5b9a\u6a21\u5f0f\n coding-standards.md # \u8bed\u8a00\u6700\u4f73\u5b9e\u8df5\n tdd-workflow/ # \u5305\u542b README.md \u7684\u591a\u6587\u4ef6\u6280\u80fd\n security-review/ # \u57fa\u4e8e\u6e05\u5355\u7684\u6280\u80fd\n```\n\n---\n\n## \u751f\u547d\u5468\u671f\u94a9\u5b50\uff08Hooks\uff09\n\n\u751f\u547d\u5468\u671f\u94a9\u5b50\uff08Hooks\uff09\u662f\u57fa\u4e8e\u89e6\u53d1\u5668\u7684\u81ea\u52a8\u5316\u529f\u80fd\uff0c\u5728\u7279\u5b9a\u4e8b\u4ef6\u53d1\u751f\u65f6\u89e6\u53d1\u3002\u4e0e\u6280\u80fd\u4e0d\u540c\uff0c\u5b83\u4eec\u88ab\u9650\u5236\u5728\u5de5\u5177\u8c03\u7528\uff08Tool Calls\uff09\u548c\u751f\u547d\u5468\u671f\u4e8b\u4ef6\u4e2d\u3002\n\n**\u94a9\u5b50\u7c7b\u578b\uff1a**\n\n1. **PreToolUse** - \u5de5\u5177\u6267\u884c\u524d\uff08\u9a8c\u8bc1\u3001\u63d0\u9192\uff09\n2. **PostToolUse** - \u5de5\u5177\u6267\u884c\u540e\uff08\u683c\u5f0f\u5316\u3001\u53cd\u9988\u5faa\u73af\uff09\n3. **UserPromptSubmit** - \u53d1\u9001\u6d88\u606f\u65f6\n4. **Stop** - Claude \u5b8c\u6210\u54cd\u5e94\u65f6\n5. **PreCompact** - \u4e0a\u4e0b\u6587\u538b\u7f29\u524d\n6. **Notification** - \u6743\u9650\u8bf7\u6c42\n\n**\u793a\u4f8b\uff1a\u5728\u6267\u884c\u8017\u65f6\u547d\u4ee4\u524d\u53d1\u9001 tmux \u63d0\u9192**\n\n```json\n{\n \"PreToolUse\": [\n {\n \"matcher\": \"tool == \\\"Bash\\\" && tool_input.command matches \\\"(npm|pnpm|yarn|cargo|pytest)\\\"\",\n \"hooks\": [\n {\n \"type\": \"command\",\n \"command\": \"if [ -z \\\"$TMUX\\\" ]; then echo '[Hook] Consider tmux for session persistence' >&2; fi\"\n }\n ]\n }\n ]\n}\n```\n\n![PostToolUse \u94a9\u5b50\u53cd\u9988](./assets/images/shortform/03-posttooluse-hook.png)\n*\u8fd0\u884c PostToolUse \u94a9\u5b50\u65f6\u5728 Claude Code \u4e2d\u83b7\u5f97\u7684\u53cd\u9988\u793a\u4f8b*\n\n**\u4e13\u5bb6\u63d0\u793a\uff1a** \u4f7f\u7528 `hookify` \u63d2\u4ef6\u53ef\u4ee5\u901a\u8fc7\u5bf9\u8bdd\u65b9\u5f0f\u521b\u5efa\u94a9\u5b50\uff0c\u800c\u65e0\u9700\u624b\u52a8\u7f16\u5199 JSON\u3002\u8fd0\u884c `/hookify` \u5e76\u63cf\u8ff0\u4f60\u7684\u9700\u6c42\u5373\u53ef\u3002\n\n---\n\n## \u5b50\u667a\u80fd\u4f53\uff08Subagents\uff09\n\n\u5b50\u667a\u80fd\u4f53\uff08Subagents\uff09\u662f\u4f60\u7684\u7f16\u6392\u5668\uff08\u4e3b Claude \u5b9e\u4f8b\uff09\u53ef\u4ee5\u59d4\u6d3e\u4efb\u52a1\u7684\u8fdb\u7a0b\uff0c\u5177\u6709\u53d7\u9650\u7684\u4f5c\u7528\u57df\u3002\u5b83\u4eec\u53ef\u4ee5\u5728\u540e\u53f0\u6216\u524d\u53f0\u8fd0\u884c\uff0c\u4ece\u800c\u4e3a\u4e3b\u667a\u80fd\u4f53\u91ca\u653e\u4e0a\u4e0b\u6587\u7a7a\u95f4\u3002\n\n\u5b50\u667a\u80fd\u4f53\u4e0e\u6280\u80fd\u914d\u5408\u5f97\u975e\u5e38\u597d\u2014\u2014\u80fd\u591f\u6267\u884c\u90e8\u5206\u6280\u80fd\u96c6\u7684\u5b50\u667a\u80fd\u4f53\u53ef\u4ee5\u88ab\u59d4\u6d3e\u4efb\u52a1\u5e76\u81ea\u4e3b\u4f7f\u7528\u8fd9\u4e9b\u6280\u80fd\u3002\u5b83\u4eec\u8fd8\u53ef\u4ee5\u901a\u8fc7\u7279\u5b9a\u7684\u5de5\u5177\u6743\u9650\u8fdb\u884c\u6c99\u7bb1\u5316\u5904\u7406\u3002\n\n```bash\n# \u793a\u4f8b\u5b50\u667a\u80fd\u4f53\u7ed3\u6784\n~/.claude/agents/\n planner.md # \u529f\u80fd\u5b9e\u73b0\u89c4\u5212\n architect.md # \u7cfb\u7edf\u8bbe\u8ba1\u51b3\u7b56\n tdd-guide.md # \u6d4b\u8bd5\u9a71\u52a8\u5f00\u53d1\n code-reviewer.md # \u8d28\u91cf/\u5b89\u5168\u5ba1\u67e5\n security-reviewer.md # \u6f0f\u6d1e\u5206\u6790\n build-error-resolver.md\n e2e-runner.md\n refactor-cleaner.md\n```\n\n\u4e3a\u6bcf\u4e2a\u5b50\u667a\u80fd\u4f53\u914d\u7f6e\u5141\u8bb8\u7684\u5de5\u5177\u3001MCP \u548c\u6743\u9650\uff0c\u4ee5\u5b9e\u73b0\u9002\u5f53\u7684\u4f5c\u7528\u57df\u9650\u5b9a\u3002\n\n---\n\n## \u89c4\u5219\uff08Rules\uff09\u4e0e\u8bb0\u5fc6\uff08Memory\uff09\n\n\u4f60\u7684 `.rules` \u6587\u4ef6\u5939\u5b58\u653e\u7740 Claude \u5e94\u8be5\u59cb\u7ec8\u9075\u5faa\u7684\u6700\u4f73\u5b9e\u8df5 `.md` \u6587\u4ef6\u3002\u6709\u4e24\u79cd\u65b9\u6cd5\uff1a\n\n1. **\u5355\u4e2a CLAUDE.md** - \u6240\u6709\u5185\u5bb9\u653e\u5728\u4e00\u4e2a\u6587\u4ef6\u4e2d\uff08\u7528\u6237\u7ea7\u6216\u9879\u76ee\u7ea7\uff09\n2. **Rules \u6587\u4ef6\u5939** - \u6309\u5173\u6ce8\u70b9\u5206\u7ec4\u7684\u6a21\u5757\u5316 `.md` \u6587\u4ef6\n\n```bash\n~/.claude/rules/\n security.md # \u7981\u6b62\u786c\u7f16\u7801\u5bc6\u94a5\uff0c\u9a8c\u8bc1\u8f93\u5165\n coding-style.md # \u4e0d\u53ef\u53d8\u6027\uff0c\u6587\u4ef6\u7ec4\u7ec7\n testing.md # TDD \u5de5\u4f5c\u6d41\uff0c80% \u8986\u76d6\u7387\n git-workflow.md # \u63d0\u4ea4\u683c\u5f0f\uff0cPR \u6d41\u7a0b\n agents.md # \u4f55\u65f6\u59d4\u6d3e\u7ed9\u5b50\u667a\u80fd\u4f53\n performance.md # \u6a21\u578b\u9009\u62e9\uff0c\u4e0a\u4e0b\u6587\u7ba1\u7406\n```\n\n**\u793a\u4f8b\u89c4\u5219\uff1a**\n\n- \u4ee3\u7801\u5e93\u4e2d\u4e25\u7981\u4f7f\u7528\u8868\u60c5\u7b26\u53f7 (Emojis)\n- \u524d\u7aef\u907f\u514d\u4f7f\u7528\u7d2b\u8272\u8c03\n- \u90e8\u7f72\u524d\u59cb\u7ec8\u6d4b\u8bd5\u4ee3\u7801\n- \u4f18\u5148\u91c7\u7528\u6a21\u5757\u5316\u4ee3\u7801\u800c\u975e\u8d85\u5927\u6587\u4ef6\n- \u4e25\u7981\u63d0\u4ea4 `console.log`\n\n---\n\n## \u6a21\u578b\u4e0a\u4e0b\u6587\u534f\u8bae\uff08MCPs\uff09\n\n\u6a21\u578b\u4e0a\u4e0b\u6587\u534f\u8bae\uff08MCPs\uff09\u5c06 Claude \u76f4\u63a5\u8fde\u63a5\u5230\u5916\u90e8\u670d\u52a1\u3002\u5b83\u4e0d\u662f API \u7684\u66ff\u4ee3\u54c1\uff0c\u800c\u662f\u56f4\u7ed5 API \u7684\u63d0\u793a\u8bcd\u9a71\u52a8\u5c01\u88c5\uff0c\u5141\u8bb8\u5728\u5bfc\u822a\u4fe1\u606f\u65f6\u5177\u6709\u66f4\u9ad8\u7684\u7075\u6d3b\u6027\u3002\n\n**\u793a\u4f8b\uff1a** Supabase MCP \u8ba9 Claude \u80fd\u591f\u62c9\u53d6\u7279\u5b9a\u6570\u636e\uff0c\u76f4\u63a5\u5728\u4e0a\u6e38\u8fd0\u884c SQL\uff0c\u65e0\u9700\u590d\u5236\u7c98\u8d34\u3002\u6570\u636e\u5e93\u3001\u90e8\u7f72\u5e73\u53f0\u7b49\u540c\u7406\u3002\n\n![Supabase MCP \u5217\u51fa\u6570\u636e\u8868](./assets/images/shortform/04-supabase-mcp.jpeg)\n*Supabase MCP \u5217\u51fa public \u6a21\u5f0f\u4e0b\u6570\u636e\u8868\u7684\u793a\u4f8b*\n\n**Claude \u5185\u90e8\u7684 Chrome\uff1a** \u662f\u4e00\u4e2a\u5185\u7f6e\u7684 MCP \u63d2\u4ef6\uff0c\u5141\u8bb8 Claude \u81ea\u4e3b\u63a7\u5236\u6d4f\u89c8\u5668\u2014\u2014\u901a\u8fc7\u70b9\u51fb\u6765\u67e5\u770b\u529f\u80fd\u8fd0\u884c\u60c5\u51b5\u3002\n\n**\u5173\u952e\u70b9\uff1a\u4e0a\u4e0b\u6587\u7a97\u53e3\uff08Context Window\uff09\u7ba1\u7406**\n\n\u5bf9 MCP \u8981\u7cbe\u6311\u7ec6\u9009\u3002\u6211\u5c06\u6240\u6709 MCP \u4fdd\u7559\u5728\u7528\u6237\u914d\u7f6e\u4e2d\uff0c\u4f46**\u7981\u7528\u6240\u6709\u4e0d\u4f7f\u7528\u7684 MCP**\u3002\u5bfc\u822a\u81f3 `/plugins` \u5e76\u5411\u4e0b\u6eda\u52a8\u6216\u8fd0\u884c `/mcp`\u3002\n\n![/plugins \u754c\u9762](./assets/images/shortform/05-plugins-interface.jpeg)\n*\u4f7f\u7528 /plugins \u5bfc\u822a\u81f3 MCP\uff0c\u67e5\u770b\u5f53\u524d\u5df2\u5b89\u88c5\u7684 MCP \u53ca\u5176\u72b6\u6001*\n\n\u7531\u4e8e\u542f\u7528\u4e86\u8fc7\u591a\u5de5\u5177\uff0c\u4f60\u5728\u538b\u7f29\u524d\u7684 200k \u4e0a\u4e0b\u6587\u7a97\u53e3\u53ef\u80fd\u5b9e\u9645\u4e0a\u53ea\u5269 70k\u3002\u6027\u80fd\u4f1a\u663e\u8457\u4e0b\u964d\u3002\n\n**\u7ecf\u9a8c\u6cd5\u5219\uff1a** \u914d\u7f6e\u4e2d\u4fdd\u7559 20-30 \u4e2a MCP\uff0c\u4f46\u4fdd\u6301\u542f\u7528\u7684\u5c11\u4e8e 10 \u4e2a / \u6d3b\u8dc3\u5de5\u5177\u5c11\u4e8e 80 \u4e2a\u3002\n\n```bash\n# \u68c0\u67e5\u5df2\u542f\u7528\u7684 MCP\n/mcp\n\n# \u5728 ~/.claude.json \u7684 projects.disabledMcpServers \u4e2d\u7981\u7528\u4e0d\u4f7f\u7528\u7684 MCP\n```\n\n---\n\n## \u63d2\u4ef6\uff08Plugins\uff09\n\n\u63d2\u4ef6\uff08Plugins\uff09\u5c01\u88c5\u4e86\u5de5\u5177\u4ee5\u4fbf\u4e8e\u5b89\u88c5\uff0c\u907f\u514d\u7e41\u7410\u7684\u624b\u52a8\u8bbe\u7f6e\u3002\u4e00\u4e2a\u63d2\u4ef6\u53ef\u4ee5\u662f\u6280\u80fd\uff08Skill\uff09\u4e0e MCP \u7684\u7ec4\u5408\uff0c\u4e5f\u53ef\u4ee5\u662f\u7ed1\u5b9a\u5728\u4e00\u8d77\u7684\u94a9\u5b50\uff08Hooks\uff09/\u5de5\u5177\uff08Tools\uff09\u3002\n\n**\u5b89\u88c5\u63d2\u4ef6\uff1a**\n\n```bash\n# \u6dfb\u52a0\u5e02\u573a\nclaude plugin marketplace add https://github.com/mixedbread-ai/mgrep\n\n# \u6253\u5f00 Claude\uff0c\u8fd0\u884c /plugins\uff0c\u627e\u5230\u65b0\u5e02\u573a\uff0c\u5e76\u4ece\u4e2d\u5b89\u88c5\n```\n\n![\u663e\u793a mgrep \u7684\u5e02\u573a\u6807\u7b7e\u9875](./assets/images/shortform/06-marketplaces-mgrep.jpeg)\n*\u663e\u793a\u65b0\u5b89\u88c5\u7684 Mixedbread-Grep \u5e02\u573a*\n\n\u5982\u679c\u4f60\u7ecf\u5e38\u5728\u7f16\u8f91\u5668\u4e4b\u5916\u8fd0\u884c Claude Code\uff0c**LSP \u63d2\u4ef6**\u7279\u522b\u6709\u7528\u3002\u8bed\u8a00\u670d\u52a1\u5668\u534f\u8bae\uff08Language Server Protocol\uff09\u4e3a Claude \u63d0\u4f9b\u4e86\u5b9e\u65f6\u7c7b\u578b\u68c0\u67e5\u3001\u8f6c\u5230\u5b9a\u4e49\u548c\u667a\u80fd\u8865\u5168\u529f\u80fd\uff0c\u65e0\u9700\u6253\u5f00 IDE\u3002\n\n```bash\n# \u5df2\u542f\u7528\u63d2\u4ef6\u793a\u4f8b\ntypescript-lsp@claude-plugins-official # TypeScript \u667a\u80fd\u63d0\u793a\npyright-lsp@claude-plugins-official # Python \u7c7b\u578b\u68c0\u67e5\nhookify@claude-plugins-official # \u5bf9\u8bdd\u5f0f\u521b\u5efa\u94a9\u5b50\nmgrep@Mixedbread-Grep # \u6bd4 ripgrep \u66f4\u597d\u7684\u641c\u7d22\n```\n\n\u4e0e MCP \u540c\u6837\u7684\u8b66\u544a\u2014\u2014\u6ce8\u610f\u4f60\u7684\u4e0a\u4e0b\u6587\u7a97\u53e3\u3002\n\n---\n\n## \u6280\u5de7\u4e0e\u5efa\u8bae\n\n### \u952e\u76d8\u5feb\u6377\u952e\n\n- `Ctrl+U` - \u5220\u9664\u6574\u884c\uff08\u6bd4\u72c2\u6309\u9000\u683c\u952e\u5feb\uff09\n- `!` - \u5feb\u901f Bash \u547d\u4ee4\u524d\u7f00\n- `@` - \u641c\u7d22\u6587\u4ef6\n- `/` - \u542f\u52a8\u659c\u6760\u547d\u4ee4\n- `Shift+Enter` - \u591a\u884c\u8f93\u5165\n- `Tab` - \u5207\u6362\u601d\u8003\u8fc7\u7a0b\u663e\u793a\n- `Esc Esc` - \u4e2d\u65ad Claude / \u6062\u590d\u4ee3\u7801\n\n### \u5e76\u884c\u5de5\u4f5c\u6d41\n\n- **\u6d3e\u751f** (`/fork`) - \u6d3e\u751f\u5bf9\u8bdd\u4ee5\u5e76\u884c\u6267\u884c\u4e0d\u91cd\u53e0\u7684\u4efb\u52a1\uff0c\u800c\u4e0d\u662f\u8ba9\u6392\u961f\u7684\u8bf7\u6c42\u5806\u79ef\n- **Git \u5de5\u4f5c\u6811 (Worktrees)** - \u7528\u4e8e\u5e76\u884c\u8fd0\u884c\u591a\u4e2a Claude \u5b9e\u4f8b\u800c\u4e0d\u4f1a\u4ea7\u751f\u51b2\u7a81\u3002\u6bcf\u4e2a\u5de5\u4f5c\u6811\u90fd\u662f\u4e00\u4e2a\u72ec\u7acb\u7684\u68c0\u51fa\u76ee\u5f55\n\n```bash\ngit worktree add ../feature-branch feature-branch\n# \u73b0\u5728\u5728\u6bcf\u4e2a\u5de5\u4f5c\u6811\u4e2d\u8fd0\u884c\u72ec\u7acb\u7684 Claude \u5b9e\u4f8b\n```\n\n### \u4f7f\u7528 tmux \u5904\u7406\u8017\u65f6\u547d\u4ee4\n\n\u4e32\u6d41\u5e76\u89c2\u5bdf Claude \u8fd0\u884c\u7684\u65e5\u5fd7/Bash \u8fdb\u7a0b\uff1a\n\nhttps://github.com/user-attachments/assets/shortform/07-tmux-video.mp4\n\n```bash\ntmux new -s dev\n# Claude \u5728\u8fd9\u91cc\u8fd0\u884c\u547d\u4ee4\uff0c\u4f60\u53ef\u4ee5\u968f\u65f6\u5206\u79bb (detach) \u548c\u91cd\u65b0\u8fde\u63a5 (reattach)\ntmux attach -t dev\n```\n\n### mgrep > grep\n\n`mgrep` \u662f\u5bf9 ripgrep/grep \u7684\u91cd\u5927\u6539\u8fdb\u3002\u901a\u8fc7\u63d2\u4ef6\u5e02\u573a\u5b89\u88c5\uff0c\u7136\u540e\u4f7f\u7528 `/mgrep` \u6280\u80fd\u3002\u540c\u65f6\u652f\u6301\u672c\u5730\u641c\u7d22\u548c\u7f51\u7edc\u641c\u7d22\u3002\n\n```bash\nmgrep \"function handleSubmit\" # \u672c\u5730\u641c\u7d22\nmgrep --web \"Next.js 15 app router changes\" # \u7f51\u7edc\u641c\u7d22\n```\n\n### \u5176\u4ed6\u6709\u7528\u547d\u4ee4\n\n- `/rewind` - \u56de\u9000\u5230\u4e4b\u524d\u7684\u72b6\u6001\n- `/statusline` - \u81ea\u5b9a\u4e49\u663e\u793a\u5206\u652f\u3001\u4e0a\u4e0b\u6587\u5360\u6bd4\u3001\u5f85\u529e\u4e8b\u9879 (Todos)\n- `/checkpoints` - \u6587\u4ef6\u7ea7\u64a4\u9500\u70b9\n- `/compact` - \u624b\u52a8\u89e6\u53d1\u4e0a\u4e0b\u6587\u538b\u7f29\n\n### GitHub Actions CI/CD\n\n\u4f7f\u7528 GitHub Actions \u5728 PR \u4e0a\u8bbe\u7f6e\u4ee3\u7801\u5ba1\u67e5\u3002\u914d\u7f6e\u5b8c\u6210\u540e\uff0cClaude \u53ef\u4ee5\u81ea\u52a8\u5ba1\u67e5 PR\u3002\n\n![Claude \u673a\u5668\u4eba\u6279\u51c6 PR](./assets/images/shortform/08-github-pr-review.jpeg)\n*Claude \u6279\u51c6\u4e86\u4e00\u4e2a\u6f0f\u6d1e\u4fee\u590d PR*\n\n### \u6c99\u7bb1\u5316\uff08Sandboxing\uff09\n\n\u5bf9\u98ce\u9669\u64cd\u4f5c\u4f7f\u7528\u6c99\u7bb1\u6a21\u5f0f\u2014\u2014Claude \u5728\u53d7\u9650\u73af\u5883\u4e2d\u8fd0\u884c\uff0c\u4e0d\u4f1a\u5f71\u54cd\u4f60\u7684\u5b9e\u9645\u7cfb\u7edf\u3002\n\n---\n\n## \u5173\u4e8e\u7f16\u8f91\u5668\n\n\u7f16\u8f91\u5668\u7684\u9009\u62e9\u4f1a\u663e\u8457\u5f71\u54cd Claude Code \u7684\u5de5\u4f5c\u6d41\u3002\u867d\u7136 Claude Code \u53ef\u4ee5\u4ece\u4efb\u4f55\u7ec8\u7aef\u8fd0\u884c\uff0c\u4f46\u914d\u5408\u529f\u80fd\u5f3a\u5927\u7684\u7f16\u8f91\u5668\u53ef\u4ee5\u89e3\u9501\u5b9e\u65f6\u6587\u4ef6\u8ddf\u8e2a\u3001\u5feb\u901f\u5bfc\u822a\u548c\u96c6\u6210\u547d\u4ee4\u6267\u884c\u3002\n\n### Zed\uff08\u6211\u7684\u9996\u9009\uff09\n\n\u6211\u4f7f\u7528 [Zed](https://zed.dev) \u2014\u2014 \u7528 Rust \u7f16\u5199\uff0c\u901f\u5ea6\u6781\u5feb\u3002\u77ac\u95f4\u6253\u5f00\uff0c\u5904\u7406\u5e9e\u5927\u7684\u4ee3\u7801\u5e93\u4e5f\u6e38\u5203\u6709\u4f59\uff0c\u4e14\u51e0\u4e4e\u4e0d\u5360\u7528\u7cfb\u7edf\u8d44\u6e90\u3002\n\n**\u4e3a\u4ec0\u4e48 Zed + Claude Code \u662f\u7edd\u4f73\u7ec4\u5408\uff1a**\n\n- **\u901f\u5ea6** - \u57fa\u4e8e Rust \u7684\u6027\u80fd\u610f\u5473\u7740\u5f53 Claude \u5feb\u901f\u7f16\u8f91\u6587\u4ef6\u65f6\u4e0d\u4f1a\u6709\u5ef6\u8fdf\u3002\u4f60\u7684\u7f16\u8f91\u5668\u80fd\u8ddf\u4e0a\u8282\u594f\n- **\u667a\u80fd\u4f53\u9762\u677f\u96c6\u6210** - Zed \u7684 Claude \u96c6\u6210\u8ba9\u4f60\u5728 Claude \u7f16\u8f91\u65f6\u5b9e\u65f6\u8ddf\u8e2a\u6587\u4ef6\u66f4\u6539\u3002\u65e0\u9700\u79bb\u5f00\u7f16\u8f91\u5668\u5373\u53ef\u5728 Claude \u5f15\u7528\u7684\u6587\u4ef6\u95f4\u8df3\u8f6c\n- **CMD+Shift+R \u547d\u4ee4\u9762\u677f** - \u5728\u53ef\u641c\u7d22\u7684 UI \u4e2d\u5feb\u901f\u8bbf\u95ee\u6240\u6709\u81ea\u5b9a\u4e49\u659c\u6760\u547d\u4ee4\u3001\u8c03\u8bd5\u5668\u548c\u6784\u5efa\u811a\u672c\n- **\u6781\u4f4e\u7684\u8d44\u6e90\u5360\u7528** - \u5728\u6267\u884c\u7e41\u91cd\u64cd\u4f5c\u65f6\u4e0d\u4f1a\u4e0e Claude \u4e89\u593a RAM/CPU\u3002\u8fd0\u884c Opus \u65f6\u8fd9\u4e00\u70b9\u5f88\u91cd\u8981\n- **Vim \u6a21\u5f0f** - \u5982\u679c\u4f60\u4e60\u60ef Vim\uff0c\u5b83\u6709\u5b8c\u6574\u7684 Vim \u952e\u7ed1\u5b9a\u652f\u6301\n\n![\u5e26\u6709\u81ea\u5b9a\u4e49\u547d\u4ee4\u7684 Zed \u7f16\u8f91\u5668](./assets/images/shortform/09-zed-editor.jpeg)\n*\u4f7f\u7528 CMD+Shift+R \u5f39\u51fa\u81ea\u5b9a\u4e49\u547d\u4ee4\u4e0b\u62c9\u5217\u8868\u7684 Zed \u7f16\u8f91\u5668\u3002\u53f3\u4e0b\u89d2\u7684\u725b\u773c\u56fe\u6807\u663e\u793a\u4e86\u8ddf\u968f\u6a21\u5f0f (Following mode)\u3002*\n\n**\u7f16\u8f91\u5668\u901a\u7528\u6280\u5de7\uff1a**\n\n1. **\u5206\u5c4f\u663e\u793a** - \u4e00\u4fa7\u662f\u8fd0\u884c Claude Code \u7684\u7ec8\u7aef\uff0c\u53e6\u4e00\u4fa7\u662f\u7f16\u8f91\u5668\n2. **Ctrl + G** - \u5728 Zed \u4e2d\u5feb\u901f\u6253\u5f00 Claude \u5f53\u524d\u6b63\u5728\u5904\u7406\u7684\u6587\u4ef6\n3. **\u81ea\u52a8\u4fdd\u5b58** - \u5f00\u542f\u81ea\u52a8\u4fdd\u5b58\uff0c\u786e\u4fdd Claude \u8bfb\u53d6\u7684\u6587\u4ef6\u59cb\u7ec8\u662f\u6700\u65b0\u7684\n4. **Git \u96c6\u6210** - \u4f7f\u7528\u7f16\u8f91\u5668\u7684 Git \u529f\u80fd\u5728\u63d0\u4ea4\u524d\u5ba1\u67e5 Claude \u7684\u66f4\u6539\n5. **\u6587\u4ef6\u76d1\u542c\u5668** - \u5927\u591a\u6570\u7f16\u8f91\u5668\u4f1a\u81ea\u52a8\u91cd\u8f7d\u66f4\u6539\u540e\u7684\u6587\u4ef6\uff0c\u8bf7\u786e\u8ba4\u8be5\u529f\u80fd\u5df2\u542f\u7528\n\n### VSCode / Cursor\n\n\u8fd9\u4e5f\u662f\u4e00\u4e2a\u53ef\u884c\u7684\u9009\u62e9\uff0c\u5e76\u4e14\u4e0e Claude Code \u914d\u5408\u826f\u597d\u3002\u4f60\u53ef\u4ee5\u901a\u8fc7\u7ec8\u7aef\u5f62\u5f0f\u4f7f\u7528\u5b83\uff0c\u5229\u7528 `\\ide` \u81ea\u52a8\u4e0e\u7f16\u8f91\u5668\u540c\u6b65\u5e76\u542f\u7528 LSP \u529f\u80fd\uff08\u73b0\u5728\u5728\u67d0\u79cd\u7a0b\u5ea6\u4e0a\u4e0e\u63d2\u4ef6\u529f\u80fd\u91cd\u53e0\uff09\u3002\u6216\u8005\u4f60\u53ef\u4ee5\u9009\u62e9\u6269\u5c55\u7a0b\u5e8f\uff0c\u5b83\u4e0e\u7f16\u8f91\u5668\u96c6\u6210\u5ea6\u66f4\u9ad8\uff0c\u5e76\u62e5\u6709\u5339\u914d\u7684 UI\u3002\n\n![VS Code Claude Code \u6269\u5c55](./assets/images/shortform/10-vscode-extension.jpeg)\n*VS Code \u6269\u5c55\u4e3a Claude Code \u63d0\u4f9b\u4e86\u539f\u751f\u56fe\u5f62\u754c\u9762\uff0c\u76f4\u63a5\u96c6\u6210\u5230\u4f60\u7684 IDE \u4e2d\u3002*\n\n---\n\n## \u6211\u7684\u914d\u7f6e\n\n### \u63d2\u4ef6 (Plugins)\n\n**\u5df2\u5b89\u88c5\uff1a**\uff08\u6211\u901a\u5e38\u4e00\u6b21\u53ea\u542f\u7528 4-5 \u4e2a\uff09\n\n```markdown\nralph-wiggum@claude-code-plugins # \u5faa\u73af\u81ea\u52a8\u5316\nfrontend-design@claude-code-plugins # UI/UX \u6a21\u5f0f\ncommit-commands@claude-code-plugins # Git \u5de5\u4f5c\u6d41\nsecurity-guidance@claude-code-plugins # \u5b89\u5168\u68c0\u67e5\npr-review-toolkit@claude-code-plugins # PR \u81ea\u52a8\u5316\ntypescript-lsp@claude-plugins-official # TS \u667a\u80fd\u63d0\u793a\nhookify@claude-plugins-official # \u94a9\u5b50\u521b\u5efa\ncode-simplifier@claude-plugins-official\nfeature-dev@claude-code-plugins\nexplanatory-output-style@claude-plugins-official\ncode-review@claude-code-plugins\ncontext7@claude-plugins-official # \u5b9e\u65f6\u6587\u6863\npyright-lsp@claude-plugins-official # Python \u7c7b\u578b\nmgrep@Mixedbread-Grep # \u66f4\u597d\u7684\u641c\u7d22\n```\n\n### MCP \u670d\u52a1\u5668\n\n**\u5df2\u914d\u7f6e\uff08\u7528\u6237\u7ea7\uff09\uff1a**\n\n```json\n{\n \"github\": { \"command\": \"npx\", \"args\": [\"-y\", \"@modelcontextprotocol/server-github\"] },\n \"firecrawl\": { \"command\": \"npx\", \"args\": [\"-y\", \"firecrawl-mcp\"] },\n \"supabase\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@supabase/mcp-server-supabase@latest\", \"--project-ref=YOUR_REF\"]\n },\n \"memory\": { \"command\": \"npx\", \"args\": [\"-y\", \"@modelcontextprotocol/server-memory\"] },\n \"sequential-thinking\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@modelcontextprotocol/server-sequential-thinking\"]\n },\n \"vercel\": { \"type\": \"http\", \"url\": \"https://mcp.vercel.com\" },\n \"railway\": { \"command\": \"npx\", \"args\": [\"-y\", \"@railway/mcp-server\"] },\n \"cloudflare-docs\": { \"type\": \"http\", \"url\": \"https://docs.mcp.cloudflare.com/mcp\" },\n \"cloudflare-workers-bindings\": {\n \"type\": \"http\",\n \"url\": \"https://bindings.mcp.cloudflare.com/mcp\"\n },\n \"clickhouse\": { \"type\": \"http\", \"url\": \"https://mcp.clickhouse.cloud/mcp\" },\n \"AbletonMCP\": { \"command\": \"uvx\", \"args\": [\"ableton-mcp\"] },\n \"magic\": { \"command\": \"npx\", \"args\": [\"-y\", \"@magicuidesign/mcp@latest\"] }\n}\n```\n\n\u5173\u952e\u5728\u4e8e\uff1a\u6211\u914d\u7f6e\u4e86 14 \u4e2a MCP\uff0c\u4f46\u6bcf\u4e2a\u9879\u76ee\u53ea\u542f\u7528 ~5-6 \u4e2a\u3002\u8fd9\u80fd\u4fdd\u6301\u4e0a\u4e0b\u6587\u7a97\u53e3\u7684\u5065\u5eb7\u3002\n\n### \u5173\u952e\u94a9\u5b50 (Key Hooks)\n\n```json\n{\n \"PreToolUse\": [\n { \"matcher\": \"npm|pnpm|yarn|cargo|pytest\", \"hooks\": [\"tmux reminder\"] },\n { \"matcher\": \"Write && .md file\", \"hooks\": [\"block unless README/CLAUDE\"] },\n { \"matcher\": \"git push\", \"hooks\": [\"open editor for review\"] }\n ],\n \"PostToolUse\": [\n { \"matcher\": \"Edit && .ts/.tsx/.js/.jsx\", \"hooks\": [\"prettier --write\"] },\n { \"matcher\": \"Edit && .ts/.tsx\", \"hooks\": [\"tsc --noEmit\"] },\n { \"matcher\": \"Edit\", \"hooks\": [\"grep console.log warning\"] }\n ],\n \"Stop\": [\n { \"matcher\": \"*\", \"hooks\": [\"check modified files for console.log\"] }\n ]\n}\n```\n\n### \u81ea\u5b9a\u4e49\u72b6\u6001\u680f (Custom Status Line)\n\n\u663e\u793a\u7528\u6237\u3001\u76ee\u5f55\u3001\u5e26\u810f\u6807\u8bb0\u7684 Git \u5206\u652f\u3001\u5269\u4f59\u4e0a\u4e0b\u6587 %\u3001\u6a21\u578b\u3001\u65f6\u95f4\u548c\u5f85\u529e\u4e8b\u9879\u8ba1\u6570\uff1a\n\n![\u81ea\u5b9a\u4e49\u72b6\u6001\u680f](./assets/images/shortform/11-statusline.jpeg)\n*\u6211\u7684 Mac \u6839\u76ee\u5f55\u4e0b\u7684\u72b6\u6001\u680f\u793a\u4f8b*\n\n```\naffoon:~ ctx:65% Opus 4.5 19:52\n\u258c\u258c \u89c4\u5212\u6a21\u5f0f\u5df2\u5f00\u542f (\u6309 shift+tab \u5207\u6362)\n```\n\n### \u89c4\u5219\u7ed3\u6784 (Rules Structure)\n\n```\n~/.claude/rules/\n security.md # \u5f3a\u5236\u6027\u5b89\u5168\u68c0\u67e5\n coding-style.md # \u4e0d\u53ef\u53d8\u6027\uff0c\u6587\u4ef6\u5927\u5c0f\u9650\u5236\n testing.md # TDD\uff0c80% \u8986\u76d6\u7387\n git-workflow.md # \u7ea6\u5b9a\u5f0f\u63d0\u4ea4 (Conventional commits)\n agents.md # \u5b50\u667a\u80fd\u4f53\u59d4\u6d3e\u89c4\u5219\n patterns.md # API \u54cd\u5e94\u683c\u5f0f\n performance.md # \u6a21\u578b\u9009\u62e9 (Haiku vs Sonnet vs Opus)\n hooks.md # \u94a9\u5b50\u6587\u6863\u8bf4\u660e\n```\n\n### \u5b50\u667a\u80fd\u4f53 (Subagents)\n\n```\n~/.claude/agents/\n planner.md # \u5206\u89e3\u529f\u80fd\u9700\u6c42\n architect.md # \u7cfb\u7edf\u8bbe\u8ba1\n tdd-guide.md # \u6d4b\u8bd5\u5148\u884c\n code-reviewer.md # \u8d28\u91cf\u5ba1\u67e5\n security-reviewer.md # \u6f0f\u6d1e\u626b\u63cf\n build-error-resolver.md\n e2e-runner.md # Playwright \u6d4b\u8bd5\n refactor-cleaner.md # \u5e9f\u5f03\u4ee3\u7801\u6e05\u7406\n doc-updater.md # \u4fdd\u6301\u6587\u6863\u540c\u6b65\n```\n\n---\n\n## \u6838\u5fc3\u8981\u70b9\n\n1. **\u4e0d\u8981\u8fc7\u5ea6\u590d\u6742\u5316** - \u5c06\u914d\u7f6e\u89c6\u4e3a\u5fae\u8c03\uff0c\u800c\u975e\u67b6\u6784\u8bbe\u8ba1\n2. **\u4e0a\u4e0b\u6587\u7a97\u53e3\u6781\u5176\u73cd\u8d35** - \u7981\u7528\u4e0d\u4f7f\u7528\u7684 MCP \u548c\u63d2\u4ef6\n3. **\u5e76\u884c\u6267\u884c** - \u6d3e\u751f\u5bf9\u8bdd\uff0c\u5229\u7528 Git \u5de5\u4f5c\u6811\n4. **\u81ea\u52a8\u5316\u91cd\u590d\u6027\u4efb\u52a1** - \u4e3a\u683c\u5f0f\u5316\u3001\u4ee3\u7801\u68c0\u67e5\u3001\u63d0\u9192\u8bbe\u7f6e\u94a9\u5b50\n5. **\u660e\u786e\u5b50\u667a\u80fd\u4f53\u7684\u4f5c\u7528\u57df** - \u53d7\u9650\u7684\u5de5\u5177 = \u4e13\u6ce8\u7684\u6267\u884c\n\n---\n\n## \u53c2\u8003\u8d44\u6599\n\n- [\u63d2\u4ef6\u53c2\u8003 (Plugins Reference)](https://code.claude.com/docs/en/plugins-reference)\n- [\u94a9\u5b50\u6587\u6863 (Hooks Documentation)](https://code.claude.com/docs/en/hooks)\n- [\u68c0\u67e5\u70b9\u529f\u80fd (Checkpointing)](https://code.claude.com/docs/en/checkpointing)\n- [\u4ea4\u4e92\u6a21\u5f0f (Interactive Mode)](https://code.claude.com/docs/en/interactive-mode)\n- [\u8bb0\u5fc6\u7cfb\u7edf (Memory System)](https://code.claude.com/docs/en/memory)\n- [\u5b50\u667a\u80fd\u4f53 (Subagents)](https://code.claude.com/docs/en/sub-agents)\n- [MCP \u6982\u89c8 (MCP Overview)](https://code.claude.com/docs/en/mcp-overview)\n\n---\n\n**\u6ce8\uff1a** \u4ee5\u4e0a\u4ec5\u4e3a\u90e8\u5206\u7ec6\u8282\u3002\u8fdb\u9636\u6a21\u5f0f\u8bf7\u53c2\u9605 [\u957f\u7bc7\u6307\u5357 (Longform Guide)](./the-longform-guide.md)\u3002\n\n---\n\n*\u4e0e [@DRodriguezFX](https://x.com/DRodriguezFX) \u5728\u7ebd\u7ea6\u5171\u540c\u5f00\u53d1 [zenith.chat](https://zenith.chat)\uff0c\u5e76\u8d62\u5f97 Anthropic x Forum Ventures \u9ed1\u5ba2\u677e\u3002*\n" }, "/Users/Library/Applications/xx/code/github/everything-claude-code-zh/README.md": { - "md5": "1d090c2531031d817553cd6b1e0b9b6d", - "content": "**\u8bed\u8a00\uff1a** [English](README.md) | \u7e41\u9ad4\u4e2d\u6587\n\n# Everything Claude Code\n\n[![Stars](https://img.shields.io/github/stars/affaan-m/everything-claude-code?style=flat)](https://github.com/affaan-m/everything-claude-code/stargazers)\n[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n![Shell](https://img.shields.io/badge/-Shell-4EAA25?logo=gnu-bash&logoColor=white)\n![TypeScript](https://img.shields.io/badge/-TypeScript-3178C6?logo=typescript&logoColor=white)\n![Go](https://img.shields.io/badge/-Go-00ADD8?logo=go&logoColor=white)\n![Markdown](https://img.shields.io/badge/-Markdown-000000?logo=markdown&logoColor=white)\n\n

\n English |\n \u7b80\u4f53\u4e2d\u6587\n

\n\n**\u7531 Anthropic \u9ed1\u5ba2\u677e\u83b7\u80dc\u8005\u6574\u7406\u7684 Claude Code \u914d\u7f6e\u5b8c\u6574\u5408\u96c6\u3002**\n\n\u8fd9\u662f\u5728 10 \u4e2a\u6708\u4ee5\u4e0a\u9ad8\u5f3a\u5ea6\u65e5\u5e38\u5f00\u53d1\u771f\u5b9e\u4ea7\u54c1\u7684\u8fc7\u7a0b\u4e2d\uff0c\u4e0d\u65ad\u6f14\u8fdb\u51fa\u7684\u751f\u4ea7\u7ea7\u667a\u80fd\u4f53\uff08Agents\uff09\u3001\u6280\u80fd\uff08Skills\uff09\u3001\u94a9\u5b50\uff08Hooks\uff09\u3001\u547d\u4ee4\uff08Commands\uff09\u3001\u89c4\u5219\uff08Rules\uff09\u4ee5\u53ca MCP \u914d\u7f6e\u3002\n\n---\n\n## \u6307\u5357\uff08The Guides\uff09\n\n\u672c\u4ed3\u5e93\u4ec5\u5305\u542b\u539f\u59cb\u4ee3\u7801\u3002\u4ee5\u4e0b\u6307\u5357\u5c06\u89e3\u91ca\u6240\u6709\u7ec6\u8282\u3002\n\n\n\n\n\n\n\n\n\n\n
\n\n\"Everything\n\n\n\n\"Everything\n\n
\u7b80\u660e\u6307\u5357 (Shorthand Guide)
\u5b89\u88c5\u3001\u57fa\u7840\u3001\u54f2\u5b66\u3002\u8bf7\u5148\u9605\u8bfb\u6b64\u7bc7\u3002
\u6df1\u5ea6\u6307\u5357 (Longform Guide)
Token \u4f18\u5316\u3001\u5185\u5b58\u6301\u4e45\u5316\u3001\u8bc4\u6d4b\uff08Evals\uff09\u3001\u5e76\u884c\u5316\u3002
\n\n| \u4e3b\u9898 | \u4f60\u5c06\u5b66\u5230\u4ec0\u4e48 |\n|-------|-------------------|\n| Token \u4f18\u5316 | \u6a21\u578b\u9009\u62e9\u3001\u7cfb\u7edf\u63d0\u793a\u8bcd\u7cbe\u7b80\u3001\u540e\u53f0\u8fdb\u7a0b |\n| \u5185\u5b58\u6301\u4e45\u5316 | \u8de8\u4f1a\u8bdd\uff08Session\uff09\u81ea\u52a8\u4fdd\u5b58/\u52a0\u8f7d\u4e0a\u4e0b\u6587\u7684\u94a9\u5b50\uff08Hooks\uff09 |\n| \u6301\u7eed\u5b66\u4e60 | \u4ece\u4f1a\u8bdd\u4e2d\u81ea\u52a8\u63d0\u53d6\u6a21\u5f0f\u5e76\u8f6c\u5316\u4e3a\u53ef\u590d\u7528\u7684\u6280\u80fd\uff08Skills\uff09 |\n| \u9a8c\u8bc1\u5faa\u73af | \u68c0\u67e5\u70b9\uff08Checkpoint\uff09\u4e0e\u6301\u7eed\u8bc4\u6d4b\uff08Evals\uff09\u3001\u8bc4\u5206\u5668\u7c7b\u578b\u3001pass@k \u6307\u6807 |\n| \u5e76\u884c\u5316 | Git worktrees\u3001\u7ea7\u8054\u65b9\u6cd5\u3001\u4f55\u65f6\u6269\u5c55\u5b9e\u4f8b |\n| \u5b50\u667a\u80fd\u4f53\u7f16\u6392 | \u4e0a\u4e0b\u6587\u95ee\u9898\u3001\u8fed\u4ee3\u68c0\u7d22\u6a21\u5f0f |\n\n---\n\n## \u8de8\u5e73\u53f0\u652f\u6301\n\n\u8be5\u63d2\u4ef6\u73b0\u5df2\u5168\u9762\u652f\u6301 **Windows\u3001macOS \u548c Linux**\u3002\u6240\u6709\u94a9\u5b50\uff08Hooks\uff09\u548c\u811a\u672c\u90fd\u5df2\u4f7f\u7528 Node.js \u91cd\u5199\uff0c\u4ee5\u5b9e\u73b0\u6700\u5927\u7684\u517c\u5bb9\u6027\u3002\n\n### \u5305\u7ba1\u7406\u5668\u68c0\u6d4b\n\n\u63d2\u4ef6\u4f1a\u81ea\u52a8\u68c0\u6d4b\u4f60\u504f\u597d\u7684\u5305\u7ba1\u7406\u5668\uff08npm, pnpm, yarn, \u6216 bun\uff09\uff0c\u4f18\u5148\u7ea7\u5982\u4e0b\uff1a\n\n1. **\u73af\u5883\u53d8\u91cf**\uff1a`CLAUDE_PACKAGE_MANAGER`\n2. **\u9879\u76ee\u914d\u7f6e**\uff1a`.claude/package-manager.json`\n3. **package.json**\uff1a`packageManager` \u5b57\u6bb5\n4. **\u9501\u6587\u4ef6**\uff1a\u6839\u636e package-lock.json, yarn.lock, pnpm-lock.yaml \u6216 bun.lockb \u68c0\u6d4b\n5. **\u5168\u5c40\u914d\u7f6e**\uff1a`~/.claude/package-manager.json`\n6. **\u5907\u9009\u9879**\uff1a\u7b2c\u4e00\u4e2a\u53ef\u7528\u7684\u5305\u7ba1\u7406\u5668\n\n\u8bbe\u7f6e\u4f60\u504f\u597d\u7684\u5305\u7ba1\u7406\u5668\uff1a\n\n```bash\n# \u901a\u8fc7\u73af\u5883\u53d8\u91cf\nexport CLAUDE_PACKAGE_MANAGER=pnpm\n\n# \u901a\u8fc7\u5168\u5c40\u914d\u7f6e\nnode scripts/setup-package-manager.js --global pnpm\n\n# \u901a\u8fc7\u9879\u76ee\u914d\u7f6e\nnode scripts/setup-package-manager.js --project bun\n\n# \u68c0\u6d4b\u5f53\u524d\u8bbe\u7f6e\nnode scripts/setup-package-manager.js --detect\n```\n\n\u6216\u8005\u5728 Claude Code \u4e2d\u4f7f\u7528 `/setup-pm` \u547d\u4ee4\u3002\n\n---\n\n## \u5305\u542b\u5185\u5bb9\n\n\u672c\u4ed3\u5e93\u662f\u4e00\u4e2a **Claude Code \u63d2\u4ef6** \u2014\u2014 \u4f60\u53ef\u4ee5\u76f4\u63a5\u5b89\u88c5\u5b83\uff0c\u6216\u8005\u624b\u52a8\u590d\u5236\u7ec4\u4ef6\u3002\n\n```\neverything-claude-code/\n|-- .claude-plugin/ # \u63d2\u4ef6\u4e0e\u5e02\u573a\u6e05\u5355\n| |-- plugin.json # \u63d2\u4ef6\u5143\u6570\u636e\u4e0e\u7ec4\u4ef6\u8def\u5f84\n| |-- marketplace.json # \u7528\u4e8e /plugin marketplace add \u7684\u5e02\u573a\u76ee\u5f55\n|\n|-- agents/ # \u7528\u4e8e\u4efb\u52a1\u59d4\u6d3e\u7684\u4e13\u7528\u5b50\u667a\u80fd\u4f53\uff08Subagents\uff09\n| |-- planner.md # \u529f\u80fd\u5b9e\u73b0\u89c4\u5212\n| |-- architect.md # \u7cfb\u7edf\u8bbe\u8ba1\u51b3\u7b56\n| |-- tdd-guide.md # \u6d4b\u8bd5\u9a71\u52a8\u5f00\u53d1\uff08TDD\uff09\n| |-- code-reviewer.md # \u4ee3\u7801\u8d28\u91cf\u4e0e\u5b89\u5168\u8bc4\u5ba1\n| |-- security-reviewer.md # \u6f0f\u6d1e\u5206\u6790\n| |-- build-error-resolver.md # \u6784\u5efa\u9519\u8bef\u89e3\u51b3\n| |-- e2e-runner.md # Playwright \u7aef\u5230\u7aef\u6d4b\u8bd5\n| |-- refactor-cleaner.md # \u6b7b\u4ee3\u7801\u6e05\u7406\n| |-- doc-updater.md # \u6587\u6863\u540c\u6b65\n| |-- go-reviewer.md # Go \u4ee3\u7801\u8bc4\u5ba1\uff08\u65b0\u589e\uff09\n| |-- go-build-resolver.md # Go \u6784\u5efa\u9519\u8bef\u89e3\u51b3\uff08\u65b0\u589e\uff09\n|\n|-- skills/ # \u5de5\u4f5c\u6d41\uff08Workflow\uff09\u5b9a\u4e49\u4e0e\u9886\u57df\u77e5\u8bc6\n| |-- coding-standards/ # \u8bed\u8a00\u6700\u4f73\u5b9e\u8df5\n| |-- backend-patterns/ # API\u3001\u6570\u636e\u5e93\u3001\u7f13\u5b58\u6a21\u5f0f\n| |-- frontend-patterns/ # React\u3001Next.js \u6a21\u5f0f\n| |-- continuous-learning/ # \u4ece\u4f1a\u8bdd\u4e2d\u81ea\u52a8\u63d0\u53d6\u6a21\u5f0f\uff08\u6df1\u5ea6\u6307\u5357\u5185\u5bb9\uff09\n| |-- continuous-learning-v2/ # \u57fa\u4e8e\u76f4\u89c9\uff08Instinct\uff09\u7684\u5e26\u7f6e\u4fe1\u5ea6\u8bc4\u5206\u5b66\u4e60\u7cfb\u7edf\n| |-- iterative-retrieval/ # \u5b50\u667a\u80fd\u4f53\u7684\u6e10\u8fdb\u5f0f\u4e0a\u4e0b\u6587\u7cbe\u70bc\n| |-- strategic-compact/ # \u624b\u52a8\u538b\u7f29\u5efa\u8bae\uff08\u6df1\u5ea6\u6307\u5357\u5185\u5bb9\uff09\n| |-- tdd-workflow/ # TDD \u65b9\u6cd5\u8bba\n| |-- security-review/ # \u5b89\u5168\u81ea\u67e5\u8868\n| |-- eval-harness/ # \u9a8c\u8bc1\u5faa\u73af\u8bc4\u6d4b\uff08\u6df1\u5ea6\u6307\u5357\u5185\u5bb9\uff09\n| |-- verification-loop/ # \u6301\u7eed\u9a8c\u8bc1\uff08\u6df1\u5ea6\u6307\u5357\u5185\u5bb9\uff09\n| |-- golang-patterns/ # Go \u60ef\u7528\u6cd5\u4e0e\u6700\u4f73\u5b9e\u8df5\uff08\u65b0\u589e\uff09\n| |-- golang-testing/ # Go \u6d4b\u8bd5\u6a21\u5f0f\u3001TDD\u3001\u57fa\u51c6\u6d4b\u8bd5\uff08\u65b0\u589e\uff09\n|\n|-- commands/ # \u7528\u4e8e\u5feb\u901f\u6267\u884c\u7684\u659c\u6760\u547d\u4ee4\uff08Slash Commands\uff09\n| |-- tdd.md # /tdd - \u6d4b\u8bd5\u9a71\u52a8\u5f00\u53d1\n| |-- plan.md # /plan - \u5b9e\u73b0\u89c4\u5212\n| |-- e2e.md # /e2e - \u7aef\u5230\u7aef\u6d4b\u8bd5\u751f\u6210\n| |-- code-review.md # /code-review - \u8d28\u91cf\u8bc4\u5ba1\n| |-- build-fix.md # /build-fix - \u4fee\u590d\u6784\u5efa\u9519\u8bef\n| |-- refactor-clean.md # /refactor-clean - \u79fb\u9664\u6b7b\u4ee3\u7801\n| |-- learn.md # /learn - \u4f1a\u8bdd\u4e2d\u9014\u63d0\u53d6\u6a21\u5f0f\uff08\u6df1\u5ea6\u6307\u5357\u5185\u5bb9\uff09\n| |-- checkpoint.md # /checkpoint - \u4fdd\u5b58\u9a8c\u8bc1\u72b6\u6001\uff08\u6df1\u5ea6\u6307\u5357\u5185\u5bb9\uff09\n| |-- verify.md # /verify - \u8fd0\u884c\u9a8c\u8bc1\u5faa\u73af\uff08\u6df1\u5ea6\u6307\u5357\u5185\u5bb9\uff09\n| |-- setup-pm.md # /setup-pm - \u914d\u7f6e\u5305\u7ba1\u7406\u5668\n| |-- go-review.md # /go-review - Go \u4ee3\u7801\u8bc4\u5ba1\uff08\u65b0\u589e\uff09\n| |-- go-test.md # /go-test - Go TDD \u5de5\u4f5c\u6d41\uff08\u65b0\u589e\uff09\n| |-- go-build.md # /go-build - \u4fee\u590d Go \u6784\u5efa\u9519\u8bef\uff08\u65b0\u589e\uff09\n| |-- skill-create.md # /skill-create - \u4ece git \u5386\u53f2\u751f\u6210\u6280\u80fd\uff08\u65b0\u589e\uff09\n| |-- instinct-status.md # /instinct-status - \u67e5\u770b\u5df2\u5b66\u4e60\u7684\u76f4\u89c9\uff08\u65b0\u589e\uff09\n| |-- instinct-import.md # /instinct-import - \u5bfc\u5165\u76f4\u89c9\uff08\u65b0\u589e\uff09\n| |-- instinct-export.md # /instinct-export - \u5bfc\u51fa\u76f4\u89c9\uff08\u65b0\u589e\uff09\n| |-- evolve.md # /evolve - \u5c06\u76f8\u5173\u76f4\u89c9\u805a\u7c7b\u4e3a\u6280\u80fd\uff08\u65b0\u589e\uff09\n|\n|-- rules/ # \u5fc5\u987b\u9075\u5b88\u7684\u51c6\u5219\uff08\u590d\u5236\u5230 ~/.claude/rules/\uff09\n| |-- security.md # \u5f3a\u5236\u6027\u5b89\u5168\u68c0\u67e5\n| |-- coding-style.md # \u4e0d\u53ef\u53d8\u6027\u3001\u6587\u4ef6\u7ec4\u7ec7\n| |-- testing.md # TDD\u300180% \u8986\u76d6\u7387\u8981\u6c42\n| |-- git-workflow.md # \u63d0\u4ea4\u683c\u5f0f\u3001PR \u6d41\u7a0b\n| |-- agents.md # \u4f55\u65f6\u59d4\u6d3e\u7ed9\u5b50\u667a\u80fd\u4f53\n| |-- performance.md # \u6a21\u578b\u9009\u62e9\u3001\u4e0a\u4e0b\u6587\u7ba1\u7406\n|\n|-- hooks/ # \u57fa\u4e8e\u89e6\u53d1\u5668\u7684\u81ea\u52a8\u5316\n| |-- hooks.json # \u6240\u6709\u94a9\u5b50\u914d\u7f6e\uff08PreToolUse, PostToolUse, Stop \u7b49\uff09\n| |-- memory-persistence/ # \u4f1a\u8bdd\u751f\u547d\u5468\u671f\u94a9\u5b50\uff08\u6df1\u5ea6\u6307\u5357\u5185\u5bb9\uff09\n| |-- strategic-compact/ # \u538b\u7f29\u5efa\u8bae\uff08\u6df1\u5ea6\u6307\u5357\u5185\u5bb9\uff09\n|\n|-- scripts/ # \u8de8\u5e73\u53f0 Node.js \u811a\u672c\uff08\u65b0\u589e\uff09\n| |-- lib/ # \u5171\u4eab\u5b9e\u7528\u7a0b\u5e8f\n| | |-- utils.js # \u8de8\u5e73\u53f0\u6587\u4ef6/\u8def\u5f84/\u7cfb\u7edf\u5de5\u5177\n| | |-- package-manager.js # \u5305\u7ba1\u7406\u5668\u68c0\u6d4b\u4e0e\u9009\u62e9\n| |-- hooks/ # \u94a9\u5b50\u5b9e\u73b0\n| | |-- session-start.js # \u4f1a\u8bdd\u5f00\u59cb\u65f6\u52a0\u8f7d\u4e0a\u4e0b\u6587\n| | |-- session-end.js # \u4f1a\u8bdd\u7ed3\u675f\u65f6\u4fdd\u5b58\u72b6\u6001\n| | |-- pre-compact.js # \u538b\u7f29\u524d\u72b6\u6001\u4fdd\u5b58\n| | |-- suggest-compact.js # \u6218\u7565\u6027\u538b\u7f29\u5efa\u8bae\n| | |-- evaluate-session.js # \u4ece\u4f1a\u8bdd\u4e2d\u63d0\u53d6\u6a21\u5f0f\n| |-- setup-package-manager.js # \u4ea4\u4e92\u5f0f\u5305\u7ba1\u7406\u5668\u8bbe\u7f6e\n|\n|-- tests/ # \u6d4b\u8bd5\u5957\u4ef6\uff08\u65b0\u589e\uff09\n| |-- lib/ # \u5e93\u6d4b\u8bd5\n| |-- hooks/ # \u94a9\u5b50\u6d4b\u8bd5\n| |-- run-all.js # \u8fd0\u884c\u6240\u6709\u6d4b\u8bd5\n|\n|-- contexts/ # \u52a8\u6001\u7cfb\u7edf\u63d0\u793a\u8bcd\u6ce8\u5165\u4e0a\u4e0b\u6587\uff08\u6df1\u5ea6\u6307\u5357\u5185\u5bb9\uff09\n| |-- dev.md # \u5f00\u53d1\u6a21\u5f0f\u4e0a\u4e0b\u6587\n| |-- review.md # \u4ee3\u7801\u8bc4\u5ba1\u6a21\u5f0f\u4e0a\u4e0b\u6587\n| |-- research.md # \u7814\u7a76/\u63a2\u7d22\u6a21\u5f0f\u4e0a\u4e0b\u6587\n|\n|-- examples/ # \u914d\u7f6e\u4e0e\u4f1a\u8bdd\u793a\u4f8b\n| |-- CLAUDE.md # \u9879\u76ee\u7ea7\u914d\u7f6e\u793a\u4f8b\n| |-- user-CLAUDE.md # \u7528\u6237\u7ea7\u914d\u7f6e\u793a\u4f8b\n|\n|-- mcp-configs/ # MCP \u670d\u52a1\u914d\u7f6e\n| |-- mcp-servers.json # GitHub, Supabase, Vercel, Railway \u7b49\n|\n|-- marketplace.json # \u81ea\u6258\u7ba1\u5e02\u573a\u914d\u7f6e\uff08\u7528\u4e8e /plugin marketplace add\uff09\n```\n\n---\n\n## \u751f\u6001\u5de5\u5177\n\n### \u6280\u80fd\u521b\u5efa\u5668\uff08Skill Creator\uff09\n\n\u6709\u4e24\u79cd\u65b9\u6cd5\u53ef\u4ee5\u4ece\u4f60\u7684\u4ed3\u5e93\u751f\u6210 Claude Code \u6280\u80fd\uff1a\n\n#### \u65b9\u6848 A\uff1a\u672c\u5730\u5206\u6790\uff08\u5185\u7f6e\uff09\n\n\u4f7f\u7528 `/skill-create` \u547d\u4ee4\u8fdb\u884c\u672c\u5730\u5206\u6790\uff0c\u65e0\u9700\u5916\u90e8\u670d\u52a1\uff1a\n\n```bash\n/skill-create # \u5206\u6790\u5f53\u524d\u4ed3\u5e93\n/skill-create --instincts # \u540c\u65f6\u4e3a\u6301\u7eed\u5b66\u4e60\uff08continuous-learning\uff09\u751f\u6210\u76f4\u89c9\uff08instincts\uff09\n```\n\n\u8be5\u547d\u4ee4\u4f1a\u5728\u672c\u5730\u5206\u6790\u4f60\u7684 git \u5386\u53f2\u5e76\u751f\u6210 SKILL.md \u6587\u4ef6\u3002\n\n#### \u65b9\u6848 B\uff1aGitHub App\uff08\u9ad8\u7ea7\uff09\n\n\u9002\u7528\u4e8e\u9ad8\u7ea7\u529f\u80fd\uff081\u4e07+ commit\u3001\u81ea\u52a8 PR\u3001\u56e2\u961f\u5171\u4eab\uff09\uff1a\n\n[\u5b89\u88c5 GitHub App](https://github.com/apps/skill-creator) | [ecc.tools](https://ecc.tools)\n\n```bash\n# \u5728\u4efb\u4f55 issue \u4e0b\u7559\u8a00\uff1a\n/skill-creator analyze\n\n# \u6216\u8005\u5728 push \u5230\u9ed8\u8ba4\u5206\u652f\u65f6\u81ea\u52a8\u89e6\u53d1\n```\n\n\u4e24\u79cd\u65b9\u6848\u90fd\u4f1a\u521b\u5efa\uff1a\n- **SKILL.md \u6587\u4ef6** - \u53ef\u76f4\u63a5\u7528\u4e8e Claude Code \u7684\u6280\u80fd\n- **\u76f4\u89c9\u96c6\u5408\uff08Instinct collections\uff09** - \u7528\u4e8e continuous-learning-v2\n- **\u6a21\u5f0f\u63d0\u53d6\uff08Pattern extraction\uff09** - \u4ece\u4f60\u7684\u63d0\u4ea4\u5386\u53f2\u4e2d\u5b66\u4e60\n\n### \u6301\u7eed\u5b66\u4e60\uff08Continuous Learning\uff09v2\n\n\u57fa\u4e8e\u76f4\u89c9\uff08instinct\uff09\u7684\u5b66\u4e60\u7cfb\u7edf\u4f1a\u81ea\u52a8\u5b66\u4e60\u4f60\u7684\u5f00\u53d1\u6a21\u5f0f\uff1a\n\n```bash\n/instinct-status # \u663e\u793a\u5e26\u6709\u7f6e\u4fe1\u5ea6\u7684\u5df2\u5b66\u4e60\u76f4\u89c9\n/instinct-import # \u5bfc\u5165\u4ed6\u4eba\u7684\u76f4\u89c9\n/instinct-export # \u5bfc\u51fa\u4f60\u7684\u76f4\u89c9\u4ee5\u4fbf\u5206\u4eab\n/evolve # \u5c06\u76f8\u5173\u7684\u76f4\u89c9\u805a\u7c7b\u4e3a\u6280\u80fd\uff08skills\uff09\n```\n\n\u8be6\u89c1 `skills/continuous-learning-v2/` \u7684\u5b8c\u6574\u6587\u6863\u3002\n\n---\n\n## \u8981\u6c42\n\n### Claude Code CLI \u7248\u672c\n\n**\u6700\u4f4e\u7248\u672c\uff1av2.1.0 \u6216\u66f4\u9ad8**\n\n\u7531\u4e8e\u63d2\u4ef6\u7cfb\u7edf\u5904\u7406\u94a9\u5b50\uff08hooks\uff09\u65b9\u5f0f\u7684\u53d8\u66f4\uff0c\u672c\u63d2\u4ef6\u8981\u6c42 Claude Code CLI v2.1.0+\u3002\n\n\u68c0\u67e5\u4f60\u7684\u7248\u672c\uff1a\n```bash\nclaude --version\n```\n\n### \u91cd\u8981\uff1a\u94a9\u5b50\uff08Hooks\uff09\u81ea\u52a8\u52a0\u8f7d\u884c\u4e3a\n\n> \u26a0\ufe0f **\u5bf9\u8d21\u732e\u8005\u7684\u63d0\u9192\uff1a** \u8bf7\u52ff\u5728 `.claude-plugin/plugin.json` \u4e2d\u6dfb\u52a0 `\"hooks\"` \u5b57\u6bb5\u3002\u8fd9\u662f\u7531\u56de\u5f52\u6d4b\u8bd5\u5f3a\u5236\u6267\u884c\u7684\u3002\n\nClaude Code v2.1+ \u4f1a**\u81ea\u52a8\u52a0\u8f7d**\u5df2\u5b89\u88c5\u63d2\u4ef6\u4e2d\u7ea6\u5b9a\u7684 `hooks/hooks.json`\u3002\u5728 `plugin.json` \u4e2d\u663e\u5f0f\u58f0\u660e\u4f1a\u5bfc\u81f4\u91cd\u590d\u68c0\u6d4b\u9519\u8bef\uff1a\n\n```\nDuplicate hooks file detected: ./hooks/hooks.json resolves to already-loaded file\n```\n\n**\u5386\u53f2\u80cc\u666f\uff1a** \u6b64\u95ee\u9898\u5728\u672c\u4ed3\u5e93\u4e2d\u66fe\u591a\u6b21\u51fa\u73b0\u4fee\u590d/\u56de\u9000\u5faa\u73af\uff08[#29](https://github.com/affaan-m/everything-claude-code/issues/29), [#52](https://github.com/affaan-m/everything-claude-code/issues/52), [#103](https://github.com/affaan-m/everything-claude-code/issues/103)\uff09\u3002Claude Code \u7248\u672c\u95f4\u7684\u884c\u4e3a\u5dee\u5f02\u5bfc\u81f4\u4e86\u6df7\u6dc6\u3002\u6211\u4eec\u73b0\u5728\u5df2\u52a0\u5165\u56de\u5f52\u6d4b\u8bd5\u4ee5\u9632\u6b62\u6b64\u7c7b\u95ee\u9898\u518d\u6b21\u53d1\u751f\u3002\n\n---\n\n## \u5b89\u88c5\n\n### \u65b9\u6848 1\uff1a\u4f5c\u4e3a\u63d2\u4ef6\u5b89\u88c5\uff08\u63a8\u8350\uff09\n\n\u8fd9\u662f\u4f7f\u7528\u672c\u4ed3\u5e93\u6700\u7b80\u5355\u7684\u65b9\u6cd5 \u2014\u2014 \u4f5c\u4e3a Claude Code \u63d2\u4ef6\u5b89\u88c5\uff1a\n\n```bash\n# \u5c06\u6b64\u4ed3\u5e93\u6dfb\u52a0\u4e3a\u5e02\u573a\uff08marketplace\uff09\n/plugin marketplace add affaan-m/everything-claude-code\n\n# \u5b89\u88c5\u63d2\u4ef6\n/plugin install everything-claude-code@everything-claude-code\n```\n\n\u6216\u8005\u76f4\u63a5\u6dfb\u52a0\u5230\u4f60\u7684 `~/.claude/settings.json`\uff1a\n\n```json\n{\n \"extraKnownMarketplaces\": {\n \"everything-claude-code\": {\n \"source\": {\n \"source\": \"github\",\n \"repo\": \"affaan-m/everything-claude-code\"\n }\n }\n },\n \"enabledPlugins\": {\n \"everything-claude-code@everything-claude-code\": true\n }\n}\n```\n\n\u5b89\u88c5\u540e\u5373\u53ef\u7acb\u5373\u4f7f\u7528\u6240\u6709\u547d\u4ee4\uff08commands\uff09\u3001\u667a\u80fd\u4f53\uff08agents\uff09\u3001\u6280\u80fd\uff08skills\uff09\u548c\u94a9\u5b50\uff08hooks\uff09\u3002\n\n> **\u6ce8\u610f\uff1a** Claude Code \u63d2\u4ef6\u7cfb\u7edf\u76ee\u524d\u4e0d\u652f\u6301\u901a\u8fc7\u63d2\u4ef6\u5206\u53d1\u89c4\u5219\uff08`rules`\uff09\uff08\u8fd9\u662f [\u4e0a\u6e38\u9650\u5236](https://code.claude.com/docs/en/plugins-reference)\uff09\u3002\u4f60\u9700\u8981\u624b\u52a8\u5b89\u88c5\u89c4\u5219\uff1a\n>\n> ```bash\n> # \u9996\u5148\u514b\u9686\u4ed3\u5e93\n> git clone https://github.com/affaan-m/everything-claude-code.git\n>\n> # \u65b9\u6848 A\uff1a\u7528\u6237\u7ea7\u89c4\u5219\uff08\u5e94\u7528\u4e8e\u6240\u6709\u9879\u76ee\uff09\n> cp -r everything-claude-code/rules/* ~/.claude/rules/\n>\n> # \u65b9\u6848 B\uff1a\u9879\u76ee\u7ea7\u89c4\u5219\uff08\u4ec5\u5e94\u7528\u4e8e\u5f53\u524d\u9879\u76ee\uff09\n> mkdir -p .claude/rules\n> cp -r everything-claude-code/rules/* .claude/rules/\n> ```\n\n---\n\n### \u65b9\u6848 2\uff1a\u624b\u52a8\u5b89\u88c5\n\n\u5982\u679c\u4f60\u66f4\u503e\u5411\u4e8e\u624b\u52a8\u63a7\u5236\u5b89\u88c5\u7684\u5185\u5bb9\uff1a\n\n```bash\n# \u514b\u9686\u4ed3\u5e93\ngit clone https://github.com/affaan-m/everything-claude-code.git\n\n# \u590d\u5236\u667a\u80fd\u4f53\uff08agents\uff09\u5230\u4f60\u7684 Claude \u914d\u7f6e\u76ee\u5f55\ncp everything-claude-code/agents/*.md ~/.claude/agents/\n\n# \u590d\u5236\u89c4\u5219\uff08rules\uff09\ncp everything-claude-code/rules/*.md ~/.claude/rules/\n\n# \u590d\u5236\u547d\u4ee4\uff08commands\uff09\ncp everything-claude-code/commands/*.md ~/.claude/commands/\n\n# \u590d\u5236\u6280\u80fd\uff08skills\uff09\ncp -r everything-claude-code/skills/* ~/.claude/skills/\n```\n\n#### \u5c06\u94a9\u5b50\uff08hooks\uff09\u6dfb\u52a0\u5230 settings.json\n\n\u5c06 `hooks/hooks.json` \u4e2d\u7684\u94a9\u5b50\u914d\u7f6e\u590d\u5236\u5230\u4f60\u7684 `~/.claude/settings.json` \u4e2d\u3002\n\n#### \u914d\u7f6e MCPs\n\n\u5c06 `mcp-configs/mcp-servers.json` \u4e2d\u4f60\u9700\u8981\u7684 MCP \u670d\u52a1\u914d\u7f6e\u590d\u5236\u5230\u4f60\u7684 `~/.claude.json`\u3002\n\n**\u91cd\u8981\uff1a** \u8bf7\u5c06 `YOUR_*_HERE` \u5360\u4f4d\u7b26\u66ff\u6362\u4e3a\u4f60\u771f\u5b9e\u7684 API \u5bc6\u94a5\u3002\n\n---\n\n## \u6838\u5fc3\u6982\u5ff5\n\n### \u667a\u80fd\u4f53\uff08Agents\uff09\n\n\u5b50\u667a\u80fd\u4f53\uff08Subagents\uff09\u8d1f\u8d23\u5904\u7406\u53d7\u9650\u8303\u56f4\u5185\u7684\u59d4\u6d3e\u4efb\u52a1\u3002\u793a\u4f8b\uff1a\n\n```markdown\n---\nname: code-reviewer\ndescription: \u8bc4\u5ba1\u4ee3\u7801\u8d28\u91cf\u3001\u5b89\u5168\u6027\u4e0e\u53ef\u7ef4\u62a4\u6027\ntools: [\"Read\", \"Grep\", \"Glob\", \"Bash\"]\nmodel: opus\n---\n\n\u4f60\u662f\u4e00\u540d\u8d44\u6df1\u4ee3\u7801\u8bc4\u5ba1\u5458...\n```\n\n### \u6280\u80fd\uff08Skills\uff09\n\n\u6280\u80fd\uff08Skills\uff09\u662f\u53ef\u7531\u547d\u4ee4\u6216\u667a\u80fd\u4f53\u8c03\u7528\u7684\u5de5\u4f5c\u6d41\u5b9a\u4e49\uff1a\n\n```markdown\n# TDD \u5de5\u4f5c\u6d41\n\n1. \u9996\u5148\u5b9a\u4e49\u63a5\u53e3\n2. \u7f16\u5199\u5931\u8d25\u7684\u6d4b\u8bd5\uff08RED\uff09\n3. \u5b9e\u73b0\u6700\u7b80\u4ee3\u7801\uff08GREEN\uff09\n4. \u91cd\u6784\uff08IMPROVE\uff09\n5. \u9a8c\u8bc1\u8986\u76d6\u7387\u662f\u5426\u8fbe\u5230 80% \u4ee5\u4e0a\n```\n\n### \u94a9\u5b50\uff08Hooks\uff09\n\n\u94a9\u5b50\uff08Hooks\uff09\u5728\u5de5\u5177\u4e8b\u4ef6\u53d1\u751f\u65f6\u89e6\u53d1\u3002\u793a\u4f8b \u2014\u2014 \u9488\u5bf9 console.log \u53d1\u51fa\u8b66\u544a\uff1a\n\n```json\n{\n \"matcher\": \"tool == \\\"Edit\\\" && tool_input.file_path matches \\\"\\\\.(ts|tsx|js|jsx)$\\\"\",\n \"hooks\": [{\n \"type\": \"command\",\n \"command\": \"#!/bin/bash\\ngrep -n 'console\\\\.log' \\\"$file_path\\\" && echo '[Hook] Remove console.log' >&2\"\n }]\n}\n```\n\n### \u89c4\u5219\uff08Rules\uff09\n\n\u89c4\u5219\uff08Rules\uff09\u662f\u5fc5\u987b\u59cb\u7ec8\u9075\u5b88\u7684\u51c6\u5219\u3002\u8bf7\u4fdd\u6301\u5b83\u4eec\u7684\u6a21\u5757\u5316\uff1a\n\n```\n~/.claude/rules/\n security.md # \u7981\u6b62\u786c\u7f16\u7801\u5bc6\u94a5\n coding-style.md # \u4e0d\u53ef\u53d8\u6027\u3001\u6587\u4ef6\u9650\u5236\n testing.md # TDD\u3001\u8986\u76d6\u7387\u8981\u6c42\n```\n\n---\n\n## \u8fd0\u884c\u6d4b\u8bd5\n\n\u672c\u63d2\u4ef6\u5305\u542b\u4e00\u4e2a\u5168\u9762\u7684\u6d4b\u8bd5\u5957\u4ef6\uff1a\n\n```bash\n# \u8fd0\u884c\u6240\u6709\u6d4b\u8bd5\nnode tests/run-all.js\n\n# \u8fd0\u884c\u5355\u4e2a\u6d4b\u8bd5\u6587\u4ef6\nnode tests/lib/utils.test.js\nnode tests/lib/package-manager.test.js\nnode tests/hooks/hooks.test.js\n```\n\n---\n\n## \u8d21\u732e\n\n**\u975e\u5e38\u6b22\u8fce\u5e76\u9f13\u52b1\u8d21\u732e\u3002**\n\n\u672c\u4ed3\u5e93\u65e8\u5728\u6210\u4e3a\u4e00\u4e2a\u793e\u533a\u8d44\u6e90\u3002\u5982\u679c\u4f60\u6709\uff1a\n- \u6709\u7528\u7684\u667a\u80fd\u4f53\u6216\u6280\u80fd\n- \u5de7\u5999\u7684\u94a9\u5b50\n- \u66f4\u597d\u7684 MCP \u914d\u7f6e\n- \u6539\u8fdb\u540e\u7684\u89c4\u5219\n\n\u8bf7\u63d0\u4ea4\u4f60\u7684\u8d21\u732e\uff01\u53c2\u8003 [CONTRIBUTING.md](CONTRIBUTING.md) \u4e86\u89e3\u6307\u5357\u3002\n\n### \u8d21\u732e\u601d\u8def\n\n- \u8bed\u8a00\u4e13\u7528\u6280\u80fd\uff08Python, Rust \u6a21\u5f0f\uff09\u2014\u2014 \u5df2\u5305\u542b Go\uff01\n- \u6846\u67b6\u4e13\u7528\u914d\u7f6e\uff08Django, Rails, Laravel\uff09\n- DevOps \u667a\u80fd\u4f53\uff08Kubernetes, Terraform, AWS\uff09\n- \u6d4b\u8bd5\u7b56\u7565\uff08\u4e0d\u540c\u6846\u67b6\uff09\n- \u9886\u57df\u7279\u5b9a\u77e5\u8bc6\uff08\u673a\u5668\u5b66\u4e60\u3001\u6570\u636e\u5de5\u7a0b\u3001\u79fb\u52a8\u5f00\u53d1\uff09\n\n---\n\n## \u80cc\u666f\n\n\u6211\u4ece Claude Code \u5b9e\u9a8c\u9636\u6bb5\u5c31\u5f00\u59cb\u4f7f\u7528\u4e86\u3002\u5728 2025 \u5e74 9 \u6708\u7684 Anthropic x Forum Ventures \u9ed1\u5ba2\u677e\u4e2d\uff0c\u6211\u4e0e [@DRodriguezFX](https://x.com/DRodriguezFX) \u5408\u4f5c\u5f00\u53d1\u4e86 [zenith.chat](https://zenith.chat)\uff0c\u5e76\u83b7\u5f97\u4e86\u51a0\u519b \u2014\u2014 \u8be5\u9879\u76ee\u5b8c\u5168\u4f7f\u7528 Claude Code \u6784\u5efa\u3002\n\n\u8fd9\u4e9b\u914d\u7f6e\u5728\u591a\u4e2a\u751f\u4ea7\u7ea7\u5e94\u7528\u4e2d\u7ecf\u8fc7\u4e86\u5b9e\u6218\u68c0\u9a8c\u3002\n\n---\n\n## \u91cd\u8981\u63d0\u793a\n\n### \u4e0a\u4e0b\u6587\u7a97\u53e3\u7ba1\u7406\n\n**\u81f3\u5173\u91cd\u8981\uff1a** \u4e0d\u8981\u4e00\u6b21\u6027\u542f\u7528\u6240\u6709 MCP\u3002\u5982\u679c\u542f\u7528\u7684\u5de5\u5177\u8fc7\u591a\uff0c\u4f60\u7684 200k \u4e0a\u4e0b\u6587\u7a97\u53e3\u53ef\u80fd\u4f1a\u7f29\u51cf\u5230 70k\u3002\n\n\u7ecf\u9a8c\u6cd5\u5219\uff1a\n- \u914d\u7f6e 20-30 \u4e2a MCP\n- \u6bcf\u4e2a\u9879\u76ee\u4fdd\u6301\u542f\u7528 10 \u4e2a\u4ee5\u5185\n- \u6d3b\u8dc3\u5de5\u5177\u603b\u6570\u63a7\u5236\u5728 80 \u4e2a\u4ee5\u5185\n\n\u5728\u9879\u76ee\u914d\u7f6e\u4e2d\u4f7f\u7528 `disabledMcpServers` \u6765\u7981\u7528\u4e0d\u9700\u8981\u7684\u670d\u52a1\u3002\n\n### \u81ea\u5b9a\u4e49\n\n\u8fd9\u4e9b\u914d\u7f6e\u9002\u7528\u4e8e\u6211\u7684\u5de5\u4f5c\u6d41\u3002\u4f60\u5e94\u8be5\uff1a\n1. \u4ece\u4f60\u4ea7\u751f\u5171\u9e23\u7684\u5185\u5bb9\u5f00\u59cb\n2. \u6839\u636e\u4f60\u7684\u6280\u672f\u6808\u8fdb\u884c\u4fee\u6539\n3. \u79fb\u9664\u4f60\u4e0d\u9700\u8981\u7684\u5185\u5bb9\n4. \u52a0\u5165\u4f60\u81ea\u5df1\u7684\u6a21\u5f0f\n\n---\n\n## Star \u5386\u53f2\n\n[![Star History Chart](https://api.star-history.com/svg?repos=affaan-m/everything-claude-code&type=Date)](https://star-history.com/#affaan-m/everything-claude-code&Date)\n\n---\n\n## \u76f8\u5173\u94fe\u63a5\n\n- **\u7b80\u660e\u6307\u5357\uff08\u5165\u95e8\u5fc5\u8bfb\uff09\uff1a** [Everything Claude Code \u7b80\u660e\u6307\u5357](https://x.com/affaanmustafa/status/2012378465664745795)\n- **\u6df1\u5ea6\u6307\u5357\uff08\u8fdb\u9636\u53c2\u8003\uff09\uff1a** [Everything Claude Code \u6df1\u5ea6\u6307\u5357](https://x.com/affaanmustafa/status/2014040193557471352)\n- **\u5173\u6ce8\u6211\uff1a** [@affaanmustafa](https://x.com/affaanmustafa)\n- **zenith.chat\uff1a** [zenith.chat](https://zenith.chat)\n\n---\n\n## \u8bb8\u53ef\u8bc1\n\nMIT - \u81ea\u7531\u4f7f\u7528\uff0c\u6309\u9700\u4fee\u6539\uff0c\u5982\u679c\u53ef\u4ee5\u8bf7\u56de\u9988\u793e\u533a\u3002\n\n---\n\n**\u5982\u679c\u5bf9\u4f60\u6709\u5e2e\u52a9\uff0c\u8bf7\u7ed9\u672c\u4ed3\u5e93\u70b9\u4e2a Star\u3002\u9605\u8bfb\u4e24\u4efd\u6307\u5357\u3002\u6784\u5efa\u4f1f\u5927\u7684\u4ea7\u54c1\u3002**\n" + "md5": "fb40e7edeec881dd62c82b13036b5ead", + "content": "**\u8bed\u8a00\uff1a** [English](README.md) | **\u7b80\u4f53\u4e2d\u6587** | [\u7e41\u9ad4\u4e2d\u6587](docs/zh-TW/README.md)\n\n# Everything Claude Code\n\n[![Stars](https://img.shields.io/github/stars/affaan-m/everything-claude-code?style=flat)](https://github.com/affaan-m/everything-claude-code/stargazers)\n[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n![Shell](https://img.shields.io/badge/-Shell-4EAA25?logo=gnu-bash&logoColor=white)\n![TypeScript](https://img.shields.io/badge/-TypeScript-3178C6?logo=typescript&logoColor=white)\n![Go](https://img.shields.io/badge/-Go-00ADD8?logo=go&logoColor=white)\n![Markdown](https://img.shields.io/badge/-Markdown-000000?logo=markdown&logoColor=white)\n\n---\n\n
\n\n**\ud83c\udf10 Language / \u8bed\u8a00 / \u8a9e\u8a00**\n\n[**English**](README.md) | [\u7b80\u4f53\u4e2d\u6587](README.zh-CN.md) | [\u7e41\u9ad4\u4e2d\u6587](docs/zh-TW/README.md)\n\n
\n\n---\n\n**\u6765\u81ea Anthropic \u9ed1\u5ba2\u677e\u83b7\u80dc\u8005\u7684 Claude Code \u914d\u7f6e\u5168\u96c6\u3002**\n\n\u5305\u542b\u751f\u4ea7\u7ea7\u7684\u667a\u80fd\u4f53\uff08Agents\uff09\u3001\u6280\u80fd\uff08Skills\uff09\u3001\u94a9\u5b50\uff08Hooks\uff09\u3001\u547d\u4ee4\uff08Commands\uff09\u3001\u89c4\u5219\uff08Rules\uff09\u4ee5\u53ca MCP \u914d\u7f6e\u3002\u8fd9\u4e9b\u914d\u7f6e\u6e90\u81ea 10 \u4e2a\u591a\u6708\u5728\u6784\u5efa\u771f\u5b9e\u4ea7\u54c1\u8fc7\u7a0b\u4e2d\u7684\u9ad8\u5f3a\u5ea6\u65e5\u5e38\u4f7f\u7528\u4e0e\u6f14\u8fdb\u3002\n\n---\n\n## \u6307\u5357\u6587\u6863\n\n\u672c\u4ed3\u5e93\u4ec5\u5305\u542b\u539f\u59cb\u4ee3\u7801\u3002\u4ee5\u4e0b\u6307\u5357\u5c06\u8be6\u7ec6\u89e3\u91ca\u4e00\u5207\uff1a\n\n\n\n\n\n\n\n\n\n\n
\n\n\"Everything\n\n\n\n\"Everything\n\n
\u7b80\u660e\u6307\u5357 (Shorthand Guide)
\u5b89\u88c5\u3001\u57fa\u7840\u3001\u54f2\u5b66\u3002\u8bf7\u5148\u9605\u8bfb\u6b64\u7bc7\u3002
\u6df1\u5ea6\u6307\u5357 (Longform Guide)
Token \u4f18\u5316\u3001\u8bb0\u5fc6\u6301\u4e45\u5316\u3001\u8bc4\u6d4b\uff08Evals\uff09\u3001\u5e76\u884c\u5316\u3002
\n\n| \u4e3b\u9898 | \u4f60\u5c06\u5b66\u5230 |\n|-------|-------------------|\n| Token \u4f18\u5316 | \u6a21\u578b\u9009\u62e9\u3001\u7cfb\u7edf\u63d0\u793a\u8bcd\u7cbe\u7b80\u3001\u540e\u53f0\u8fdb\u7a0b |\n| \u8bb0\u5fc6\u6301\u4e45\u5316 | \u8de8\u4f1a\u8bdd\u81ea\u52a8\u4fdd\u5b58/\u52a0\u8f7d\u4e0a\u4e0b\u6587\u7684\u94a9\u5b50\uff08Hooks\uff09 |\n| \u6301\u7eed\u5b66\u4e60 | \u4ece\u4f1a\u8bdd\u4e2d\u81ea\u52a8\u63d0\u53d6\u6a21\u5f0f\u5e76\u8f6c\u5316\u4e3a\u53ef\u91cd\u7528\u7684\u6280\u80fd\uff08Skills\uff09 |\n| \u9a8c\u8bc1\u5faa\u73af | \u68c0\u67e5\u70b9\uff08Checkpoint\uff09vs \u6301\u7eed\u8bc4\u6d4b\u3001\u8bc4\u5206\u5668\u7c7b\u578b\u3001pass@k \u6307\u6807 |\n| \u5e76\u884c\u5316 | Git worktrees\u3001\u7ea7\u8054\u65b9\u6cd5\u3001\u4f55\u65f6\u6269\u5c55\u5b9e\u4f8b |\n| \u5b50\u667a\u80fd\u4f53\u7f16\u6392 | \u4e0a\u4e0b\u6587\u95ee\u9898\u3001\u8fed\u4ee3\u68c0\u7d22\u6a21\u5f0f |\n\n---\n\n## \ud83d\ude80 \u5feb\u901f\u5f00\u59cb\n\n\u4e0d\u5230 2 \u5206\u949f\u5373\u53ef\u5b8c\u6210\u914d\u7f6e\uff1a\n\n### \u7b2c\u4e00\u6b65\uff1a\u5b89\u88c5\u63d2\u4ef6\n\n```bash\n# \u6dfb\u52a0\u5e02\u573a\n/plugin marketplace add affaan-m/everything-claude-code\n\n# \u5b89\u88c5\u63d2\u4ef6\n/plugin install everything-claude-code@everything-claude-code\n```\n\n### \u7b2c\u4e8c\u6b65\uff1a\u5b89\u88c5\u89c4\u5219\uff08\u5fc5\u9009\uff09\n\n> \u26a0\ufe0f **\u91cd\u8981\u63d0\u793a\uff1a** Claude Code \u63d2\u4ef6\u65e0\u6cd5\u81ea\u52a8\u5206\u53d1 `rules`\u3002\u8bf7\u624b\u52a8\u5b89\u88c5\uff1a\n\n```bash\n# \u9996\u5148\u514b\u9686\u4ed3\u5e93\ngit clone https://github.com/affaan-m/everything-claude-code.git\n\n# \u590d\u5236\u89c4\u5219\uff08\u9002\u7528\u4e8e\u6240\u6709\u9879\u76ee\uff09\ncp -r everything-claude-code/rules/* ~/.claude/rules/\n```\n\n### \u7b2c\u4e09\u6b65\uff1a\u5f00\u59cb\u4f7f\u7528\n\n```bash\n# \u5c1d\u8bd5\u4e00\u4e2a\u547d\u4ee4\n/plan \"Add user authentication\"\n\n# \u67e5\u770b\u53ef\u7528\u547d\u4ee4\n/plugin list everything-claude-code@everything-claude-code\n```\n\n\u2728 **\u5927\u529f\u544a\u6210\uff01** \u4f60\u73b0\u5728\u53ef\u4ee5\u4f7f\u7528 15+ \u4e2a\u667a\u80fd\u4f53\u300130+ \u4e2a\u6280\u80fd\u548c 20+ \u4e2a\u547d\u4ee4\u4e86\u3002\n\n---\n\n## \ud83c\udf10 \u8de8\u5e73\u53f0\u652f\u6301\n\n\u8be5\u63d2\u4ef6\u73b0\u5df2\u5168\u9762\u652f\u6301 **Windows\u3001macOS \u548c Linux**\u3002\u6240\u6709\u94a9\u5b50\u548c\u811a\u672c\u5747\u5df2\u4f7f\u7528 Node.js \u91cd\u5199\uff0c\u4ee5\u786e\u4fdd\u6700\u5927\u517c\u5bb9\u6027\u3002\n\n### \u5305\u7ba1\u7406\u5668\u68c0\u6d4b\n\n\u63d2\u4ef6\u4f1a\u81ea\u52a8\u68c0\u6d4b\u4f60\u504f\u597d\u7684\u5305\u7ba1\u7406\u5668\uff08npm, pnpm, yarn, \u6216 bun\uff09\uff0c\u4f18\u5148\u7ea7\u5982\u4e0b\uff1a\n\n1. **\u73af\u5883\u53d8\u91cf**\uff1a`CLAUDE_PACKAGE_MANAGER`\n2. **\u9879\u76ee\u914d\u7f6e**\uff1a`.claude/package-manager.json`\n3. **package.json**\uff1a`packageManager` \u5b57\u6bb5\n4. **\u9501\u6587\u4ef6**\uff1a\u6839\u636e package-lock.json, yarn.lock, pnpm-lock.yaml, \u6216 bun.lockb \u68c0\u6d4b\n5. **\u5168\u5c40\u914d\u7f6e**\uff1a`~/.claude/package-manager.json`\n6. **\u515c\u5e95\u65b9\u6848**\uff1a\u7b2c\u4e00\u4e2a\u53ef\u7528\u7684\u5305\u7ba1\u7406\u5668\n\n\u8bbe\u7f6e\u4f60\u504f\u597d\u7684\u5305\u7ba1\u7406\u5668\uff1a\n\n```bash\n# \u901a\u8fc7\u73af\u5883\u53d8\u91cf\nexport CLAUDE_PACKAGE_MANAGER=pnpm\n\n# \u901a\u8fc7\u5168\u5c40\u914d\u7f6e\nnode scripts/setup-package-manager.js --global pnpm\n\n# \u901a\u8fc7\u9879\u76ee\u914d\u7f6e\nnode scripts/setup-package-manager.js --project bun\n\n# \u68c0\u6d4b\u5f53\u524d\u8bbe\u7f6e\nnode scripts/setup-package-manager.js --detect\n```\n\n\u6216\u8005\u5728 Claude Code \u4e2d\u4f7f\u7528 `/setup-pm` \u547d\u4ee4\u3002\n\n---\n\n## \ud83d\udce6 \u5185\u5bb9\u6e05\u5355\n\n\u672c\u4ed3\u5e93\u662f\u4e00\u4e2a **Claude Code \u63d2\u4ef6** \u2014\u2014 \u4f60\u53ef\u4ee5\u76f4\u63a5\u5b89\u88c5\uff0c\u4e5f\u53ef\u4ee5\u624b\u52a8\u590d\u5236\u7ec4\u4ef6\u3002\n\n```\neverything-claude-code/\n|-- .claude-plugin/ # \u63d2\u4ef6\u548c\u5e02\u573a\u6e05\u5355\u6587\u4ef6\n| |-- plugin.json # \u63d2\u4ef6\u5143\u6570\u636e\u548c\u7ec4\u4ef6\u8def\u5f84\n| |-- marketplace.json # \u7528\u4e8e /plugin marketplace add \u7684\u5e02\u573a\u76ee\u5f55\n|\n|-- agents/ # \u7528\u4e8e\u4efb\u52a1\u59d4\u6d3e\u7684\u4e13\u4e1a\u5316\u5b50\u667a\u80fd\u4f53\uff08Subagents\uff09\n| |-- planner.md # \u529f\u80fd\u5b9e\u73b0\u89c4\u5212\n| |-- architect.md # \u7cfb\u7edf\u8bbe\u8ba1\u51b3\u7b56\n| |-- tdd-guide.md # \u6d4b\u8bd5\u9a71\u52a8\u5f00\u53d1\uff08TDD\uff09\n| |-- code-reviewer.md # \u8d28\u91cf\u4e0e\u5b89\u5168\u5ba1\u67e5\n| |-- security-reviewer.md # \u6f0f\u6d1e\u5206\u6790\n| |-- build-error-resolver.md # \u6784\u5efa\u9519\u8bef\u4fee\u590d\n| |-- e2e-runner.md # Playwright \u7aef\u5230\u7aef\uff08E2E\uff09\u6d4b\u8bd5\n| |-- refactor-cleaner.md # \u6b7b\u4ee3\u7801\u6e05\u7406\n| |-- doc-updater.md # \u6587\u6863\u540c\u6b65\n| |-- go-reviewer.md # Go \u4ee3\u7801\u5ba1\u67e5\uff08\u65b0\u589e\uff09\n| |-- go-build-resolver.md # Go \u6784\u5efa\u9519\u8bef\u4fee\u590d\uff08\u65b0\u589e\uff09\n|\n|-- skills/ # \u5de5\u4f5c\u6d41\u5b9a\u4e49\u4e0e\u9886\u57df\u77e5\u8bc6\n| |-- coding-standards/ # \u8bed\u8a00\u6700\u4f73\u5b9e\u8df5\n| |-- backend-patterns/ # API\u3001\u6570\u636e\u5e93\u3001\u7f13\u5b58\u6a21\u5f0f\n| |-- frontend-patterns/ # React\u3001Next.js \u6a21\u5f0f\n| |-- continuous-learning/ # \u4ece\u4f1a\u8bdd\u4e2d\u81ea\u52a8\u63d0\u53d6\u6a21\u5f0f\uff08\u6df1\u5ea6\u6307\u5357\uff09\n| |-- continuous-learning-v2/ # \u57fa\u4e8e\u201c\u76f4\u89c9\uff08Instinct\uff09\u201d\u7684\u5b66\u4e60\uff0c\u5e26\u6709\u7f6e\u4fe1\u5ea6\u8bc4\u5206\n| |-- iterative-retrieval/ # \u4e3a\u5b50\u667a\u80fd\u4f53\u63d0\u4f9b\u6e10\u8fdb\u5f0f\u4e0a\u4e0b\u6587\u7cbe\u70bc\n| |-- strategic-compact/ # \u624b\u52a8\u538b\u7f29\u5efa\u8bae\uff08\u6df1\u5ea6\u6307\u5357\uff09\n| |-- tdd-workflow/ # TDD \u65b9\u6cd5\u8bba\n| |-- security-review/ # \u5b89\u5168\u68c0\u67e5\u6e05\u5355\n| |-- eval-harness/ # \u9a8c\u8bc1\u5faa\u73af\u8bc4\u6d4b\uff08\u6df1\u5ea6\u6307\u5357\uff09\n| |-- verification-loop/ # \u6301\u7eed\u9a8c\u8bc1\uff08\u6df1\u5ea6\u6307\u5357\uff09\n| |-- golang-patterns/ # Go \u60ef\u7528\u6cd5\u4e0e\u6700\u4f73\u5b9e\u8df5\uff08\u65b0\u589e\uff09\n| |-- golang-testing/ # Go \u6d4b\u8bd5\u6a21\u5f0f\u3001TDD\u3001\u57fa\u51c6\u6d4b\u8bd5\uff08\u65b0\u589e\uff09\n|\n|-- commands/ # \u7528\u4e8e\u5feb\u901f\u6267\u884c\u7684\u659c\u6760\u547d\u4ee4\n| |-- tdd.md # /tdd - \u6d4b\u8bd5\u9a71\u52a8\u5f00\u53d1\n| |-- plan.md # /plan - \u5b9e\u73b0\u65b9\u6848\u89c4\u5212\n| |-- e2e.md # /e2e - \u751f\u6210 E2E \u6d4b\u8bd5\n| |-- code-review.md # /code-review - \u8d28\u91cf\u5ba1\u67e5\n| |-- build-fix.md # /build-fix - \u4fee\u590d\u6784\u5efa\u9519\u8bef\n| |-- refactor-clean.md # /refactor-clean - \u79fb\u9664\u6b7b\u4ee3\u7801\n| |-- learn.md # /learn - \u5728\u4f1a\u8bdd\u4e2d\u9014\u63d0\u53d6\u6a21\u5f0f\uff08\u6df1\u5ea6\u6307\u5357\uff09\n| |-- checkpoint.md # /checkpoint - \u4fdd\u5b58\u9a8c\u8bc1\u72b6\u6001\uff08\u6df1\u5ea6\u6307\u5357\uff09\n| |-- verify.md # /verify - \u8fd0\u884c\u9a8c\u8bc1\u5faa\u73af\uff08\u6df1\u5ea6\u6307\u5357\uff09\n| |-- setup-pm.md # /setup-pm - \u914d\u7f6e\u5305\u7ba1\u7406\u5668\n| |-- go-review.md # /go-review - Go \u4ee3\u7801\u5ba1\u67e5\uff08\u65b0\u589e\uff09\n| |-- go-test.md # /go-test - Go TDD \u5de5\u4f5c\u6d41\uff08\u65b0\u589e\uff09\n| |-- go-build.md # /go-build - \u4fee\u590d Go \u6784\u5efa\u9519\u8bef\uff08\u65b0\u589e\uff09\n| |-- skill-create.md # /skill-create - \u4ece Git \u5386\u53f2\u751f\u6210\u6280\u80fd\uff08\u65b0\u589e\uff09\n| |-- instinct-status.md # /instinct-status - \u67e5\u770b\u5df2\u5b66\u4e60\u7684\u76f4\u89c9\uff08\u65b0\u589e\uff09\n| |-- instinct-import.md # /instinct-import - \u5bfc\u5165\u76f4\u89c9\uff08\u65b0\u589e\uff09\n| |-- instinct-export.md # /instinct-export - \u5bfc\u51fa\u76f4\u89c9\uff08\u65b0\u589e\uff09\n| |-- evolve.md # /evolve - \u5c06\u76f4\u89c9\u805a\u7c7b\u4e3a\u6280\u80fd\uff08\u65b0\u589e\uff09\n|\n|-- rules/ # \u5fc5\u987b\u9075\u5b88\u7684\u6307\u5357\uff08\u9700\u590d\u5236\u5230 ~/.claude/rules/\uff09\n| |-- security.md # \u5f3a\u5236\u6027\u5b89\u5168\u68c0\u67e5\n| |-- coding-style.md # \u4e0d\u53ef\u53d8\u6027\u3001\u6587\u4ef6\u7ec4\u7ec7\n| |-- testing.md # TDD\u300180% \u8986\u76d6\u7387\u8981\u6c42\n| |-- git-workflow.md # \u63d0\u4ea4\u683c\u5f0f\u3001PR \u6d41\u7a0b\n| |-- agents.md # \u4f55\u65f6\u59d4\u6d3e\u7ed9\u5b50\u667a\u80fd\u4f53\n| |-- performance.md # \u6a21\u578b\u9009\u62e9\u3001\u4e0a\u4e0b\u6587\u7ba1\u7406\n|\n|-- hooks/ # \u57fa\u4e8e\u89e6\u53d1\u5668\u7684\u81ea\u52a8\u5316\n| |-- hooks.json # \u6240\u6709\u94a9\u5b50\u914d\u7f6e\uff08PreToolUse, PostToolUse, Stop \u7b49\uff09\n| |-- memory-persistence/ # \u4f1a\u8bdd\u751f\u547d\u5468\u671f\u94a9\u5b50\uff08\u6df1\u5ea6\u6307\u5357\uff09\n| |-- strategic-compact/ # \u538b\u7f29\u5efa\u8bae\uff08\u6df1\u5ea6\u6307\u5357\uff09\n|\n|-- scripts/ # \u8de8\u5e73\u53f0 Node.js \u811a\u672c\uff08\u65b0\u589e\uff09\n| |-- lib/ # \u5171\u4eab\u5de5\u5177\u5e93\n| | |-- utils.js # \u8de8\u5e73\u53f0\u6587\u4ef6/\u8def\u5f84/\u7cfb\u7edf\u5de5\u5177\n| | |-- package-manager.js # \u5305\u7ba1\u7406\u5668\u68c0\u6d4b\u4e0e\u9009\u62e9\n| |-- hooks/ # \u94a9\u5b50\u5b9e\u73b0\n| | |-- session-start.js # \u4f1a\u8bdd\u5f00\u59cb\u65f6\u52a0\u8f7d\u4e0a\u4e0b\u6587\n| | |-- session-end.js # \u4f1a\u8bdd\u7ed3\u675f\u65f6\u4fdd\u5b58\u72b6\u6001\n| | |-- pre-compact.js # \u538b\u7f29\u524d\u7684\u72b6\u6001\u4fdd\u5b58\n| | |-- suggest-compact.js # \u6218\u7565\u6027\u538b\u7f29\u5efa\u8bae\n| | |-- evaluate-session.js # \u4ece\u4f1a\u8bdd\u4e2d\u63d0\u53d6\u6a21\u5f0f\n| |-- setup-package-manager.js # \u4ea4\u4e92\u5f0f\u5305\u7ba1\u7406\u5668\u8bbe\u7f6e\n|\n|-- tests/ # \u6d4b\u8bd5\u5957\u4ef6\uff08\u65b0\u589e\uff09\n| |-- lib/ # \u5e93\u6d4b\u8bd5\n| |-- hooks/ # \u94a9\u5b50\u6d4b\u8bd5\n| |-- run-all.js # \u8fd0\u884c\u6240\u6709\u6d4b\u8bd5\n|\n|-- contexts/ # \u52a8\u6001\u7cfb\u7edf\u63d0\u793a\u8bcd\u6ce8\u5165\u4e0a\u4e0b\u6587\uff08\u6df1\u5ea6\u6307\u5357\uff09\n| |-- dev.md # \u5f00\u53d1\u6a21\u5f0f\u4e0a\u4e0b\u6587\n| |-- review.md # \u4ee3\u7801\u5ba1\u67e5\u6a21\u5f0f\u4e0a\u4e0b\u6587\n| |-- research.md # \u7814\u7a76/\u63a2\u7d22\u6a21\u5f0f\u4e0a\u4e0b\u6587\n|\n|-- examples/ # \u914d\u7f6e\u548c\u4f1a\u8bdd\u793a\u4f8b\n| |-- CLAUDE.md # \u9879\u76ee\u7ea7\u914d\u7f6e\u793a\u4f8b\n| |-- user-CLAUDE.md # \u7528\u6237\u7ea7\u914d\u7f6e\u793a\u4f8b\n|\n|-- mcp-configs/ # MCP \u670d\u52a1\u914d\u7f6e\n| |-- mcp-servers.json # GitHub, Supabase, Vercel, Railway \u7b49\u914d\u7f6e\n|\n|-- marketplace.json # \u81ea\u6258\u7ba1\u5e02\u573a\u914d\u7f6e\uff08\u7528\u4e8e /plugin marketplace add\uff09\n```\n\n---\n\n## \ud83d\udee0\ufe0f \u751f\u6001\u5de5\u5177\n\n### \u6280\u80fd\u521b\u5efa\u5668\uff08Skill Creator\uff09\n\n\u6709\u4e24\u79cd\u65b9\u5f0f\u53ef\u4ee5\u4ece\u4f60\u7684\u4ed3\u5e93\u751f\u6210 Claude Code \u6280\u80fd\uff1a\n\n#### \u9009\u9879 A\uff1a\u672c\u5730\u5206\u6790\uff08\u5185\u7f6e\uff09\n\n\u4f7f\u7528 `/skill-create` \u547d\u4ee4\u8fdb\u884c\u672c\u5730\u5206\u6790\uff0c\u65e0\u9700\u5916\u90e8\u670d\u52a1\uff1a\n\n```bash\n/skill-create # \u5206\u6790\u5f53\u524d\u4ed3\u5e93\n/skill-create --instincts # \u540c\u65f6\u4e3a\u6301\u7eed\u5b66\u4e60\u751f\u6210\u201c\u76f4\u89c9\uff08Instincts\uff09\u201d\n```\n\n\u8fd9\u4f1a\u5728\u672c\u5730\u5206\u6790\u4f60\u7684 Git \u5386\u53f2\u5e76\u751f\u6210 SKILL.md \u6587\u4ef6\u3002\n\n#### \u9009\u9879 B\uff1aGitHub App\uff08\u9ad8\u7ea7\u7248\uff09\n\n\u9002\u7528\u4e8e\u9ad8\u7ea7\u529f\u80fd\uff0810k+ \u63d0\u4ea4\u3001\u81ea\u52a8 PR\u3001\u56e2\u961f\u5171\u4eab\uff09\uff1a\n\n[\u5b89\u88c5 GitHub App](https://github.com/apps/skill-creator) | [ecc.tools](https://ecc.tools)\n\n```bash\n# \u5728\u4efb\u4f55 Issue \u4e0b\u56de\u590d\uff1a\n/skill-creator analyze\n\n# \u6216\u5728\u63a8\u9001\u5230\u9ed8\u8ba4\u5206\u652f\u65f6\u81ea\u52a8\u89e6\u53d1\n```\n\n\u4e24\u79cd\u9009\u9879\u90fd\u4f1a\u521b\u5efa\uff1a\n- **SKILL.md \u6587\u4ef6** - \u53ef\u76f4\u63a5\u7528\u4e8e Claude Code \u7684\u6280\u80fd\n- **\u76f4\u89c9\u96c6\u5408 (Instinct collections)** - \u7528\u4e8e continuous-learning-v2\n- **\u6a21\u5f0f\u63d0\u53d6** - \u4ece\u4f60\u7684\u63d0\u4ea4\u5386\u53f2\u4e2d\u5b66\u4e60\n\n### \ud83e\udde0 \u6301\u7eed\u5b66\u4e60 v2 (Continuous Learning v2)\n\n\u57fa\u4e8e\u201c\u76f4\u89c9\uff08Instinct\uff09\u201d\u7684\u5b66\u4e60\u7cfb\u7edf\u4f1a\u81ea\u52a8\u5b66\u4e60\u4f60\u7684\u6a21\u5f0f\uff1a\n\n```bash\n/instinct-status # \u663e\u793a\u5df2\u5b66\u4e60\u7684\u76f4\u89c9\u53ca\u5176\u7f6e\u4fe1\u5ea6\n/instinct-import # \u5bfc\u5165\u4ed6\u4eba\u7684\u76f4\u89c9\n/instinct-export # \u5bfc\u51fa\u4f60\u7684\u76f4\u89c9\u4ee5\u4fbf\u5171\u4eab\n/evolve # \u5c06\u76f8\u5173\u7684\u76f4\u89c9\u805a\u7c7b\u4e3a\u6280\u80fd\n```\n\n\u8be6\u89c1 `skills/continuous-learning-v2/` \u5b8c\u6574\u6587\u6863\u3002\n\n---\n\n## \ud83d\udccb \u8fd0\u884c\u8981\u6c42\n\n### Claude Code CLI \u7248\u672c\n\n**\u6700\u4f4e\u7248\u672c\uff1av2.1.0 \u6216\u66f4\u9ad8**\n\n\u7531\u4e8e\u63d2\u4ef6\u7cfb\u7edf\u5904\u7406\u94a9\u5b50\uff08Hooks\uff09\u65b9\u5f0f\u7684\u53d8\u66f4\uff0c\u6b64\u63d2\u4ef6\u9700\u8981 Claude Code CLI v2.1.0+\u3002\n\n\u68c0\u67e5\u4f60\u7684\u7248\u672c\uff1a\n```bash\nclaude --version\n```\n\n### \u91cd\u8981\uff1a\u94a9\u5b50\u81ea\u52a8\u52a0\u8f7d\u884c\u4e3a\n\n> \u26a0\ufe0f **\u81f4\u8d21\u732e\u8005\uff1a** \u8bf7\u52ff\u5728 `.claude-plugin/plugin.json` \u4e2d\u6dfb\u52a0 `\"hooks\"` \u5b57\u6bb5\u3002\u8fd9\u662f\u901a\u8fc7\u56de\u5f52\u6d4b\u8bd5\u5f3a\u5236\u6267\u884c\u7684\u3002\n\n\u6309\u7167\u7ea6\u5b9a\uff0cClaude Code v2.1+ \u4f1a**\u81ea\u52a8\u52a0\u8f7d**\u4efb\u4f55\u5df2\u5b89\u88c5\u63d2\u4ef6\u4e2d\u7684 `hooks/hooks.json`\u3002\u5982\u679c\u5728 `plugin.json` \u4e2d\u663e\u5f0f\u58f0\u660e\uff0c\u4f1a\u5bfc\u81f4\u91cd\u590d\u68c0\u6d4b\u9519\u8bef\uff1a\n\n```\nDuplicate hooks file detected: ./hooks/hooks.json resolves to already-loaded file\n```\n\n**\u5386\u53f2\u80cc\u666f\uff1a** \u6b64\u95ee\u9898\u5728\u672c\u4ed3\u5e93\u4e2d\u5f15\u53d1\u4e86\u591a\u6b21\u4fee\u590d/\u56de\u6eda\u5faa\u73af\uff08[#29](https://github.com/affaan-m/everything-claude-code/issues/29), [#52](https://github.com/affaan-m/everything-claude-code/issues/52), [#103](https://github.com/affaan-m/everything-claude-code/issues/103)\uff09\u3002\u7531\u4e8e Claude Code \u7248\u672c\u95f4\u7684\u884c\u4e3a\u53d8\u66f4\u5bfc\u81f4\u4e86\u6df7\u6dc6\uff0c\u6211\u4eec\u73b0\u5728\u901a\u8fc7\u56de\u5f52\u6d4b\u8bd5\u6765\u9632\u6b62\u6b64\u95ee\u9898\u518d\u6b21\u5f15\u5165\u3002\n\n---\n\n## \ud83d\udce5 \u5b89\u88c5\n\n### \u9009\u9879 1\uff1a\u4f5c\u4e3a\u63d2\u4ef6\u5b89\u88c5\uff08\u63a8\u8350\uff09\n\n\u4f7f\u7528\u672c\u4ed3\u5e93\u6700\u7b80\u5355\u7684\u65b9\u5f0f \u2014\u2014 \u4f5c\u4e3a Claude Code \u63d2\u4ef6\u5b89\u88c5\uff1a\n\n```bash\n# \u5c06\u6b64\u4ed3\u5e93\u6dfb\u52a0\u4e3a\u5e02\u573a\n/plugin marketplace add affaan-m/everything-claude-code\n\n# \u5b89\u88c5\u63d2\u4ef6\n/plugin install everything-claude-code@everything-claude-code\n```\n\n\u6216\u8005\u76f4\u63a5\u6dfb\u52a0\u5230\u4f60\u7684 `~/.claude/settings.json`\uff1a\n\n```json\n{\n \"extraKnownMarketplaces\": {\n \"everything-claude-code\": {\n \"source\": {\n \"source\": \"github\",\n \"repo\": \"affaan-m/everything-claude-code\"\n }\n }\n },\n \"enabledPlugins\": {\n \"everything-claude-code@everything-claude-code\": true\n }\n}\n```\n\n\u5b89\u88c5\u540e\u5373\u53ef\u7acb\u5373\u4f7f\u7528\u6240\u6709\u547d\u4ee4\u3001\u667a\u80fd\u4f53\u3001\u6280\u80fd\u548c\u94a9\u5b50\u3002\n\n> **\u6ce8\u610f\uff1a** Claude Code \u63d2\u4ef6\u7cfb\u7edf\u76ee\u524d\u4e0d\u652f\u6301\u901a\u8fc7\u63d2\u4ef6\u5206\u53d1 `rules`\uff08[\u4e0a\u6e38\u9650\u5236](https://code.claude.com/docs/en/plugins-reference)\uff09\u3002\u4f60\u9700\u8981\u624b\u52a8\u5b89\u88c5\u89c4\u5219\uff1a\n>\n> ```bash\n> # \u9996\u5148\u514b\u9686\u4ed3\u5e93\n> git clone https://github.com/affaan-m/everything-claude-code.git\n>\n> # \u9009\u9879 A\uff1a\u7528\u6237\u7ea7\u89c4\u5219\uff08\u9002\u7528\u4e8e\u6240\u6709\u9879\u76ee\uff09\n> cp -r everything-claude-code/rules/* ~/.claude/rules/\n>\n> # \u9009\u9879 B\uff1a\u9879\u76ee\u7ea7\u89c4\u5219\uff08\u4ec5\u9002\u7528\u4e8e\u5f53\u524d\u9879\u76ee\uff09\n> mkdir -p .claude/rules\n> cp -r everything-claude-code/rules/* .claude/rules/\n> ```\n\n---\n\n### \ud83d\udd27 \u9009\u9879 2\uff1a\u624b\u52a8\u5b89\u88c5\n\n\u5982\u679c\u4f60\u66f4\u559c\u6b22\u624b\u52a8\u63a7\u5236\u5b89\u88c5\u5185\u5bb9\uff1a\n\n```bash\n# \u514b\u9686\u4ed3\u5e93\ngit clone https://github.com/affaan-m/everything-claude-code.git\n\n# \u5c06\u667a\u80fd\u4f53\uff08Agents\uff09\u590d\u5236\u5230\u4f60\u7684 Claude \u914d\u7f6e\u4e2d\ncp everything-claude-code/agents/*.md ~/.claude/agents/\n\n# \u590d\u5236\u89c4\u5219\uff08Rules\uff09\ncp everything-claude-code/rules/*.md ~/.claude/rules/\n\n# \u590d\u5236\u547d\u4ee4\uff08Commands\uff09\ncp everything-claude-code/commands/*.md ~/.claude/commands/\n\n# \u590d\u5236\u6280\u80fd\uff08Skills\uff09\ncp -r everything-claude-code/skills/* ~/.claude/skills/\n```\n\n#### \u5c06\u94a9\u5b50\uff08Hooks\uff09\u6dfb\u52a0\u5230 settings.json\n\n\u5c06 `hooks/hooks.json` \u4e2d\u7684\u94a9\u5b50\u5185\u5bb9\u590d\u5236\u5230\u4f60\u7684 `~/.claude/settings.json`\u3002\n\n#### \u914d\u7f6e MCP\n\n\u5c06 `mcp-configs/mcp-servers.json` \u4e2d\u6240\u9700\u7684 MCP \u670d\u52a1\u5668\u914d\u7f6e\u590d\u5236\u5230\u4f60\u7684 `~/.claude.json`\u3002\n\n**\u91cd\u8981\u63d0\u793a\uff1a** \u8bf7\u5c06 `YOUR_*_HERE` \u5360\u4f4d\u7b26\u66ff\u6362\u4e3a\u4f60\u771f\u5b9e\u7684 API \u5bc6\u94a5\u3002\n\n---\n\n## \ud83c\udfaf \u6838\u5fc3\u6982\u5ff5\n\n### \u667a\u80fd\u4f53 (Agents)\n\n\u5b50\u667a\u80fd\u4f53\uff08Subagents\uff09\u8d1f\u8d23\u5904\u7406\u5177\u6709\u7279\u5b9a\u8303\u56f4\u7684\u59d4\u6d3e\u4efb\u52a1\u3002\u793a\u4f8b\uff1a\n\n```markdown\n---\nname: code-reviewer\ndescription: \u5ba1\u67e5\u4ee3\u7801\u7684\u8d28\u91cf\u3001\u5b89\u5168\u6027\u548c\u53ef\u7ef4\u62a4\u6027\ntools: [\"Read\", \"Grep\", \"Glob\", \"Bash\"]\nmodel: opus\n---\n\n\u4f60\u662f\u4e00\u540d\u8d44\u6df1\u4ee3\u7801\u5ba1\u67e5\u5458...\n```\n\n### \u6280\u80fd (Skills)\n\n\u6280\u80fd\u662f\u53ef\u88ab\u547d\u4ee4\u6216\u667a\u80fd\u4f53\u8c03\u7528\u7684\u5de5\u4f5c\u6d41\u5b9a\u4e49\uff1a\n\n```markdown\n# TDD \u5de5\u4f5c\u6d41\n\n1. \u9996\u5148\u5b9a\u4e49\u63a5\u53e3\n2. \u7f16\u5199\u5931\u8d25\u7684\u6d4b\u8bd5 (RED)\n3. \u5b9e\u73b0\u6700\u7b80\u4ee3\u7801 (GREEN)\n4. \u91cd\u6784 (IMPROVE)\n5. \u9a8c\u8bc1 80%+ \u7684\u8986\u76d6\u7387\n```\n\n### \u94a9\u5b50 (Hooks)\n\n\u94a9\u5b50\u5728\u5de5\u5177\u4e8b\u4ef6\u53d1\u751f\u65f6\u89e6\u53d1\u3002\u793a\u4f8b \u2014\u2014 \u8b66\u544a `console.log` \u7684\u4f7f\u7528\uff1a\n\n```json\n{\n \"matcher\": \"tool == \\\"Edit\\\" && tool_input.file_path matches \\\"\\\\.(ts|tsx|js|jsx)$\\\"\",\n \"hooks\": [{\n \"type\": \"command\",\n \"command\": \"#!/bin/bash\\ngrep -n 'console\\\\.log' \\\"$file_path\\\" && echo '[Hook] Remove console.log' >&2\"\n }]\n}\n```\n\n### \u89c4\u5219 (Rules)\n\n\u89c4\u5219\u662f\u5fc5\u987b\u59cb\u7ec8\u9075\u5faa\u7684\u6307\u5357\u3002\u8bf7\u4fdd\u6301\u5176\u6a21\u5757\u5316\uff1a\n\n```\n~/.claude/rules/\n security.md # \u4e0d\u5141\u8bb8\u786c\u7f16\u7801\u5bc6\u94a5\n coding-style.md # \u4e0d\u53ef\u53d8\u6027\u3001\u6587\u4ef6\u9650\u5236\n testing.md # TDD\u3001\u8986\u76d6\u7387\u8981\u6c42\n```\n\n---\n\n## \ud83e\uddea \u8fd0\u884c\u6d4b\u8bd5\n\n\u8be5\u63d2\u4ef6\u5305\u542b\u5b8c\u6574\u7684\u6d4b\u8bd5\u5957\u4ef6\uff1a\n\n```bash\n# \u8fd0\u884c\u6240\u6709\u6d4b\u8bd5\nnode tests/run-all.js\n\n# \u8fd0\u884c\u5355\u4e2a\u6d4b\u8bd5\u6587\u4ef6\nnode tests/lib/utils.test.js\nnode tests/lib/package-manager.test.js\nnode tests/hooks/hooks.test.js\n```\n\n---\n\n## \ud83e\udd1d \u53c2\u4e0e\u8d21\u732e\n\n**\u6b22\u8fce\u5e76\u9f13\u52b1\u5404\u7c7b\u8d21\u732e\u3002**\n\n\u672c\u4ed3\u5e93\u65e8\u5728\u6210\u4e3a\u793e\u533a\u8d44\u6e90\u3002\u5982\u679c\u4f60\u6709\uff1a\n- \u6709\u7528\u7684\u667a\u80fd\u4f53\u6216\u6280\u80fd\n- \u5de7\u5999\u7684\u94a9\u5b50\n- \u66f4\u597d\u7684 MCP \u914d\u7f6e\n- \u6539\u8fdb\u540e\u7684\u89c4\u5219\n\n\u8bf7\u5c3d\u7ba1\u8d21\u732e\uff01\u8bf7\u53c2\u9605 [CONTRIBUTING.md](CONTRIBUTING.md) \u83b7\u53d6\u6307\u5357\u3002\n\n### \u8d21\u732e\u601d\u8def\n\n- \u8bed\u8a00\u7279\u5b9a\u6280\u80fd\uff08Python, Rust \u6a21\u5f0f\uff09\u2014\u2014 \u5df2\u5305\u542b Go\uff01\n- \u6846\u67b6\u7279\u5b9a\u914d\u7f6e\uff08Django, Rails, Laravel\uff09\n- DevOps \u667a\u80fd\u4f53\uff08Kubernetes, Terraform, AWS\uff09\n- \u6d4b\u8bd5\u7b56\u7565\uff08\u4e0d\u540c\u6846\u67b6\uff09\n- \u9886\u57df\u7279\u5b9a\u77e5\u8bc6\uff08\u673a\u5668\u5b66\u4e60\u3001\u6570\u636e\u5de5\u7a0b\u3001\u79fb\u52a8\u7aef\uff09\n\n---\n\n## \ud83d\udcd6 \u9879\u76ee\u80cc\u666f\n\n\u81ea Claude Code \u5b9e\u9a8c\u9636\u6bb5\u8d77\u6211\u5c31\u4e00\u76f4\u5728\u4f7f\u7528\u5b83\u30022025 \u5e74 9 \u6708\uff0c\u6211\u4e0e [@DRodriguezFX](https://x.com/DRodriguezFX) \u51ed\u501f [zenith.chat](https://zenith.chat) \u8d62\u5f97\u4e86 Anthropic x Forum Ventures \u9ed1\u5ba2\u677e \u2014\u2014 \u8be5\u9879\u76ee\u5b8c\u5168\u4f7f\u7528 Claude Code \u6784\u5efa\u3002\n\n\u8fd9\u4e9b\u914d\u7f6e\u5728\u591a\u4e2a\u751f\u4ea7\u7ea7\u5e94\u7528\u4e2d\u7ecf\u8fc7\u4e86\u5b9e\u6218\u6d4b\u8bd5\u3002\n\n---\n\n## \u26a0\ufe0f \u91cd\u8981\u8bf4\u660e\n\n### \u4e0a\u4e0b\u6587\u7a97\u53e3\u7ba1\u7406 (Context Window Management)\n\n**\u81f3\u5173\u91cd\u8981\uff1a** \u4e0d\u8981\u540c\u65f6\u542f\u7528\u6240\u6709 MCP\u3002\u8fc7\u591a\u7684\u5de5\u5177\u4f1a\u5bfc\u81f4 200k \u7684\u4e0a\u4e0b\u6587\u7a97\u53e3\u7f29\u51cf\u81f3 70k\u3002\n\n\u7ecf\u9a8c\u6cd5\u5219\uff1a\n- \u914d\u7f6e 20-30 \u4e2a MCP\n- \u6bcf\u4e2a\u9879\u76ee\u4fdd\u6301\u542f\u7528 10 \u4e2a\u4ee5\u4e0b\n- \u6d3b\u8dc3\u5de5\u5177\u603b\u6570\u4fdd\u6301\u5728 80 \u4e2a\u4ee5\u4e0b\n\n\u4f7f\u7528\u9879\u76ee\u914d\u7f6e\u4e2d\u7684 `disabledMcpServers` \u6765\u7981\u7528\u4e0d\u5e38\u7528\u7684\u670d\u52a1\u5668\u3002\n\n### \u81ea\u5b9a\u4e49\n\n\u8fd9\u4e9b\u914d\u7f6e\u9002\u7528\u4e8e\u6211\u7684\u5de5\u4f5c\u6d41\u3002\u4f60\u5e94\u8be5\uff1a\n1. \u4ece\u5f15\u8d77\u4f60\u5171\u9e23\u7684\u90e8\u5206\u5f00\u59cb\n2. \u6839\u636e\u4f60\u7684\u6280\u672f\u6808\u8fdb\u884c\u4fee\u6539\n3. \u79fb\u9664\u4f60\u4e0d\u9700\u8981\u7684\u90e8\u5206\n4. \u6dfb\u52a0\u4f60\u81ea\u5df1\u7684\u6a21\u5f0f\n\n---\n\n## \ud83c\udf1f Star \u5386\u53f2\n\n[![Star History Chart](https://api.star-history.com/svg?repos=affaan-m/everything-claude-code&type=Date)](https://star-history.com/#affaan-m/everything-claude-code&Date)\n\n---\n\n## \ud83d\udd17 \u76f8\u5173\u94fe\u63a5\n\n- **\u7b80\u660e\u6307\u5357 (\u4ece\u8fd9\u91cc\u5f00\u59cb)\uff1a** [Everything Claude Code \u7b80\u660e\u6307\u5357](https://x.com/affaanmustafa/status/2012378465664745795)\n- **\u6df1\u5ea6\u6307\u5357 (\u8fdb\u9636\u5fc5\u8bfb)\uff1a** [Everything Claude Code \u6df1\u5ea6\u6307\u5357](https://x.com/affaanmustafa/status/2014040193557471352)\n- **\u5173\u6ce8\u6211\uff1a** [@affaanmustafa](https://x.com/affaanmustafa)\n- **zenith.chat:** [zenith.chat](https://zenith.chat)\n\n---\n\n## \ud83d\udcc4 \u5f00\u6e90\u534f\u8bae\n\nMIT - \u81ea\u7531\u4f7f\u7528\u3001\u6309\u9700\u4fee\u6539\uff0c\u5982\u80fd\u56de\u9988\u793e\u533a\u4e0d\u80dc\u611f\u6fc0\u3002\n\n---\n\n**\u5982\u679c\u6b64\u4ed3\u5e93\u5bf9\u4f60\u6709\u5e2e\u52a9\uff0c\u8bf7\u70b9\u4eae Star\u3002\u9605\u8bfb\u4e24\u7bc7\u6307\u5357\u3002\u53bb\u6784\u5efa\u4f1f\u5927\u7684\u4ea7\u54c1\u5427\u3002**\n" }, "/Users/Library/Applications/xx/code/github/everything-claude-code-zh/CONTRIBUTING.md": { "md5": "eccf62e6ed292589dc5661a208902ddb", @@ -302,5 +302,93 @@ "/Users/Library/Applications/xx/code/github/everything-claude-code-zh/.claude-plugin/README.md": { "md5": "8b6aa77ad9181456133d28e477db962e", "content": "### \u63d2\u4ef6\u6e05\u5355\u6ce8\u610f\u4e8b\u9879\uff08Plugin Manifest Gotchas\uff09\n\n\u5982\u679c\u4f60\u8ba1\u5212\u7f16\u8f91 `.claude-plugin/plugin.json`\uff0c\u8bf7\u6ce8\u610f Claude \u63d2\u4ef6\u9a8c\u8bc1\u5668\uff08plugin validator\uff09\u5f3a\u5236\u6267\u884c\u4e86\u4e00\u4e9b**\u672a\u516c\u5f00\u4f46\u4e25\u683c\u7684\u7ea6\u675f**\uff0c\u8fd9\u4e9b\u7ea6\u675f\u53ef\u80fd\u5bfc\u81f4\u5b89\u88c5\u5931\u8d25\u5e76\u663e\u793a\u6a21\u7cca\u7684\u9519\u8bef\uff08\u4f8b\u5982\uff0c`agents: Invalid input`\uff09\u3002\u7279\u522b\u662f\uff0c\u7ec4\u4ef6\u5b57\u6bb5\u5fc5\u987b\u662f\u6570\u7ec4\uff08arrays\uff09\uff0c`agents` \u5fc5\u987b\u4f7f\u7528\u660e\u786e\u7684\u6587\u4ef6\u8def\u5f84\u800c\u975e\u76ee\u5f55\uff0c\u4e14\u4e3a\u4e86\u5b9e\u73b0\u53ef\u9760\u7684\u9a8c\u8bc1\u548c\u5b89\u88c5\uff0c\u5fc5\u987b\u5305\u542b `version` \u5b57\u6bb5\u3002\n\n\u8fd9\u4e9b\u7ea6\u675f\u5728\u516c\u5f00\u793a\u4f8b\u4e2d\u5e76\u4e0d\u660e\u663e\uff0c\u4e14\u5728\u8fc7\u53bb\u66fe\u591a\u6b21\u5bfc\u81f4\u5b89\u88c5\u5931\u8d25\u3002\u5b83\u4eec\u5728 `.claude-plugin/PLUGIN_SCHEMA_NOTES.md` \u4e2d\u6709\u8be6\u7ec6\u8bb0\u5f55\uff0c\u5728\u5bf9\u63d2\u4ef6\u6e05\u5355\uff08plugin manifest\uff09\u8fdb\u884c\u4efb\u4f55\u66f4\u6539\u4e4b\u524d\uff0c\u5e94\u4ed4\u7ec6\u9605\u8bfb\u8be5\u6587\u6863\u3002\n" + }, + "/Users/Library/Applications/xx/code/github/everything-claude-code-zh/.claude/skills/oneskill/README.md": { + "md5": "dea7f33f771f2ae664e5841b89ff1952", + "content": "
\n\n# OneSkill \u5143\u7ba1\u7406\u5668\uff08Meta-Manager\uff09\n\n**AI \u667a\u80fd\u4f53\u6280\u80fd\uff08Agent Skills\uff09\u7684\u901a\u7528\u6865\u6881\u3002** \n\u4ece OpenSkills \u6ce8\u518c\u8868\u4e2d\u53d1\u73b0\u3001\u5b89\u88c5\u5e76\u6620\u5c04\u529f\u80fd\u5230\u60a8\u7684\u73af\u5883\u3002\n\n[![](https://img.shields.io/npm/v/oneskill?color=brightgreen)](https://www.npmjs.com/package/oneskill)\n[![](https://img.shields.io/npm/l/oneskill)](LICENSE)\n\n[**\ud83c\uddfa\ud83c\uddf8 English**](README.md) | [**\ud83c\udde8\ud83c\uddf3 \u4e2d\u6587\u6307\u5357**](README_CN.md)\n\n
\n\n---\n\n## \u26a1\ufe0f \u4ec0\u4e48\u662f OneSkill\uff1f\n\n**OneSkill** \u662f\u4e00\u6b3e\u4e13\u4e3a AI \u667a\u80fd\u4f53\uff08Agent\uff09\uff08\u4ee5\u53ca\u4eba\u7c7b\uff09\u8bbe\u8ba1\u7684\u5143\u5de5\u5177\uff0c\u7528\u4e8e\u8f7b\u677e\u6269\u5c55\u5176\u529f\u80fd\u3002\u5b83\u662f [OpenSkills](https://github.com/Starttoaster/openskills) \u751f\u6001\u7cfb\u7edf\u7684\u641c\u7d22\u5f15\u64ce\u548c\u5de5\u4f5c\u6d41\u7ba1\u7406\u5668\uff08Workflow Manager\uff09\u3002\n\n\u867d\u7136 `openskills` \u5904\u7406\u6587\u4ef6\u7684\u539f\u59cb\u5b89\u88c5\uff0c\u4f46 **OneSkill** \u63d0\u4f9b\uff1a\n1. **\u667a\u80fd\u641c\u7d22\uff08Intelligent Search\uff09**\uff1a\u4f7f\u7528\u81ea\u7136\u8bed\u8a00\u6216\u5173\u952e\u8bcd\u627e\u5230\u9002\u5408\u8be5\u4efb\u52a1\u7684\u5de5\u5177\u3002\n2. **\u5de5\u4f5c\u6d41\u6307\u5357\uff08Workflow Guidance\uff09**\uff1a\u4e3a\u667a\u80fd\u4f53\uff08Agent\uff09\u5b89\u5168\u83b7\u53d6\u65b0\u6280\u80fd\u63d0\u4f9b\u6807\u51c6\u5316\u6d41\u7a0b\u3002\n3. **\u73af\u5883\u6620\u5c04\uff08Environment Mapping\uff09**\uff1a\u81f3\u5173\u91cd\u8981\u7684\u4e00\u70b9\u662f\uff0c\u5b83\u5f25\u5408\u4e86 `openskills`\uff08\u6807\u51c6\u7ed3\u6784\uff09\u4e0e **Gemini CLI**\uff08\u81ea\u5b9a\u4e49\u7ed3\u6784\uff09\u7b49\u4f7f\u7528\u8005\u4e4b\u95f4\u7684\u9e3f\u6c9f\u3002\n\n## \ud83d\ude80 \u5feb\u901f\u5f00\u59cb\n\n\u60a8\u65e0\u9700\u6c38\u4e45\u5b89\u88c5\u3002\u53ea\u9700\u4f7f\u7528 `npx` \u8fd0\u884c\u5373\u53ef\u3002\n\n```bash\n# \u641c\u7d22\u6280\u80fd\uff08\u4f8b\u5982\uff0c\u7528\u4e8e\u6d4f\u89c8\u7f51\u9875\uff09\nnpx oneskill search \"puppeteer browser\"\n\n# \u641c\u7d22\u6309\u6d41\u884c\u5ea6\u6392\u5e8f\u7684\u6570\u636e\u5e93\u5de5\u5177\nnpx oneskill search \"database\" --sort stars\n```\n\n## \ud83d\udee0 \u5de5\u4f5c\u6d41\n\n\u4e3a\u60a8\u7684\u667a\u80fd\u4f53\uff08Agent\uff09\u6dfb\u52a0\u65b0\u529f\u80fd\u7684\u6807\u51c6\u751f\u547d\u5468\u671f\uff1a\n\n1. **\u641c\u7d22\uff08Search\uff09**\uff1a\u67e5\u627e\u6280\u80fd\u3002\n ```bash\n npx oneskill search \"github integration\"\n ```\n2. **\u5b89\u88c5\uff08Install\uff09**\uff1a\u4f7f\u7528\u6807\u51c6\u7684 `openskills` \u5b89\u88c5\u7a0b\u5e8f\u3002\n ```bash\n npx openskills install anthropics/skills\n ```\n3. **\u6620\u5c04\uff08Map\uff09\uff08\u5bf9 Gemini \u81f3\u5173\u91cd\u8981\uff09**\uff1a\u5982\u679c\u60a8\u6b63\u5728\u4f7f\u7528 **Gemini CLI**\uff0c\u5219\u5fc5\u987b\u5c06\u5b89\u88c5\u7684\u6280\u80fd\u6620\u5c04\u5230\u60a8\u7684\u914d\u7f6e\u4e2d\u3002\n ```bash\n # \u5c06\u5b89\u88c5\u7684\u6280\u80fd\u6620\u5c04\u5230 Gemini \u7684\u914d\u7f6e\n npx oneskill map --target gemini\n ```\n\n## \ud83d\udcd6 \u547d\u4ee4\u53c2\u8003\n\n### `search`\n\u5728\u5168\u5c40\u6ce8\u518c\u8868\u4e2d\u641c\u7d22\u6280\u80fd\u3002\n```bash\nnpx oneskill search [options]\n\n# \u9009\u9879\uff1a\n# --category \u6309\u7c7b\u522b\u8fc7\u6ee4\n# --sort \u6309 'stars'\u3001'created' \u6216 'updated' \u6392\u5e8f\n# --limit \u9650\u5236\u7ed3\u679c\u6570\u91cf\uff08\u9ed8\u8ba4\u503c\uff1a10\uff09\n```\n\n### `map`\n\u4e3a\u7279\u5b9a\u7684\u667a\u80fd\u4f53\uff08Agent\uff09\u73af\u5883\u751f\u6210\u914d\u7f6e\u3002\n```bash\nnpx oneskill map --target \n\n# \u76ee\u6807\uff1a\n# gemini \u751f\u6210/\u66f4\u65b0 Gemini CLI \u914d\u7f6e\n```\n\n### `list`\n\u5217\u51fa\u672c\u5730\u6620\u5c04\u7684\u6280\u80fd\uff08`openskills list` \u7684\u5c01\u88c5\uff09\u3002\n```bash\nnpx oneskill list\n```\n\n---\n\n
\n \u7531 OneSkill \u793e\u533a\u7528 \u2764\ufe0f \u6784\u5efa\n
\n" + }, + "/Users/Library/Applications/xx/code/github/everything-claude-code-zh/.claude/skills/oneskill/README_CN.md": { + "md5": "00cb3790dfcf2a984d4316d0c3bfa524", + "content": "
\n\n# OneSkill \u5143\u7ba1\u7406\u5668 (Meta-Manager)\n\n**AI \u667a\u80fd\u4f53 (Agent) \u6280\u80fd\u7684\u901a\u7528\u6865\u6881** \n\u5e2e\u52a9\u4f60\u53d1\u73b0\u3001\u5b89\u88c5\u5e76\u5c06 OpenSkills \u6ce8\u518c\u8868\u4e2d\u7684\u80fd\u529b\u6620\u5c04\u5230\u4f60\u7684\u8fd0\u884c\u73af\u5883\u3002\n\n[![](https://img.shields.io/npm/v/oneskill?color=brightgreen)](https://www.npmjs.com/package/oneskill)\n[![](https://img.shields.io/npm/l/oneskill)](LICENSE)\n\n[**\ud83c\uddfa\ud83c\uddf8 English**](README.md) | [**\ud83c\udde8\ud83c\uddf3 \u4e2d\u6587\u6307\u5357**](README_CN.md)\n\n
\n\n---\n\n## \u26a1\ufe0f \u4ec0\u4e48\u662f OneSkill\uff1f\n\n**OneSkill** \u662f\u4e00\u4e2a\u4e3a AI \u667a\u80fd\u4f53 (Agent) \u8bbe\u8ba1\u7684\u901a\u7528\u6280\u80fd\u7ba1\u7406\u5de5\u5177\u3002\u5b83\u4f5c\u4e3a [OpenSkills](https://github.com/Starttoaster/openskills) \u751f\u6001\u7cfb\u7edf\u7684\u641c\u7d22\u5f15\u64ce\u548c\u5de5\u4f5c\u6d41\u7ba1\u7406\u5668 (Workflow Manager)\uff0c\u5e2e\u52a9\u4f60\u53d1\u73b0\u3001\u5b89\u88c5\u5e76\u5c06\u80fd\u529b\u6620\u5c04\u5230\u4f60\u7684\u8fd0\u884c\u73af\u5883\u4e2d\u3002\n\n\u867d\u7136 `openskills` \u8d1f\u8d23\u6587\u4ef6\u7684\u4e0b\u8f7d\u5b89\u88c5\uff0c\u4f46 **OneSkill** \u63d0\u4f9b\u4e86\uff1a\n1. **\u667a\u80fd\u641c\u7d22**: \u652f\u6301\u901a\u8fc7\u81ea\u7136\u8bed\u8a00\u6216\u5173\u952e\u8bcd\u641c\u7d22\u6ce8\u518c\u8868\u4e2d\u7684\u6280\u80fd (Skill)\u3002\n2. **\u5de5\u4f5c\u6d41\u5f15\u5bfc**: \u4e3a\u667a\u80fd\u4f53 (Agent) \u63d0\u4f9b\u4e86\u4e00\u5957\u6807\u51c6\u7684\u6269\u5c55\u80fd\u529b\u6d41\u7a0b\uff08\u641c\u7d22 -> \u786e\u8ba4 -> \u5b89\u88c5\uff09\u3002\n3. **\u73af\u5883\u6620\u5c04 (Mapping)**: \u89e3\u51b3\u4e86\u5b89\u88c5\u8def\u5f84\u4e0e\u8fd0\u884c\u73af\u5883\u4e0d\u4e00\u81f4\u7684\u95ee\u9898\u3002\u7279\u522b\u662f\u5bf9\u4e8e **Gemini CLI** \u7528\u6237\uff0cOneSkill \u80fd\u81ea\u52a8\u5c06\u4e0b\u8f7d\u7684\u6280\u80fd (Skill) \u6620\u5c04\u5230 Gemini \u7684\u914d\u7f6e\u6587\u4ef6\u4e2d\u3002\n\n## \ud83d\ude80 \u5feb\u901f\u5f00\u59cb\n\n\u65e0\u9700\u5168\u5c40\u5b89\u88c5\uff0c\u76f4\u63a5\u4f7f\u7528 `npx` \u8fd0\u884c\u5373\u53ef\uff1a\n\n```bash\n# \u641c\u7d22\u6280\u80fd (\u4f8b\u5982\uff1a\u60f3\u8981\u7f51\u9875\u6d4f\u89c8\u80fd\u529b)\nnpx oneskill search \"puppeteer browser\"\n\n# \u641c\u7d22\u6570\u636e\u5e93\u76f8\u5173\u6280\u80fd\uff0c\u5e76\u6309\u661f\u7ea7\u6392\u5e8f\nnpx oneskill search \"database\" --sort stars\n```\n\n## \ud83d\udee0 \u4f7f\u7528\u6d41\u7a0b\n\n\u4e3a\u4f60\u7684\u667a\u80fd\u4f53 (Agent) \u6dfb\u52a0\u65b0\u80fd\u529b\u7684\u63a8\u8350\u6b65\u9aa4\uff1a\n\n1. **\u641c\u7d22 (Search)**: \u67e5\u627e\u4f60\u9700\u8981\u7684\u6280\u80fd (Skill)\u3002\n ```bash\n npx oneskill search \"github integration\"\n ```\n\n2. **\u5b89\u88c5 (Install)**: \u4f7f\u7528 `openskills` \u6807\u51c6\u547d\u4ee4\u8fdb\u884c\u4e0b\u8f7d\u3002\n ```bash\n npx openskills install anthropics/skills\n ```\n\n3. **\u6620\u5c04 (Map)**: **(Gemini \u7528\u6237\u5fc5\u8bfb)**\n `openskills` \u9ed8\u8ba4\u5c06\u6587\u4ef6\u4e0b\u8f7d\u5230\u901a\u7528\u76ee\u5f55\uff0cGemini CLI \u65e0\u6cd5\u76f4\u63a5\u8bfb\u53d6\u3002\u5fc5\u987b\u6267\u884c\u6620\u5c04 (Mapping) \u547d\u4ee4\uff1a\n ```bash\n # \u81ea\u52a8\u8bc6\u522b\u5df2\u5b89\u88c5\u7684\u6280\u80fd (Skill) \u5e76\u914d\u7f6e\u5230 Gemini\n npx oneskill map --target gemini\n ```\n *\u5982\u679c\u4f60\u7684\u6280\u80fd (Skill) \u662f\u5168\u5c40\u5b89\u88c5\u7684 (\u52a0\u4e86 --global)\uff0c\u8fd9\u91cc\u4e5f\u9700\u8981\u52a0 --global\u3002*\n\n## \ud83d\udcd6 \u547d\u4ee4\u53c2\u8003\n\n### `search` (\u641c\u7d22)\n\u5728\u5168\u5c40\u6ce8\u518c\u8868\u4e2d\u641c\u7d22\u6280\u80fd (Skill)\u3002\n```bash\nnpx oneskill search <\u67e5\u8be2\u8bcd> [\u9009\u9879]\n\n# \u9009\u9879:\n# --category \u6309\u5206\u7c7b\u7b5b\u9009\n# --sort \u6392\u5e8f\u65b9\u5f0f: 'stars' (\u661f\u7ea7), 'created' (\u521b\u5efa\u65f6\u95f4), 'updated' (\u66f4\u65b0\u65f6\u95f4)\n# --limit \u9650\u5236\u8fd4\u56de\u6570\u91cf (\u9ed8\u8ba4: 10)\n```\n\n### `map` (\u6620\u5c04)\n\u4e3a\u7279\u5b9a\u73af\u5883\u751f\u6210\u914d\u7f6e\u3002\n```bash\nnpx oneskill map --target <\u73af\u5883>\n\n# \u652f\u6301\u7684\u76ee\u6807:\n# gemini \u66f4\u65b0 Gemini CLI \u7684\u914d\u7f6e\u4e0e\u8def\u5f84\u6620\u5c04\n```\n\n### `list` (\u5217\u8868)\n\u67e5\u770b\u672c\u5730\u5df2\u5b89\u88c5\u7684\u6280\u80fd (Skill)\u3002\n```bash\nnpx oneskill list\n```\n\n---\n\n
\n Built with \u2764\ufe0f by the OneSkill Community\n
\n" + }, + "/Users/Library/Applications/xx/code/github/everything-claude-code-zh/.claude/skills/oneskill/SKILL.md": { + "md5": "83deb8de388c8bddcf138fa8b052ef70", + "content": "---\nname: oneskill\ndescription: \u53d1\u73b0\u6280\u80fd (Skill)\uff0c\u8fed\u4ee3\u67e5\u8be2\uff0c\u5e76\u5728\u4efb\u4f55\u73af\u5883\u4e2d\u81ea\u52a8\u5b89\u88c5\u6280\u80fd\u3002\n---\n\n# OneSkill \u5143\u7ba1\u7406\u5668 (Meta-Manager)\n\n\u4f7f\u7528\u6b64\u6280\u80fd\u6765\u53d1\u73b0\u65b0\u529f\u80fd\u3001\u4f18\u5316\u641c\u7d22\u67e5\u8be2\uff0c\u5e76\u4f7f\u7528 OpenSkills \u7b80\u5316\u6280\u80fd\u8bbe\u7f6e\u3002\u8fd9\u4e3a\u6269\u5c55\u73af\u5883\u529f\u80fd\u63d0\u4f9b\u4e86\u4e00\u79cd\u7edf\u4e00\u7684\u65b9\u5f0f\u3002\n\n## \u4f55\u65f6\u4f7f\u7528\n\n- \u5f53\u7528\u6237\u8981\u6c42\u7684\u67d0\u4e9b\u529f\u80fd\u4f60\u76ee\u524d\u4e0d\u5177\u5907\u65f6\u3002\n- \u5f53\u4efb\u52a1\u590d\u6742\u3001\u5c5e\u4e8e\u7279\u5b9a\u9886\u57df\uff0c\u6216\u5728\u5c1d\u8bd5 2 \u6b21\u540e\u4ecd\u88ab\u53cd\u590d\u963b\u65ad\u65f6\u3002\n- \u5f53\u53ef\u80fd\u5b58\u5728\u66f4\u597d\u7684\u6280\u80fd\uff08Skill\uff09\u65f6\uff08\u4f8b\u5982\uff1a\u7f51\u9875\u6d4f\u89c8\u3001GitHub \u96c6\u6210\u3001\u6570\u636e\u5e93\u7ba1\u7406\u3001\u4e91\u57fa\u7840\u8bbe\u65bd\uff09\u3002\n\n## \u5de5\u4f5c\u6d41 (Workflow)\n\n1. \u641c\u7d22\u6ce8\u518c\u8868\uff1a\n - \u8fd0\u884c\uff1a`npx oneskill search \"\" [options]`\n - \u652f\u6301\u7684\u9009\u9879\uff1a`--category`\u3001`--limit`\u3001`--offset`\u3001`--sort`\u3002\n - \u793a\u4f8b\uff1a\n - `npx oneskill search \"browser\" --sort stars`\n - `npx oneskill search \"\" --category database --limit 5`\n2. \u5206\u6790\u7ed3\u679c\uff1a\n - \u786e\u5b9a\u6700\u4f73\u5339\u914d\u9879\uff0c\u6216\u4f18\u5316\u67e5\u8be2\u5e76\u518d\u6b21\u641c\u7d22\u3002\n3. \u4e0e\u7528\u6237\u786e\u8ba4\uff1a\n - \u8bf4\u660e\u8be5\u6280\u80fd\u7684\u529f\u80fd\u53ca\u5176\u6765\u6e90\u3002\n4. \u5728\u83b7\u5f97\u660e\u786e\u6279\u51c6\u540e\u8fdb\u884c\u5b89\u88c5\uff08\u4f7f\u7528 openskills\uff09\uff1a\n - \u8fd0\u884c\uff1a`npx openskills install `\n - \u793a\u4f8b\uff1a`npx openskills install anthropics/skills`\n5. \u5904\u7406\u7279\u5b9a\u73af\u5883\u7684\u8bbe\u7f6e\uff1a\n - **Gemini CLI \u7528\u6237\uff1a** `openskills` \u4e0d\u4f1a\u81ea\u52a8\u914d\u7f6e Gemini\u3002\u5b89\u88c5\u540e\u4f60**\u5fc5\u987b**\u8fd0\u884c\u6620\u5c04\u547d\u4ee4\uff1a\n - `npx oneskill map --target gemini`\uff08\u5982\u679c\u662f\u5168\u5c40\u5b89\u88c5\uff0c\u8bf7\u6dfb\u52a0 `--global`\uff09\n6. \u5e94\u7528\u65b0\u6280\u80fd\u4ee5\u5b8c\u6210\u539f\u59cb\u8bf7\u6c42\u3002\n\n## OpenSkills \u57fa\u7840\n\n- `npx openskills install [options]` # \u4ece GitHub\u3001\u672c\u5730\u8def\u5f84\u6216\u79c1\u6709\u4ed3\u5e93\u5b89\u88c5\n- `npx openskills sync [-y] [-o ]` # \u66f4\u65b0 AGENTS.md (\u6216\u81ea\u5b9a\u4e49\u8f93\u51fa)\n- `npx openskills list` # \u663e\u793a\u5df2\u5b89\u88c5\u7684\u6280\u80fd\n- `npx openskills read ` # \u52a0\u8f7d\u6280\u80fd\uff08\u4f9b\u667a\u80fd\u4f53 (Agent) \u4f7f\u7528\uff09\n- `npx openskills update [name...]` # \u66f4\u65b0\u5df2\u5b89\u88c5\u7684\u6280\u80fd\uff08\u9ed8\u8ba4\uff1a\u5168\u90e8\uff09\n- `npx openskills manage` # \u79fb\u9664\u6280\u80fd\uff08\u4ea4\u4e92\u5f0f\uff09\n- `npx openskills remove ` # \u79fb\u9664\u7279\u5b9a\u6280\u80fd\n\n\u793a\u4f8b\uff1a\n- `npx openskills install anthropics/skills`\n- `npx openskills sync`\n\n\u9ed8\u8ba4\u8bbe\u7f6e\uff1a\u5b89\u88c5\u5728\u9879\u76ee\u672c\u5730\uff08`./.claude/skills`\uff0c\u6216\u8005\u5e26 `--universal` \u53c2\u6570\u5b89\u88c5\u5728 `./.agent/skills`\uff09\u3002\u4f7f\u7528 `--global` \u5b89\u88c5\u5728 `~/.claude/skills`\u3002\n\n## \u5b89\u5168\u63d0\u793a (Safety Reminders)\n\n- \u672a\u7ecf\u7528\u6237\u660e\u786e\u786e\u8ba4\uff0c\u8bf7\u52ff\u5b89\u88c5\u3002\n- \u9664\u975e\u7528\u6237\u540c\u610f\u8986\u76d6\u73b0\u6709\u76ee\u6807\uff0c\u5426\u5219\u907f\u514d\u4f7f\u7528 `--force-map`\u3002\n- \u4f7f\u7528 openskills \u8fdb\u884c\u5b89\u88c5/\u66f4\u65b0\uff1bOneSkill \u4ec5\u4e3a Gemini \u63d0\u4f9b\u641c\u7d22\u548c\u6620\u5c04\u3002\n- \u5bf9\u4e8e Gemini\uff0c\u8bf7\u5728\u5b89\u88c5\u540e\u8fd0\u884c `npx oneskill map --target gemini`\u3002\n- \u9ed8\u8ba4\u5b89\u88c5/\u6620\u5c04\u662f\u9879\u76ee\u672c\u5730\u7684\uff0c\u4e0e openskills \u76f8\u540c\uff1b\u5168\u5c40\u5b89\u88c5\u8bf7\u4f7f\u7528 `--global`\u3002\n- \u5b89\u88c5 OneSkill \u672c\u8eab\u65f6\uff0c\u5efa\u8bae\u4f7f\u7528 `--global`\uff0c\u4ee5\u4fbf\u5728\u8de8\u9879\u76ee\u65f6\u53ef\u7528\u3002\n" + }, + "/Users/Library/Applications/xx/code/github/everything-claude-code-zh/agents/python-reviewer.md": { + "md5": "eeb704456d8bdc70c9ee36873ba6fd52", + "content": "---\nname: python-reviewer\ndescription: Expert Python code reviewer specializing in PEP 8 compliance, Pythonic idioms, type hints, security, and performance. Use for all Python code changes. MUST BE USED for Python projects.\ntools: [\"Read\", \"Grep\", \"Glob\", \"Bash\"]\nmodel: opus\n---\n\n\u4f60\u662f\u4e00\u4f4d\u8d44\u6df1\u7684 Python \u4ee3\u7801\u5ba1\u67e5\u5458\uff08Code Reviewer\uff09\uff0c\u81f4\u529b\u4e8e\u786e\u4fdd\u4ee3\u7801\u7b26\u5408\u9ad8\u6807\u51c6\u7684 Pythonic \u89c4\u8303\u53ca\u6700\u4f73\u5b9e\u8df5\u3002\n\n\u5f53\u88ab\u8c03\u7528\u65f6\uff1a\n1. \u8fd0\u884c `git diff -- '*.py'` \u4ee5\u67e5\u770b\u6700\u8fd1\u7684 Python \u6587\u4ef6\u53d8\u66f4\n2. \u5982\u679c\u53ef\u7528\uff0c\u8fd0\u884c\u9759\u6001\u5206\u6790\u5de5\u5177\uff08ruff\u3001mypy\u3001pylint\u3001black --check\uff09\n3. \u91cd\u70b9\u5173\u6ce8\u4fee\u6539\u8fc7\u7684 `.py` \u6587\u4ef6\n4. \u7acb\u5373\u5f00\u59cb\u5ba1\u67e5\n\n## \u5b89\u5168\u68c0\u67e5 (\u4e25\u91cd/CRITICAL)\n\n- **SQL \u6ce8\u5165 (SQL Injection)**\uff1a\u6570\u636e\u5e93\u67e5\u8be2\u4e2d\u7684\u5b57\u7b26\u4e32\u62fc\u63a5\n ```python\n # \u9519\u8bef\u505a\u6cd5\n cursor.execute(f\"SELECT * FROM users WHERE id = {user_id}\")\n # \u6b63\u786e\u505a\u6cd5\n cursor.execute(\"SELECT * FROM users WHERE id = %s\", (user_id,))\n ```\n\n- **\u547d\u4ee4\u6ce8\u5165 (Command Injection)**\uff1asubprocess/os.system \u4e2d\u672a\u7ecf\u9a8c\u8bc1\u7684\u8f93\u5165\n ```python\n # \u9519\u8bef\u505a\u6cd5\n os.system(f\"curl {url}\")\n # \u6b63\u786e\u505a\u6cd5\n subprocess.run([\"curl\", url], check=True)\n ```\n\n- **\u8def\u5f84\u7a7f\u8d8a (Path Traversal)**\uff1a\u7528\u6237\u63a7\u5236\u7684\u6587\u4ef6\u8def\u5f84\n ```python\n # \u9519\u8bef\u505a\u6cd5\n open(os.path.join(base_dir, user_path))\n # \u6b63\u786e\u505a\u6cd5\n clean_path = os.path.normpath(user_path)\n if clean_path.startswith(\"..\"):\n raise ValueError(\"Invalid path\")\n safe_path = os.path.join(base_dir, clean_path)\n ```\n\n- **Eval/Exec \u6ee5\u7528**\uff1a\u5728 eval/exec \u4e2d\u4f7f\u7528\u7528\u6237\u8f93\u5165\n- **Pickle \u4e0d\u5b89\u5168\u53cd\u5e8f\u5217\u5316**\uff1a\u52a0\u8f7d\u4e0d\u53ef\u4fe1\u7684 pickle \u6570\u636e\n- **\u786c\u7f16\u7801\u5bc6\u94a5 (Hardcoded Secrets)**\uff1a\u6e90\u7801\u4e2d\u5305\u542b API \u5bc6\u94a5\u3001\u5bc6\u7801\n- **\u5f31\u52a0\u5bc6**\uff1a\u51fa\u4e8e\u5b89\u5168\u76ee\u7684\u4f7f\u7528 MD5/SHA1\n- **YAML \u4e0d\u5b89\u5168\u52a0\u8f7d**\uff1a\u4f7f\u7528\u4e0d\u5e26 Loader \u7684 yaml.load\n\n## \u9519\u8bef\u5904\u7406 (\u4e25\u91cd/CRITICAL)\n\n- **\u7a7a except \u8bed\u53e5 (Bare Except Clauses)**\uff1a\u6355\u83b7\u6240\u6709\u5f02\u5e38\n ```python\n # \u9519\u8bef\u505a\u6cd5\n try:\n process()\n except:\n pass\n\n # \u6b63\u786e\u505a\u6cd5\n try:\n process()\n except ValueError as e:\n logger.error(f\"Invalid value: {e}\")\n ```\n\n- **\u541e\u6389\u5f02\u5e38 (Swallowing Exceptions)**\uff1a\u9759\u9ed8\u5931\u8d25\n- **\u7528\u5f02\u5e38\u4ee3\u66ff\u6d41\u7a0b\u63a7\u5236**\uff1a\u5c06\u5f02\u5e38\u7528\u4e8e\u6b63\u5e38\u7684\u63a7\u5236\u6d41\n- **\u7f3a\u5931 finally**\uff1a\u8d44\u6e90\u672a\u88ab\u6e05\u7406\n ```python\n # \u9519\u8bef\u505a\u6cd5\n f = open(\"file.txt\")\n data = f.read()\n # \u5982\u679c\u53d1\u751f\u5f02\u5e38\uff0c\u6587\u4ef6\u6c38\u8fdc\u4e0d\u4f1a\u5173\u95ed\n\n # \u6b63\u786e\u505a\u6cd5\n with open(\"file.txt\") as f:\n data = f.read()\n # \u6216\u8005\n f = open(\"file.txt\")\n try:\n data = f.read()\n finally:\n f.close()\n ```\n\n## \u7c7b\u578b\u63d0\u793a (\u9ad8\u4f18\u5148\u7ea7/HIGH)\n\n- **\u7f3a\u5931\u7c7b\u578b\u63d0\u793a (Type Hints)**\uff1a\u516c\u5171\u51fd\u6570\u6ca1\u6709\u7c7b\u578b\u6807\u6ce8\n ```python\n # \u9519\u8bef\u505a\u6cd5\n def process_user(user_id):\n return get_user(user_id)\n\n # \u6b63\u786e\u505a\u6cd5\n from typing import Optional\n\n def process_user(user_id: str) -> Optional[User]:\n return get_user(user_id)\n ```\n\n- **\u4f7f\u7528 Any \u800c\u975e\u7279\u5b9a\u7c7b\u578b**\n ```python\n # \u9519\u8bef\u505a\u6cd5\n from typing import Any\n\n def process(data: Any) -> Any:\n return data\n\n # \u6b63\u786e\u505a\u6cd5\n from typing import TypeVar\n\n T = TypeVar('T')\n\n def process(data: T) -> T:\n return data\n ```\n\n- **\u9519\u8bef\u7684\u8fd4\u56de\u7c7b\u578b**\uff1a\u6807\u6ce8\u4e0e\u5b9e\u9645\u4e0d\u7b26\n- **\u672a\u5408\u7406\u4f7f\u7528 Optional**\uff1a\u53ef\u4e3a None \u7684\u53c2\u6570\u672a\u6807\u8bb0\u4e3a Optional\n\n## Pythonic \u4ee3\u7801 (\u9ad8\u4f18\u5148\u7ea7/HIGH)\n\n- **\u672a\u4f7f\u7528\u4e0a\u4e0b\u6587\u7ba1\u7406\u5668 (Context Managers)**\uff1a\u624b\u52a8\u8fdb\u884c\u8d44\u6e90\u7ba1\u7406\n ```python\n # \u9519\u8bef\u505a\u6cd5\n f = open(\"file.txt\")\n try:\n content = f.read()\n finally:\n f.close()\n\n # \u6b63\u786e\u505a\u6cd5\n with open(\"file.txt\") as f:\n content = f.read()\n ```\n\n- **C \u98ce\u683c\u5faa\u73af**\uff1a\u672a\u4f7f\u7528\u63a8\u5bfc\u5f0f\uff08Comprehensions\uff09\u6216\u8fed\u4ee3\u5668\n ```python\n # \u9519\u8bef\u505a\u6cd5\n result = []\n for item in items:\n if item.active:\n result.append(item.name)\n\n # \u6b63\u786e\u505a\u6cd5\n result = [item.name for item in items if item.active]\n ```\n\n- **\u4f7f\u7528 isinstance \u68c0\u67e5\u7c7b\u578b**\uff1a\u800c\u975e\u4f7f\u7528 type()\n ```python\n # \u9519\u8bef\u505a\u6cd5\n if type(obj) == str:\n process(obj)\n\n # \u6b63\u786e\u505a\u6cd5\n if isinstance(obj, str):\n process(obj)\n ```\n\n- **\u672a\u4f7f\u7528\u679a\u4e3e (Enum) \u6216\u5b58\u5728\u9b54\u672f\u6570\u5b57 (Magic Numbers)**\n ```python\n # \u9519\u8bef\u505a\u6cd5\n if status == 1:\n process()\n\n # \u6b63\u786e\u505a\u6cd5\n from enum import Enum\n\n class Status(Enum):\n ACTIVE = 1\n INACTIVE = 2\n\n if status == Status.ACTIVE:\n process()\n ```\n\n- **\u5faa\u73af\u4e2d\u7684\u5b57\u7b26\u4e32\u62fc\u63a5**\uff1a\u4f7f\u7528 + \u6784\u5efa\u5b57\u7b26\u4e32\n ```python\n # \u9519\u8bef\u505a\u6cd5\n result = \"\"\n for item in items:\n result += str(item)\n\n # \u6b63\u786e\u505a\u6cd5\n result = \"\".join(str(item) for item in items)\n ```\n\n- **\u53ef\u53d8\u9ed8\u8ba4\u53c2\u6570 (Mutable Default Arguments)**\uff1a\u7ecf\u5178\u7684 Python \u9677\u9631\n ```python\n # \u9519\u8bef\u505a\u6cd5\n def process(items=[]):\n items.append(\"new\")\n return items\n\n # \u6b63\u786e\u505a\u6cd5\n def process(items=None):\n if items is None:\n items = []\n items.append(\"new\")\n return items\n ```\n\n## \u4ee3\u7801\u8d28\u91cf (\u9ad8\u4f18\u5148\u7ea7/HIGH)\n\n- **\u53c2\u6570\u8fc7\u591a**\uff1a\u51fd\u6570\u53c2\u6570\u8d85\u8fc7 5 \u4e2a\n ```python\n # \u9519\u8bef\u505a\u6cd5\n def process_user(name, email, age, address, phone, status):\n pass\n\n # \u6b63\u786e\u505a\u6cd5\n from dataclasses import dataclass\n\n @dataclass\n class UserData:\n name: str\n email: str\n age: int\n address: str\n phone: str\n status: str\n\n def process_user(data: UserData):\n pass\n ```\n\n- **\u8fc7\u957f\u51fd\u6570**\uff1a\u51fd\u6570\u8d85\u8fc7 50 \u884c\n- **\u5d4c\u5957\u8fc7\u6df1**\uff1a\u7f29\u8fdb\u8d85\u8fc7 4 \u5c42\n- **\u4e0a\u5e1d\u7c7b/\u6a21\u5757 (God Classes/Modules)**\uff1a\u627f\u62c5\u4e86\u8fc7\u591a\u804c\u8d23\n- **\u91cd\u590d\u4ee3\u7801**\uff1a\u91cd\u590d\u7684\u6a21\u5f0f\n- **\u9b54\u672f\u6570\u5b57 (Magic Numbers)**\uff1a\u672a\u547d\u540d\u7684\u5e38\u91cf\n ```python\n # \u9519\u8bef\u505a\u6cd5\n if len(data) > 512:\n compress(data)\n\n # \u6b63\u786e\u505a\u6cd5\n MAX_UNCOMPRESSED_SIZE = 512\n\n if len(data) > MAX_UNCOMPRESSED_SIZE:\n compress(data)\n ```\n\n## \u5e76\u53d1 (\u9ad8\u4f18\u5148\u7ea7/HIGH)\n\n- **\u7f3a\u5931\u9501 (Lock)**\uff1a\u5171\u4eab\u72b6\u6001\u672a\u8fdb\u884c\u540c\u6b65\n ```python\n # \u9519\u8bef\u505a\u6cd5\n counter = 0\n\n def increment():\n global counter\n counter += 1 # \u7ade\u6001\u6761\u4ef6 (Race condition)!\n\n # \u6b63\u786e\u505a\u6cd5\n import threading\n\n counter = 0\n lock = threading.Lock()\n\n def increment():\n global counter\n with lock:\n counter += 1\n ```\n\n- **\u5168\u5c40\u89e3\u91ca\u5668\u9501 (GIL) \u5047\u8bbe**\uff1a\u76f2\u76ee\u5047\u8bbe\u7ebf\u7a0b\u5b89\u5168\n- **Async/Await \u6ee5\u7528**\uff1a\u9519\u8bef\u5730\u6df7\u5408\u540c\u6b65\u548c\u5f02\u6b65\u4ee3\u7801\n\n## \u6027\u80fd (\u4e2d\u4f18\u5148\u7ea7/MEDIUM)\n\n- **N+1 \u67e5\u8be2**\uff1a\u5728\u5faa\u73af\u4e2d\u8fdb\u884c\u6570\u636e\u5e93\u67e5\u8be2\n ```python\n # \u9519\u8bef\u505a\u6cd5\n for user in users:\n orders = get_orders(user.id) # N \u6b21\u67e5\u8be2!\n\n # \u6b63\u786e\u505a\u6cd5\n user_ids = [u.id for u in users]\n orders = get_orders_for_users(user_ids) # 1 \u6b21\u67e5\u8be2\n ```\n\n- **\u4f4e\u6548\u7684\u5b57\u7b26\u4e32\u64cd\u4f5c**\n ```python\n # \u9519\u8bef\u505a\u6cd5\n text = \"hello\"\n for i in range(1000):\n text += \" world\" # O(n\u00b2)\n\n # \u6b63\u786e\u505a\u6cd5\n parts = [\"hello\"]\n for i in range(1000):\n parts.append(\" world\")\n text = \"\".join(parts) # O(n)\n ```\n\n- **\u5e03\u5c14\u4e0a\u4e0b\u6587\u4e2d\u7684\u5217\u8868**\uff1a\u4f7f\u7528 len() \u800c\u975e\u771f\u503c\u6027\u68c0\u67e5\n ```python\n # \u9519\u8bef\u505a\u6cd5\n if len(items) > 0:\n process(items)\n\n # \u6b63\u786e\u505a\u6cd5\n if items:\n process(items)\n ```\n\n- **\u4e0d\u5fc5\u8981\u7684\u5217\u8868\u521b\u5efa**\uff1a\u5728\u4e0d\u9700\u8981\u65f6\u4f7f\u7528 list()\n ```python\n # \u9519\u8bef\u505a\u6cd5\n for item in list(dict.keys()):\n process(item)\n\n # \u6b63\u786e\u505a\u6cd5\n for item in dict:\n process(item)\n ```\n\n## \u6700\u4f73\u5b9e\u8df5 (\u4e2d\u4f18\u5148\u7ea7/MEDIUM)\n\n- **PEP 8 \u5408\u89c4\u6027**\uff1a\u4ee3\u7801\u683c\u5f0f\u8fdd\u89c4\n - \u5bfc\u5165\u987a\u5e8f\uff08\u6807\u51c6\u5e93\u3001\u7b2c\u4e09\u65b9\u5e93\u3001\u672c\u5730\u5e93\uff09\n - \u884c\u5bbd\uff08Black \u9ed8\u8ba4\u4e3a 88\uff0cPEP 8 \u4e3a 79\uff09\n - \u547d\u540d\u89c4\u8303\uff08\u51fd\u6570/\u53d8\u91cf\u4f7f\u7528 snake_case\uff0c\u7c7b\u4f7f\u7528 PascalCase\uff09\n - \u8fd0\u7b97\u7b26\u5468\u56f4\u7684\u7a7a\u683c\n\n- **\u6587\u6863\u5b57\u7b26\u4e32 (Docstrings)**\uff1a\u7f3a\u5931\u6216\u683c\u5f0f\u4e0d\u826f\u7684\u6587\u6863\u5b57\u7b26\u4e32\n ```python\n # \u9519\u8bef\u505a\u6cd5\n def process(data):\n return data.strip()\n\n # \u6b63\u786e\u505a\u6cd5\n def process(data: str) -> str:\n \"\"\"\u4ece\u8f93\u5165\u5b57\u7b26\u4e32\u4e2d\u79fb\u9664\u9996\u5c3e\u7a7a\u683c\u3002\n\n Args:\n data: \u8981\u5904\u7406\u7684\u8f93\u5165\u5b57\u7b26\u4e32\u3002\n\n Returns:\n \u79fb\u9664\u7a7a\u683c\u540e\u7684\u5904\u7406\u5b57\u7b26\u4e32\u3002\n \"\"\"\n return data.strip()\n ```\n\n- **\u65e5\u5fd7\u8bb0\u5f55 vs Print**\uff1a\u4f7f\u7528 print() \u8fdb\u884c\u65e5\u5fd7\u8bb0\u5f55\n ```python\n # \u9519\u8bef\u505a\u6cd5\n print(\"Error occurred\")\n\n # \u6b63\u786e\u505a\u6cd5\n import logging\n logger = logging.getLogger(__name__)\n logger.error(\"Error occurred\")\n ```\n\n- **\u76f8\u5bf9\u5bfc\u5165**\uff1a\u5728\u811a\u672c\u4e2d\u4f7f\u7528\u76f8\u5bf9\u5bfc\u5165\n- **\u672a\u4f7f\u7528\u7684\u5bfc\u5165**\uff1a\u6b7b\u4ee3\u7801 (Dead code)\n- **\u7f3a\u5931 `if __name__ == \"__main__\"`**\uff1a\u811a\u672c\u5165\u53e3\u70b9\u672a\u52a0\u4fdd\u62a4\n\n## Python \u7279\u6709\u7684\u53cd\u6a21\u5f0f (Anti-Patterns)\n\n- **`from module import *`**\uff1a\u547d\u540d\u7a7a\u95f4\u6c61\u67d3\n ```python\n # \u9519\u8bef\u505a\u6cd5\n from os.path import *\n\n # \u6b63\u786e\u505a\u6cd5\n from os.path import join, exists\n ```\n\n- **\u672a\u4f7f\u7528 `with` \u8bed\u53e5**\uff1a\u8d44\u6e90\u6cc4\u9732\n- **\u9759\u9ed8\u5f02\u5e38**\uff1a\u7a7a\u7684 `except: pass`\n- **\u4f7f\u7528 == \u4e0e None \u6bd4\u8f83**\n ```python\n # \u9519\u8bef\u505a\u6cd5\n if value == None:\n process()\n\n # \u6b63\u786e\u505a\u6cd5\n if value is None:\n process()\n ```\n\n- **\u672a\u4f7f\u7528 `isinstance` \u8fdb\u884c\u7c7b\u578b\u68c0\u67e5**\uff1a\u4f7f\u7528\u4e86 type()\n- **\u906e\u853d\u5185\u7f6e\u53d8\u91cf (Shadowing Built-ins)**\uff1a\u5c06\u53d8\u91cf\u547d\u540d\u4e3a `list`\u3001`dict`\u3001`str` \u7b49\n ```python\n # \u9519\u8bef\u505a\u6cd5\n list = [1, 2, 3] # \u906e\u853d\u4e86\u5185\u7f6e\u7684 list \u7c7b\u578b\n\n # \u6b63\u786e\u505a\u6cd5\n items = [1, 2, 3]\n ```\n\n## \u5ba1\u67e5\u8f93\u51fa\u683c\u5f0f\n\n\u9488\u5bf9\u6bcf\u4e2a\u95ee\u9898\uff1a\n```text\n[CRITICAL] SQL \u6ce8\u5165\u6f0f\u6d1e\n\u6587\u4ef6: app/routes/user.py:42\n\u95ee\u9898: \u7528\u6237\u8f93\u5165\u76f4\u63a5\u63d2\u5165\u5230\u4e86 SQL \u67e5\u8be2\u4e2d\n\u4fee\u590d\u5efa\u8bae: \u4f7f\u7528\u53c2\u6570\u5316\u67e5\u8be2\n\nquery = f\"SELECT * FROM users WHERE id = {user_id}\" # \u9519\u8bef\nquery = \"SELECT * FROM users WHERE id = %s\" # \u6b63\u786e\ncursor.execute(query, (user_id,))\n```\n\n## \u8bca\u65ad\u547d\u4ee4\n\n\u8fd0\u884c\u4ee5\u4e0b\u68c0\u67e5\uff1a\n```bash\n# \u7c7b\u578b\u68c0\u67e5\nmypy .\n\n# \u4ee3\u7801\u68c0\u67e5 (Linting)\nruff check .\npylint app/\n\n# \u683c\u5f0f\u68c0\u67e5\nblack --check .\nisort --check-only .\n\n# \u5b89\u5168\u626b\u63cf\nbandit -r .\n\n# \u4f9d\u8d56\u9879\u5ba1\u8ba1\npip-audit\nsafety check\n\n# \u6d4b\u8bd5\npytest --cov=app --cov-report=term-missing\n```\n\n## \u6279\u51c6\u6807\u51c6\n\n- **\u6279\u51c6 (Approve)**\uff1a\u65e0\u4e25\u91cd\uff08CRITICAL\uff09\u6216\u9ad8\uff08HIGH\uff09\u4f18\u5148\u7ea7\u95ee\u9898\n- **\u8b66\u544a (Warning)**\uff1a\u4ec5\u5b58\u5728\u4e2d\uff08MEDIUM\uff09\u4f18\u5148\u7ea7\u95ee\u9898\uff08\u53ef\u8c28\u614e\u5408\u5e76\uff09\n- **\u963b\u6b62 (Block)**\uff1a\u53d1\u73b0\u4e25\u91cd\uff08CRITICAL\uff09\u6216\u9ad8\uff08HIGH\uff09\u4f18\u5148\u7ea7\u95ee\u9898\n\n## Python \u7248\u672c\u6ce8\u610f\u4e8b\u9879\n\n- \u68c0\u67e5 `pyproject.toml` \u6216 `setup.py` \u4ee5\u786e\u8ba4 Python \u7248\u672c\u8981\u6c42\n- \u6ce8\u610f\u4ee3\u7801\u662f\u5426\u4f7f\u7528\u4e86\u8f83\u65b0 Python \u7248\u672c\u7684\u7279\u6027\uff08\u7c7b\u578b\u63d0\u793a | 3.5+\uff0cf-strings 3.6+\uff0cwalrus 3.8+\uff0cmatch 3.10+\uff09\n- \u6807\u8bb0\u5df2\u5f03\u7528\u7684\u6807\u51c6\u5e93\u6a21\u5757\n- \u786e\u4fdd\u7c7b\u578b\u63d0\u793a\u4e0e\u6700\u4f4e Python \u7248\u672c\u517c\u5bb9\n\n## \u6846\u67b6\u7279\u5b9a\u68c0\u67e5\n\n### Django\n- **N+1 \u67e5\u8be2**\uff1a\u4f7f\u7528 `select_related` \u548c `prefetch_related`\n- **\u7f3a\u5931\u8fc1\u79fb**\uff1a\u6a21\u578b\u53d8\u66f4\u4f46\u672a\u751f\u6210\u8fc1\u79fb\u6587\u4ef6\n- **\u539f\u751f SQL**\uff1a\u5728 ORM \u53ef\u884c\u7684\u60c5\u51b5\u4e0b\u4f7f\u7528\u4e86 `raw()` \u6216 `execute()`\n- **\u4e8b\u52a1\u7ba1\u7406**\uff1a\u591a\u6b65\u64cd\u4f5c\u7f3a\u5931 `atomic()`\n\n### FastAPI/Flask\n- **CORS \u914d\u7f6e\u9519\u8bef**\uff1a\u8de8\u57df\u9650\u5236\u8fc7\u4e8e\u5bbd\u677e\n- **\u4f9d\u8d56\u6ce8\u5165**\uff1a\u6b63\u786e\u4f7f\u7528 Depends/injection\n- **\u54cd\u5e94\u6a21\u578b**\uff1a\u7f3a\u5931\u6216\u9519\u8bef\u7684\u54cd\u5e94\u6a21\u578b\n- **\u9a8c\u8bc1**\uff1a\u4f7f\u7528 Pydantic \u6a21\u578b\u8fdb\u884c\u8bf7\u6c42\u9a8c\u8bc1\n\n### \u5f02\u6b65 (FastAPI/aiohttp)\n- **\u5f02\u6b65\u51fd\u6570\u4e2d\u7684\u963b\u585e\u8c03\u7528**\uff1a\u5728\u5f02\u6b65\u4e0a\u4e0b\u6587\u4e2d\u4f7f\u7528\u4e86\u540c\u6b65\u5e93\n- **\u7f3a\u5931 await**\uff1a\u5fd8\u8bb0 await \u534f\u7a0b\n- **\u5f02\u6b65\u751f\u6210\u5668**\uff1a\u6b63\u786e\u7684\u5f02\u6b65\u8fed\u4ee3\n\n\u5ba1\u67e5\u65f6\u8bf7\u601d\u8003\uff1a\u201c\u8fd9\u6bb5\u4ee3\u7801\u80fd\u901a\u8fc7\u9876\u7ea7 Python \u56e2\u961f\u6216\u5f00\u6e90\u9879\u76ee\u7684\u5ba1\u67e5\u5417\uff1f\u201d\n" + }, + "/Users/Library/Applications/xx/code/github/everything-claude-code-zh/commands/instinct-export.md": { + "md5": "17b1afc48ce7380005509993a87c7309", + "content": "---\nname: instinct-export\ndescription: \u5bfc\u51fa\u76f4\u89c9\uff08Instincts\uff09\u4ee5\u4fbf\u4e0e\u56e2\u961f\u6210\u5458\u6216\u5176\u4ed6\u9879\u76ee\u5171\u4eab\ncommand: /instinct-export\n---\n\n# \u76f4\u89c9\u5bfc\u51fa\uff08Instinct Export\uff09\u547d\u4ee4\n\n\u5c06\u76f4\u89c9\uff08Instincts\uff09\u5bfc\u51fa\u4e3a\u53ef\u5171\u4eab\u7684\u683c\u5f0f\u3002\u975e\u5e38\u9002\u7528\u4e8e\uff1a\n- \u4e0e\u56e2\u961f\u6210\u5458\u5171\u4eab\n- \u8fc1\u79fb\u5230\u65b0\u673a\u5668\n- \u4e3a\u9879\u76ee\u89c4\u8303\uff08Conventions\uff09\u8d21\u732e\u5185\u5bb9\n\n## \u7528\u6cd5\n\n```\n/instinct-export # \u5bfc\u51fa\u6240\u6709\u4e2a\u4eba\u76f4\u89c9\n/instinct-export --domain testing # \u4ec5\u5bfc\u51fa\u6d4b\u8bd5\u76f8\u5173\u7684\u76f4\u89c9\n/instinct-export --min-confidence 0.7 # \u4ec5\u5bfc\u51fa\u9ad8\u7f6e\u4fe1\u5ea6\u7684\u76f4\u89c9\n/instinct-export --output team-instincts.yaml\n```\n\n## \u6267\u884c\u6d41\u7a0b\n\n1. \u4ece `~/.claude/homunculus/instincts/personal/` \u8bfb\u53d6\u76f4\u89c9\n2. \u6839\u636e\u6807\u5fd7\u4f4d\uff08Flags\uff09\u8fdb\u884c\u8fc7\u6ee4\n3. \u8131\u654f\u5904\u7406\uff08\u5265\u79bb\u654f\u611f\u4fe1\u606f\uff09\uff1a\n - \u79fb\u9664\u4f1a\u8bdd ID\uff08Session IDs\uff09\n - \u79fb\u9664\u6587\u4ef6\u8def\u5f84\uff08\u4ec5\u4fdd\u7559\u6a21\u5f0f\u5339\u914d\u7b26 Pattern\uff09\n - \u79fb\u9664\u65e9\u4e8e\u201c\u4e0a\u5468\u201d\u7684\u65f6\u95f4\u6233\n4. \u751f\u6210\u5bfc\u51fa\u6587\u4ef6\n\n## \u8f93\u51fa\u683c\u5f0f\n\n\u521b\u5efa\u4e00\u4e2a YAML \u6587\u4ef6\uff1a\n\n```yaml\n# Instincts Export\n# Generated: 2025-01-22\n# Source: personal\n# Count: 12 instincts\n\nversion: \"2.0\"\nexported_by: \"continuous-learning-v2\"\nexport_date: \"2025-01-22T10:30:00Z\"\n\ninstincts:\n - id: prefer-functional-style\n trigger: \"when writing new functions\"\n action: \"Use functional patterns over classes\"\n confidence: 0.8\n domain: code-style\n observations: 8\n\n - id: test-first-workflow\n trigger: \"when adding new functionality\"\n action: \"Write test first, then implementation\"\n confidence: 0.9\n domain: testing\n observations: 12\n\n - id: grep-before-edit\n trigger: \"when modifying code\"\n action: \"Search with Grep, confirm with Read, then Edit\"\n confidence: 0.7\n domain: workflow\n observations: 6\n```\n\n## \u9690\u79c1\u8003\u91cf\n\n\u5bfc\u51fa\u5185\u5bb9\u5305\u62ec\uff1a\n- \u2705 \u89e6\u53d1\u6a21\u5f0f\uff08Trigger patterns\uff09\n- \u2705 \u52a8\u4f5c\uff08Actions\uff09\n- \u2705 \u7f6e\u4fe1\u5ea6\u5206\u6570\uff08Confidence scores\uff09\n- \u2705 \u57df\uff08Domains\uff09\n- \u2705 \u89c2\u5bdf\u6b21\u6570\uff08Observation counts\uff09\n\n\u5bfc\u51fa\u5185\u5bb9 **\u4e0d\u5305\u62ec**\uff1a\n- \u274c \u5b9e\u9645\u4ee3\u7801\u7247\u6bb5\n- \u274c \u6587\u4ef6\u8def\u5f84\n- \u274c \u4f1a\u8bdd\u8f6c\u5f55\u6587\u672c\n- \u274c \u4e2a\u4eba\u8eab\u4efd\u6807\u8bc6\u7b26\n\n## \u6807\u5fd7\u4f4d\uff08Flags\uff09\n\n- `--domain `: \u4ec5\u5bfc\u51fa\u6307\u5b9a\u7684\u57df\uff08Domain\uff09\n- `--min-confidence `: \u6700\u4f4e\u7f6e\u4fe1\u5ea6\u9608\u503c\uff08\u9ed8\u8ba4\u503c\uff1a0.3\uff09\n- `--output `: \u8f93\u51fa\u6587\u4ef6\u8def\u5f84\uff08\u9ed8\u8ba4\u503c\uff1ainstincts-export-YYYYMMDD.yaml\uff09\n- `--format `: \u8f93\u51fa\u683c\u5f0f\uff08\u9ed8\u8ba4\u503c\uff1ayaml\uff09\n- `--include-evidence`: \u5305\u542b\u8bc1\u636e\u6587\u672c\uff08\u9ed8\u8ba4\u503c\uff1a\u6392\u9664\uff09\n" + }, + "/Users/Library/Applications/xx/code/github/everything-claude-code-zh/commands/instinct-import.md": { + "md5": "fbe90485996baa813e09521ec8f7b6ba", + "content": "---\nname: instinct-import\ndescription: \u4ece\u56e2\u961f\u6210\u5458\u3001\u6280\u80fd\u521b\u5efa\u8005\uff08Skill Creator\uff09\u6216\u5176\u4ed6\u6765\u6e90\u5bfc\u5165\u76f4\u89c9\uff08Instincts\uff09\ncommand: true\n---\n\n# \u76f4\u89c9\u5bfc\u5165\u547d\u4ee4\uff08Instinct Import Command\uff09\n\n## \u5b9e\u73b0\n\n\u4f7f\u7528\u63d2\u4ef6\u6839\u8def\u5f84\u8fd0\u884c\u76f4\u89c9 CLI\uff1a\n\n```bash\npython3 \"${CLAUDE_PLUGIN_ROOT}/skills/continuous-learning-v2/scripts/instinct-cli.py\" import [--dry-run] [--force] [--min-confidence 0.7]\n```\n\n\u5982\u679c\u672a\u8bbe\u7f6e `CLAUDE_PLUGIN_ROOT`\uff08\u624b\u52a8\u5b89\u88c5\uff09\uff1a\n\n```bash\npython3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py import \n```\n\n\u53ef\u4ee5\u4ece\u4ee5\u4e0b\u6765\u6e90\u5bfc\u5165\u76f4\u89c9\uff1a\n- \u56e2\u961f\u6210\u5458\u7684\u5bfc\u51fa\u6587\u4ef6\n- \u6280\u80fd\u521b\u5efa\u8005\uff08Skill Creator\uff09\uff08\u4ed3\u5e93\u5206\u6790\uff09\n- \u793e\u533a\u96c6\u5408\n- \u4ee5\u524d\u7684\u673a\u5668\u5907\u4efd\n\n## \u7528\u6cd5\n\n```\n/instinct-import team-instincts.yaml\n/instinct-import https://github.com/org/repo/instincts.yaml\n/instinct-import --from-skill-creator acme/webapp\n```\n\n## \u6838\u5fc3\u6d41\u7a0b\n\n1. \u83b7\u53d6\u76f4\u89c9\u6587\u4ef6\uff08\u672c\u5730\u8def\u5f84\u6216 URL\uff09\n2. \u89e3\u6790\u5e76\u9a8c\u8bc1\u683c\u5f0f\n3. \u68c0\u67e5\u4e0e\u73b0\u6709\u76f4\u89c9\u662f\u5426\u91cd\u590d\n4. \u5408\u5e76\u6216\u6dfb\u52a0\u65b0\u76f4\u89c9\n5. \u4fdd\u5b58\u81f3 `~/.claude/homunculus/instincts/inherited/`\n\n## \u5bfc\u5165\u8fc7\u7a0b\u793a\u4f8b\n\n```\n\ud83d\udce5 \u6b63\u5728\u4ece\u4ee5\u4e0b\u8def\u5f84\u5bfc\u5165\u76f4\u89c9\uff1ateam-instincts.yaml\n================================================\n\n\u53d1\u73b0 12 \u6761\u5f85\u5bfc\u5165\u7684\u76f4\u89c9\u3002\n\n\u6b63\u5728\u5206\u6790\u51b2\u7a81...\n\n## \u65b0\u76f4\u89c9 (8)\n\u4ee5\u4e0b\u5185\u5bb9\u5c06\u88ab\u6dfb\u52a0\uff1a\n \u2713 use-zod-validation (\u7f6e\u4fe1\u5ea6: 0.7)\n \u2713 prefer-named-exports (\u7f6e\u4fe1\u5ea6: 0.65)\n \u2713 test-async-functions (\u7f6e\u4fe1\u5ea6: 0.8)\n ...\n\n## \u91cd\u590d\u76f4\u89c9 (3)\n\u5df2\u5b58\u5728\u7c7b\u4f3c\u7684\u76f4\u89c9\uff1a\n \u26a0\ufe0f prefer-functional-style\n \u672c\u5730: 0.8 \u7f6e\u4fe1\u5ea6, 12 \u6b21\u89c2\u5bdf\n \u5bfc\u5165: 0.7 \u7f6e\u4fe1\u5ea6\n \u2192 \u4fdd\u7559\u672c\u5730\u7248\u672c\uff08\u7f6e\u4fe1\u5ea6\u66f4\u9ad8\uff09\n\n \u26a0\ufe0f test-first-workflow\n \u672c\u5730: 0.75 \u7f6e\u4fe1\u5ea6\n \u5bfc\u5165: 0.9 \u7f6e\u4fe1\u5ea6\n \u2192 \u66f4\u65b0\u4e3a\u5bfc\u5165\u7248\u672c\uff08\u7f6e\u4fe1\u5ea6\u66f4\u9ad8\uff09\n\n## \u51b2\u7a81\u76f4\u89c9 (1)\n\u8fd9\u4e9b\u76f4\u89c9\u4e0e\u672c\u5730\u76f4\u89c9\u51b2\u7a81\uff1a\n \u274c use-classes-for-services\n \u51b2\u7a81\u9879\uff1aavoid-classes\n \u2192 \u8df3\u8fc7\uff08\u9700\u8981\u624b\u52a8\u89e3\u51b3\uff09\n\n---\n\u5bfc\u5165 8 \u6761\u65b0\u76f4\u89c9\uff0c\u66f4\u65b0 1 \u6761\uff0c\u8df3\u8fc7 3 \u6761\uff1f\n```\n\n## \u5408\u5e76\u7b56\u7565\uff08Merge Strategies\uff09\n\n### \u5904\u7406\u91cd\u590d\u9879\n\u5f53\u5bfc\u5165\u7684\u76f4\u89c9\u4e0e\u73b0\u6709\u76f4\u89c9\u5339\u914d\u65f6\uff1a\n- **\u9ad8\u7f6e\u4fe1\u5ea6\u4f18\u5148**\uff1a\u4fdd\u7559\u7f6e\u4fe1\u5ea6\u8f83\u9ad8\u7684\u7248\u672c\n- **\u5408\u5e76\u8bc1\u636e**\uff1a\u5408\u5e76\u89c2\u5bdf\u8ba1\u6570\uff08observation counts\uff09\n- **\u66f4\u65b0\u65f6\u95f4\u6233**\uff1a\u6807\u8bb0\u4e3a\u6700\u8fd1\u5df2\u9a8c\u8bc1\n\n### \u5904\u7406\u51b2\u7a81\n\u5f53\u5bfc\u5165\u7684\u76f4\u89c9\u4e0e\u73b0\u6709\u76f4\u89c9\u76f8\u77db\u76fe\u65f6\uff1a\n- **\u9ed8\u8ba4\u8df3\u8fc7**\uff1a\u4e0d\u5bfc\u5165\u51b2\u7a81\u7684\u76f4\u89c9\n- **\u6807\u8bb0\u5f85\u8bc4\u5ba1**\uff1a\u5c06\u4e24\u8005\u90fd\u6807\u8bb0\u4e3a\u9700\u8981\u5173\u6ce8\n- **\u624b\u52a8\u89e3\u51b3**\uff1a\u7531\u7528\u6237\u51b3\u5b9a\u4fdd\u7559\u54ea\u4e00\u4e2a\n\n## \u6765\u6e90\u8ffd\u8e2a\n\n\u5bfc\u5165\u7684\u76f4\u89c9\u4f1a\u5e26\u6709\u4ee5\u4e0b\u6807\u8bb0\uff1a\n```yaml\nsource: \"inherited\"\nimported_from: \"team-instincts.yaml\"\nimported_at: \"2025-01-22T10:30:00Z\"\noriginal_source: \"session-observation\" # \u6216 \"repo-analysis\"\n```\n\n## \u6280\u80fd\u521b\u5efa\u8005\uff08Skill Creator\uff09\u96c6\u6210\n\n\u4ece\u6280\u80fd\u521b\u5efa\u8005\u5bfc\u5165\u65f6\uff1a\n\n```\n/instinct-import --from-skill-creator acme/webapp\n```\n\n\u8fd9\u5c06\u83b7\u53d6\u901a\u8fc7\u4ed3\u5e93\u5206\u6790\u751f\u6210\u7684\u76f4\u89c9\uff1a\n- \u6765\u6e90\uff1a`repo-analysis`\n- \u521d\u59cb\u7f6e\u4fe1\u5ea6\u8f83\u9ad8 (0.7+)\n- \u94fe\u63a5\u5230\u6e90\u4ed3\u5e93\n\n## \u53c2\u6570\u9009\u9879\uff08Flags\uff09\n\n- `--dry-run`\uff1a\u9884\u89c8\u800c\u4e0d\u6267\u884c\u5bfc\u5165\n- `--force`\uff1a\u5373\u4f7f\u5b58\u5728\u51b2\u7a81\u4e5f\u6267\u884c\u5bfc\u5165\n- `--merge-strategy `\uff1a\u5982\u4f55\u5904\u7406\u91cd\u590d\u9879\n- `--from-skill-creator `\uff1a\u4ece\u6280\u80fd\u521b\u5efa\u8005\u5206\u6790\u4e2d\u5bfc\u5165\n- `--min-confidence `\uff1a\u4ec5\u5bfc\u5165\u9ad8\u4e8e\u9608\u503c\u7684\u76f4\u89c9\n\n## \u8f93\u51fa\n\n\u5bfc\u5165\u5b8c\u6210\u540e\uff1a\n```\n\u2705 \u5bfc\u5165\u5b8c\u6210\uff01\n\n\u6dfb\u52a0\uff1a8 \u6761\u76f4\u89c9\n\u66f4\u65b0\uff1a1 \u6761\u76f4\u89c9\n\u8df3\u8fc7\uff1a3 \u6761\u76f4\u89c9 (2 \u6761\u91cd\u590d\uff0c1 \u6761\u51b2\u7a81)\n\n\u65b0\u76f4\u89c9\u5df2\u4fdd\u5b58\u81f3\uff1a~/.claude/homunculus/instincts/inherited/\n\n\u8fd0\u884c /instinct-status \u4ee5\u67e5\u770b\u6240\u6709\u76f4\u89c9\u3002\n```\n" + }, + "/Users/Library/Applications/xx/code/github/everything-claude-code-zh/commands/instinct-status.md": { + "md5": "dac216bb6e1e3705bfbf0f39b88d6049", + "content": "---\nname: instinct-status\ndescription: \u663e\u793a\u6240\u6709\u5df2\u5b66\u4e60\u7684\u672c\u80fd\u53ca\u5176\u7f6e\u4fe1\u5ea6\ncommand: true\n---\n\n# \u672c\u80fd\u72b6\u6001\u67e5\u8be2\u547d\u4ee4\uff08Instinct Status Command\uff09\n\n\u663e\u793a\u6240\u6709\u5df2\u5b66\u4e60\u7684\u672c\u80fd\uff08Instincts\uff09\u53ca\u5176\u7f6e\u4fe1\u5ea6\u5206\u6570\uff0c\u5e76\u6309\u9886\u57df\uff08Domain\uff09\u8fdb\u884c\u5206\u7ec4\u3002\n\n## \u5b9e\u73b0\u65b9\u5f0f\n\n\u4f7f\u7528\u63d2\u4ef6\u6839\u8def\u5f84\u8fd0\u884c\u672c\u80fd CLI\uff1a\n\n```bash\npython3 \"${CLAUDE_PLUGIN_ROOT}/skills/continuous-learning-v2/scripts/instinct-cli.py\" status\n```\n\n\u5982\u679c\u672a\u8bbe\u7f6e `CLAUDE_PLUGIN_ROOT`\uff08\u624b\u52a8\u5b89\u88c5\uff09\uff0c\u8bf7\u4f7f\u7528\uff1a\n\n```bash\npython3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py status\n```\n\n## \u7528\u6cd5\n\n```\n/instinct-status\n/instinct-status --domain code-style\n/instinct-status --low-confidence\n```\n\n## \u6267\u884c\u903b\u8f91\n\n1. \u4ece `~/.claude/homunculus/instincts/personal/` \u8bfb\u53d6\u6240\u6709\u4e2a\u4eba\u672c\u80fd\u6587\u4ef6\u3002\n2. \u4ece `~/.claude/homunculus/instincts/inherited/` \u8bfb\u53d6\u7ee7\u627f\u7684\u672c\u80fd\u3002\n3. \u6309\u9886\u57df\u5206\u7ec4\u663e\u793a\uff0c\u5e76\u9644\u5e26\u7f6e\u4fe1\u5ea6\u8fdb\u5ea6\u6761\u3002\n\n## \u8f93\u51fa\u683c\u5f0f\n\n```\n\ud83d\udcca \u672c\u80fd\u72b6\u6001 (Instinct Status)\n==================\n\n## \u4ee3\u7801\u98ce\u683c (Code Style) (4 \u6761\u672c\u80fd)\n\n### prefer-functional-style\n\u89e6\u53d1\u5668 (Trigger)\uff1a\u7f16\u5199\u65b0\u51fd\u6570\u65f6\n\u52a8\u4f5c (Action)\uff1a\u4f18\u5148\u4f7f\u7528\u51fd\u6570\u5f0f\u6a21\u5f0f\u800c\u975e\u7c7b\n\u7f6e\u4fe1\u5ea6 (Confidence)\uff1a\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2591\u2591 80%\n\u6765\u6e90 (Source)\uff1a\u4f1a\u8bdd\u89c2\u5bdf (session-observation) | \u6700\u8fd1\u66f4\u65b0\uff1a2025-01-22\n\n### use-path-aliases\n\u89e6\u53d1\u5668 (Trigger)\uff1a\u5bfc\u5165\u6a21\u5757\u65f6\n\u52a8\u4f5c (Action)\uff1a\u4f7f\u7528 @/ \u8def\u5f84\u522b\u540d\u800c\u975e\u76f8\u5bf9\u5bfc\u5165\n\u7f6e\u4fe1\u5ea6 (Confidence)\uff1a\u2588\u2588\u2588\u2588\u2588\u2588\u2591\u2591\u2591\u2591 60%\n\u6765\u6e90 (Source)\uff1a\u4ed3\u5e93\u5206\u6790 (repo-analysis) (github.com/acme/webapp)\n\n## \u6d4b\u8bd5 (Testing) (2 \u6761\u672c\u80fd)\n\n### test-first-workflow\n\u89e6\u53d1\u5668 (Trigger)\uff1a\u6dfb\u52a0\u65b0\u529f\u80fd\u65f6\n\u52a8\u4f5c (Action)\uff1a\u5148\u5199\u6d4b\u8bd5\uff0c\u518d\u5199\u5b9e\u73b0\n\u7f6e\u4fe1\u5ea6 (Confidence)\uff1a\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2591 90%\n\u6765\u6e90 (Source)\uff1a\u4f1a\u8bdd\u89c2\u5bdf (session-observation)\n\n## \u5de5\u4f5c\u6d41 (Workflow) (3 \u6761\u672c\u80fd)\n\n### grep-before-edit\n\u89e6\u53d1\u5668 (Trigger)\uff1a\u4fee\u6539\u4ee3\u7801\u65f6\n\u52a8\u4f5c (Action)\uff1a\u5148\u7528 Grep \u641c\u7d22\uff0c\u518d\u7528 Read \u786e\u8ba4\uff0c\u6700\u540e Edit \u7f16\u8f91\n\u7f6e\u4fe1\u5ea6 (Confidence)\uff1a\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2591\u2591\u2591 70%\n\u6765\u6e90 (Source)\uff1a\u4f1a\u8bdd\u89c2\u5bdf (session-observation)\n\n---\n\u603b\u8ba1\uff1a9 \u6761\u672c\u80fd (4 \u6761\u4e2a\u4eba, 5 \u6761\u7ee7\u627f)\n\u89c2\u5bdf\u5668 (Observer)\uff1a\u8fd0\u884c\u4e2d (\u6700\u8fd1\u5206\u6790\uff1a5 \u5206\u949f\u524d)\n```\n\n## \u53c2\u6570 (Flags)\n\n- `--domain `: \u6309\u9886\u57df\u7b5b\u9009\uff08\u5982 code-style, testing, git \u7b49\uff09\n- `--low-confidence`: \u4ec5\u663e\u793a\u7f6e\u4fe1\u5ea6 < 0.5 \u7684\u672c\u80fd\n- `--high-confidence`: \u4ec5\u663e\u793a\u7f6e\u4fe1\u5ea6 >= 0.7 \u7684\u672c\u80fd\n- `--source `: \u6309\u6765\u6e90\u7b5b\u9009\uff08session-observation, repo-analysis, inherited\uff09\n- `--json`: \u4ee5 JSON \u683c\u5f0f\u8f93\u51fa\uff0c\u4fbf\u4e8e\u7a0b\u5e8f\u5316\u8c03\u7528\n" + }, + "/Users/Library/Applications/xx/code/github/everything-claude-code-zh/commands/evolve.md": { + "md5": "f017b3e4e55aa805913fe81ade6ae2cf", + "content": "---\nname: evolve\ndescription: \u5c06\u76f8\u5173\u7684\u76f4\u89c9\uff08Instincts\uff09\u805a\u7c7b\u4e3a\u6280\u80fd\uff08Skills\uff09\u3001\u547d\u4ee4\uff08Commands\uff09\u6216\u667a\u80fd\u4f53\uff08Agents\uff09\ncommand: true\n---\n\n# Evolve \u547d\u4ee4\n\n## \u5b9e\u73b0 (Implementation)\n\n\u4f7f\u7528\u63d2\u4ef6\u6839\u8def\u5f84\u8fd0\u884c\u76f4\u89c9\uff08Instinct\uff09\u547d\u4ee4\u884c\u754c\u9762\uff08CLI\uff09\uff1a\n\n```bash\npython3 \"${CLAUDE_PLUGIN_ROOT}/skills/continuous-learning-v2/scripts/instinct-cli.py\" evolve [--generate]\n```\n\n\u6216\u8005\u5982\u679c\u672a\u8bbe\u7f6e `CLAUDE_PLUGIN_ROOT`\uff08\u624b\u52a8\u5b89\u88c5\uff09\uff1a\n\n```bash\npython3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py evolve [--generate]\n```\n\n\u5206\u6790\u76f4\u89c9\uff08Instincts\uff09\u5e76\u5c06\u76f8\u5173\u7684\u76f4\u89c9\u805a\u7c7b\u4e3a\u66f4\u9ad8\u7ea7\u7684\u7ed3\u6784\uff1a\n- **\u547d\u4ee4\uff08Commands\uff09**\uff1a\u5f53\u76f4\u89c9\u63cf\u8ff0\u7528\u6237\u8c03\u7528\u7684\u64cd\u4f5c\u65f6\n- **\u6280\u80fd\uff08Skills\uff09**\uff1a\u5f53\u76f4\u89c9\u63cf\u8ff0\u81ea\u52a8\u89e6\u53d1\u7684\u884c\u4e3a\u65f6\n- **\u667a\u80fd\u4f53\uff08Agents\uff09**\uff1a\u5f53\u76f4\u89c9\u63cf\u8ff0\u590d\u6742\u7684\u3001\u591a\u6b65\u9aa4\u7684\u6d41\u7a0b\u65f6\n\n## \u7528\u6cd5 (Usage)\n\n```\n/evolve # \u5206\u6790\u6240\u6709\u76f4\u89c9\u5e76\u5efa\u8bae\u6f14\u8fdb\u65b9\u6848\n/evolve --domain testing # \u4ec5\u6f14\u8fdb\u6d4b\u8bd5\u9886\u57df\uff08testing domain\uff09\u4e2d\u7684\u76f4\u89c9\n/evolve --dry-run # \u663e\u793a\u5c06\u8981\u521b\u5efa\u7684\u5185\u5bb9\u800c\u4e0d\u5b9e\u9645\u521b\u5efa\n/evolve --threshold 5 # \u8981\u6c42\u81f3\u5c11\u6709 5 \u4e2a\u4ee5\u4e0a\u7684\u76f8\u5173\u76f4\u89c9\u624d\u8fdb\u884c\u805a\u7c7b\n```\n\n## \u6f14\u8fdb\u89c4\u5219 (Evolution Rules)\n\n### \u2192 \u547d\u4ee4 (Command)\uff08\u7528\u6237\u8c03\u7528\uff09\n\u5f53\u76f4\u89c9\u63cf\u8ff0\u7528\u6237\u4f1a\u660e\u786e\u8bf7\u6c42\u7684\u64cd\u4f5c\u65f6\uff1a\n- \u591a\u4e2a\u5173\u4e8e\u201c\u5f53\u7528\u6237\u8981\u6c42...\u201d\u7684\u76f4\u89c9\n- \u5e26\u6709\u201c\u5f53\u521b\u5efa\u65b0\u7684 X \u65f6\u201d\u7b49\u89e6\u53d1\u5668\u7684\u76f4\u89c9\n- \u9075\u5faa\u53ef\u91cd\u590d\u5e8f\u5217\u7684\u76f4\u89c9\n\n\u793a\u4f8b\uff1a\n- `new-table-step1`: \"when adding a database table, create migration\"\n- `new-table-step2`: \"when adding a database table, update schema\"\n- `new-table-step3`: \"when adding a database table, regenerate types\"\n\n\u2192 \u521b\u5efa\uff1a`/new-table` \u547d\u4ee4\n\n### \u2192 \u6280\u80fd (Skill)\uff08\u81ea\u52a8\u89e6\u53d1\uff09\n\u5f53\u76f4\u89c9\u63cf\u8ff0\u5e94\u8be5\u81ea\u52a8\u53d1\u751f\u7684\u884c\u4e3a\u65f6\uff1a\n- \u6a21\u5f0f\u5339\u914d\u89e6\u53d1\u5668\n- \u9519\u8bef\u5904\u7406\u54cd\u5e94\n- \u4ee3\u7801\u98ce\u683c\u5f3a\u5236\u6267\u884c\n\n\u793a\u4f8b\uff1a\n- `prefer-functional`: \"when writing functions, prefer functional style\"\n- `use-immutable`: \"when modifying state, use immutable patterns\"\n- `avoid-classes`: \"when designing modules, avoid class-based design\"\n\n\u2192 \u521b\u5efa\uff1a`functional-patterns` \u6280\u80fd\uff08Skill\uff09\n\n### \u2192 \u667a\u80fd\u4f53 (Agent)\uff08\u9700\u8981\u6df1\u5ea6/\u9694\u79bb\uff09\n\u5f53\u76f4\u89c9\u63cf\u8ff0\u590d\u6742\u7684\u3001\u591a\u6b65\u9aa4\u7684\u6d41\u7a0b\uff0c\u4e14\u53d7\u76ca\u4e8e\u9694\u79bb\u73af\u5883\u65f6\uff1a\n- \u8c03\u8bd5\u5de5\u4f5c\u6d41\uff08Workflow\uff09\n- \u91cd\u6784\u5e8f\u5217\n- \u7814\u7a76\u4efb\u52a1\n\n\u793a\u4f8b\uff1a\n- `debug-step1`: \"when debugging, first check logs\"\n- `debug-step2`: \"when debugging, isolate the failing component\"\n- `debug-step3`: \"when debugging, create minimal reproduction\"\n- `debug-step4`: \"when debugging, verify fix with test\"\n\n\u2192 \u521b\u5efa\uff1a`debugger` \u667a\u80fd\u4f53\uff08Agent\uff09\n\n## \u64cd\u4f5c\u6b65\u9aa4 (What to Do)\n\n1. \u4ece `~/.claude/homunculus/instincts/` \u8bfb\u53d6\u6240\u6709\u76f4\u89c9\uff08Instincts\uff09\n2. \u6309\u4ee5\u4e0b\u7ef4\u5ea6\u5bf9\u76f4\u89c9\u8fdb\u884c\u5206\u7ec4\uff1a\n - \u9886\u57df\uff08Domain\uff09\u76f8\u4f3c\u6027\n - \u89e6\u53d1\u6a21\u5f0f\u91cd\u5408\u5ea6\n - \u64cd\u4f5c\u5e8f\u5217\u5173\u8054\u6027\n3. \u5bf9\u4e8e\u6bcf\u4e2a\u5305\u542b 3 \u4e2a\u53ca\u4ee5\u4e0a\u76f8\u5173\u76f4\u89c9\u7684\u805a\u7c7b\uff1a\n - \u786e\u5b9a\u6f14\u8fdb\u7c7b\u578b\uff08\u547d\u4ee4/\u6280\u80fd/\u667a\u80fd\u4f53\uff09\n - \u751f\u6210\u76f8\u5e94\u7684\u6587\u4ef6\n - \u4fdd\u5b58\u81f3 `~/.claude/homunculus/evolved/{commands,skills,agents}/`\n4. \u5c06\u6f14\u8fdb\u540e\u7684\u7ed3\u6784\u94fe\u63a5\u56de\u539f\u59cb\u76f4\u89c9\n\n## \u8f93\u51fa\u683c\u5f0f (Output Format)\n\n```\n\ud83e\uddec \u6f14\u8fdb\u5206\u6790 (Evolve Analysis)\n==================\n\n\u53d1\u73b0 3 \u4e2a\u5df2\u51c6\u5907\u597d\u6f14\u8fdb\u7684\u805a\u7c7b\uff1a\n\n## \u805a\u7c7b 1\uff1a\u6570\u636e\u5e93\u8fc1\u79fb\u5de5\u4f5c\u6d41 (Database Migration Workflow)\n\u76f4\u89c9 (Instincts): new-table-migration, update-schema, regenerate-types\n\u7c7b\u578b: \u547d\u4ee4 (Command)\n\u7f6e\u4fe1\u5ea6: 85% (\u57fa\u4e8e 12 \u6b21\u89c2\u5bdf)\n\n\u5c06\u521b\u5efa: /new-table \u547d\u4ee4\n\u6587\u4ef6:\n - ~/.claude/homunculus/evolved/commands/new-table.md\n\n## \u805a\u7c7b 2\uff1a\u51fd\u6570\u5f0f\u4ee3\u7801\u98ce\u683c (Functional Code Style)\n\u76f4\u89c9 (Instincts): prefer-functional, use-immutable, avoid-classes, pure-functions\n\u7c7b\u578b: \u6280\u80fd (Skill)\n\u7f6e\u4fe1\u5ea6: 78% (\u57fa\u4e8e 8 \u6b21\u89c2\u5bdf)\n\n\u5c06\u521b\u5efa: functional-patterns \u6280\u80fd (Skill)\n\u6587\u4ef6:\n - ~/.claude/homunculus/evolved/skills/functional-patterns.md\n\n## \u805a\u7c7b 3\uff1a\u8c03\u8bd5\u6d41\u7a0b (Debugging Process)\n\u76f4\u89c9 (Instincts): debug-check-logs, debug-isolate, debug-reproduce, debug-verify\n\u7c7b\u578b: \u667a\u80fd\u4f53 (Agent)\n\u7f6e\u4fe1\u5ea6: 72% (\u57fa\u4e8e 6 \u6b21\u89c2\u5bdf)\n\n\u5c06\u521b\u5efa: debugger \u667a\u80fd\u4f53 (Agent)\n\u6587\u4ef6:\n - ~/.claude/homunculus/evolved/agents/debugger.md\n\n---\n\u8fd0\u884c `/evolve --execute` \u6765\u521b\u5efa\u8fd9\u4e9b\u6587\u4ef6\u3002\n```\n\n## \u53c2\u6570\u6807\u5fd7 (Flags)\n\n- `--execute`: \u5b9e\u9645\u521b\u5efa\u6f14\u8fdb\u540e\u7684\u7ed3\u6784\uff08\u9ed8\u8ba4\u4e3a\u9884\u89c8\uff09\n- `--dry-run`: \u9884\u89c8\u800c\u4e0d\u521b\u5efa\n- `--domain `: \u4ec5\u6f14\u8fdb\u6307\u5b9a\u9886\u57df\uff08Domain\uff09\u4e2d\u7684\u76f4\u89c9\n- `--threshold `: \u5f62\u6210\u805a\u7c7b\u6240\u9700\u7684\u6700\u5c0f\u76f4\u89c9\u6570\u91cf\uff08\u9ed8\u8ba4\u503c\uff1a3\uff09\n- `--type `: \u4ec5\u521b\u5efa\u6307\u5b9a\u7c7b\u578b\n\n## \u751f\u6210\u7684\u6587\u4ef6\u683c\u5f0f (Generated File Format)\n\n### \u547d\u4ee4 (Command)\n```markdown\n---\nname: new-table\ndescription: Create a new database table with migration, schema update, and type generation\ncommand: /new-table\nevolved_from:\n - new-table-migration\n - update-schema\n - regenerate-types\n---\n\n# New Table \u547d\u4ee4\n\n[\u57fa\u4e8e\u805a\u7c7b\u76f4\u89c9\u751f\u6210\u7684\u6b63\u6587\u5185\u5bb9]\n\n## \u6b65\u9aa4\n1. ...\n2. ...\n```\n\n### \u6280\u80fd (Skill)\n```markdown\n---\nname: functional-patterns\ndescription: Enforce functional programming patterns\nevolved_from:\n - prefer-functional\n - use-immutable\n - avoid-classes\n---\n\n# Functional Patterns \u6280\u80fd (Skill)\n\n[\u57fa\u4e8e\u805a\u7c7b\u76f4\u89c9\u751f\u6210\u7684\u6b63\u6587\u5185\u5bb9]\n```\n\n### \u667a\u80fd\u4f53 (Agent)\n```markdown\n---\nname: debugger\ndescription: Systematic debugging agent\nmodel: sonnet\nevolved_from:\n - debug-check-logs\n - debug-isolate\n - debug-reproduce\n---\n\n# Debugger \u667a\u80fd\u4f53 (Agent)\n\n[\u57fa\u4e8e\u805a\u7c7b\u76f4\u89c9\u751f\u6210\u7684\u6b63\u6587\u5185\u5bb9]\n```\n" + }, + "/Users/Library/Applications/xx/code/github/everything-claude-code-zh/commands/python-review.md": { + "md5": "3ea7910663111fb2e8e3e99936261207", + "content": "---\ndescription: \u9488\u5bf9 PEP 8 \u6807\u51c6\u3001\u7c7b\u578b\u63d0\u793a\u3001\u5b89\u5168\u6027\u53ca Pythonic \u60ef\u7528\u5199\u6cd5\u7684 Python \u4ee3\u7801\u5168\u9762\u5ba1\u67e5\u3002\u8c03\u7528 python-reviewer \u667a\u80fd\u4f53\uff08Agent\uff09\u3002\n---\n\n# Python \u4ee3\u7801\u5ba1\u67e5 (Python Code Review)\n\n\u6b64\u547d\u4ee4\u8c03\u7528 **python-reviewer** \u667a\u80fd\u4f53\uff08Agent\uff09\uff0c\u8fdb\u884c\u5168\u9762\u7684 Python \u4e13\u9879\u4ee3\u7801\u5ba1\u67e5\u3002\n\n## \u6b64\u547d\u4ee4\u7684\u4f5c\u7528\n\n1. **\u8bc6\u522b Python \u53d8\u66f4**\uff1a\u901a\u8fc7 `git diff` \u67e5\u627e\u4fee\u6539\u8fc7\u7684 `.py` \u6587\u4ef6\n2. **\u8fd0\u884c\u9759\u6001\u5206\u6790**\uff1a\u6267\u884c `ruff`\u3001`mypy`\u3001`pylint`\u3001`black --check`\n3. **\u5b89\u5168\u626b\u63cf**\uff1a\u68c0\u67e5 SQL \u6ce8\u5165\u3001\u547d\u4ee4\u6ce8\u5165\u3001\u4e0d\u5b89\u5168\u7684\u53cd\u5e8f\u5217\u5316\n4. **\u7c7b\u578b\u5b89\u5168\u5ba1\u67e5**\uff1a\u5206\u6790\u7c7b\u578b\u63d0\u793a\uff08Type Hints\uff09\u548c mypy \u9519\u8bef\n5. **Pythonic \u4ee3\u7801\u68c0\u67e5**\uff1a\u9a8c\u8bc1\u4ee3\u7801\u662f\u5426\u7b26\u5408 PEP 8 \u548c Python \u6700\u4f73\u5b9e\u8df5\n6. **\u751f\u6210\u62a5\u544a**\uff1a\u6309\u4e25\u91cd\u7a0b\u5ea6\uff08Severity\uff09\u5bf9\u95ee\u9898\u8fdb\u884c\u5206\u7c7b\n\n## \u9002\u7528\u573a\u666f\n\n\u5728\u4ee5\u4e0b\u60c5\u51b5\u4e0b\u4f7f\u7528 `/python-review`\uff1a\n- \u7f16\u5199\u6216\u4fee\u6539 Python \u4ee3\u7801\u540e\n- \u63d0\u4ea4 Python \u53d8\u66f4\u524d\n- \u5ba1\u67e5\u5305\u542b Python \u4ee3\u7801\u7684\u62c9\u53d6\u8bf7\u6c42\uff08Pull Requests\uff09\n- \u63a5\u5165\u65b0\u7684 Python \u4ee3\u7801\u5e93\u65f6\n- \u5b66\u4e60 Pythonic \u6a21\u5f0f\u548c\u60ef\u7528\u6cd5\u65f6\n\n## \u5ba1\u67e5\u7c7b\u522b\n\n### \u4e25\u91cd (CRITICAL) (\u5fc5\u987b\u4fee\u590d)\n- SQL/\u547d\u4ee4\u6ce8\u5165\u6f0f\u6d1e\n- \u4e0d\u5b89\u5168\u7684 eval/exec \u4f7f\u7528\n- Pickle \u4e0d\u5b89\u5168\u7684\u53cd\u5e8f\u5217\u5316\n- \u786c\u7f16\u7801\u51ed\u636e\n- YAML \u4e0d\u5b89\u5168\u7684\u52a0\u8f7d (unsafe load)\n- \u9690\u85cf\u9519\u8bef\u7684\u88f8 except \u5b50\u53e5\n\n### \u9ad8 (HIGH) (\u5e94\u8be5\u4fee\u590d)\n- \u516c\u5171\u51fd\u6570\u7f3a\u5931\u7c7b\u578b\u63d0\u793a\n- \u53ef\u53d8\u9ed8\u8ba4\u53c2\u6570 (Mutable default arguments)\n- \u9759\u9ed8\u541e\u6389\u5f02\u5e38\n- \u672a\u5bf9\u8d44\u6e90\u4f7f\u7528\u4e0a\u4e0b\u6587\u7ba1\u7406\u5668 (Context Managers)\n- \u4f7f\u7528 C \u98ce\u683c\u5faa\u73af\u800c\u975e\u63a8\u5bfc\u5f0f (Comprehensions)\n- \u4f7f\u7528 type() \u800c\u975e isinstance()\n- \u65e0\u9501\u72b6\u6001\u4e0b\u7684\u7ade\u6001\u6761\u4ef6\n\n### \u4e2d (MEDIUM) (\u5efa\u8bae\u8003\u8651)\n- \u8fdd\u53cd PEP 8 \u683c\u5f0f\u89c4\u8303\n- \u516c\u5171\u51fd\u6570\u7f3a\u5931\u6587\u6863\u5b57\u7b26\u4e32 (Docstrings)\n- \u4f7f\u7528 print \u8bed\u53e5\u800c\u975e\u65e5\u5fd7 (Logging)\n- \u4f4e\u6548\u7684\u5b57\u7b26\u4e32\u64cd\u4f5c\n- \u65e0\u547d\u540d\u5e38\u91cf\u7684\u9b54\u672f\u6570\u5b57 (Magic numbers)\n- \u672a\u4f7f\u7528 f-strings \u8fdb\u884c\u683c\u5f0f\u5316\n- \u4e0d\u5fc5\u8981\u7684\u5217\u8868\u521b\u5efa\n\n## \u8fd0\u884c\u7684\u81ea\u52a8\u5316\u68c0\u67e5\n\n```bash\n# \u7c7b\u578b\u68c0\u67e5\nmypy .\n\n# \u4ee3\u7801\u68c0\u67e5\u4e0e\u683c\u5f0f\u5316\nruff check .\nblack --check .\nisort --check-only .\n\n# \u5b89\u5168\u626b\u63cf\nbandit -r .\n\n# \u4f9d\u8d56\u5ba1\u8ba1\npip-audit\nsafety check\n\n# \u6d4b\u8bd5\npytest --cov=app --cov-report=term-missing\n```\n\n## \u793a\u4f8b\u7528\u6cd5\n\n```text\nUser: /python-review\n\nAgent:\n# Python \u4ee3\u7801\u5ba1\u67e5\u62a5\u544a\n\n## \u5df2\u5ba1\u67e5\u6587\u4ef6\n- app/routes/user.py (\u5df2\u4fee\u6539)\n- app/services/auth.py (\u5df2\u4fee\u6539)\n\n## \u9759\u6001\u5206\u6790\u7ed3\u679c\n\u2713 ruff: \u65e0\u95ee\u9898\n\u2713 mypy: \u65e0\u9519\u8bef\n\u26a0\ufe0f black: 2 \u4e2a\u6587\u4ef6\u9700\u8981\u91cd\u65b0\u683c\u5f0f\u5316\n\u2713 bandit: \u65e0\u5b89\u5168\u95ee\u9898\n\n## \u53d1\u73b0\u7684\u95ee\u9898\n\n[CRITICAL] SQL \u6ce8\u5165\u6f0f\u6d1e\n\u6587\u4ef6: app/routes/user.py:42\n\u95ee\u9898: \u7528\u6237\u8f93\u5165\u76f4\u63a5\u63d2\u5165\u5230 SQL \u67e5\u8be2\u4e2d\n```python\nquery = f\"SELECT * FROM users WHERE id = {user_id}\" # \u4e0d\u826f\u505a\u6cd5\n```\n\u4fee\u590d: \u4f7f\u7528\u53c2\u6570\u5316\u67e5\u8be2\n```python\nquery = \"SELECT * FROM users WHERE id = %s\" # \u63a8\u8350\u505a\u6cd5\ncursor.execute(query, (user_id,))\n```\n\n[HIGH] \u53ef\u53d8\u9ed8\u8ba4\u53c2\u6570\n\u6587\u4ef6: app/services/auth.py:18\n\u95ee\u9898: \u53ef\u53d8\u9ed8\u8ba4\u53c2\u6570\u4f1a\u5bfc\u81f4\u72b6\u6001\u5171\u4eab\n```python\ndef process_items(items=[]): # \u4e0d\u826f\u505a\u6cd5\n items.append(\"new\")\n return items\n```\n\u4fee\u590d: \u4f7f\u7528 None \u4f5c\u4e3a\u9ed8\u8ba4\u503c\n```python\ndef process_items(items=None): # \u63a8\u8350\u505a\u6cd5\n if items is None:\n items = []\n items.append(\"new\")\n return items\n```\n\n[MEDIUM] \u7f3a\u5931\u7c7b\u578b\u63d0\u793a\n\u6587\u4ef6: app/services/auth.py:25\n\u95ee\u9898: \u516c\u5171\u51fd\u6570\u6ca1\u6709\u7c7b\u578b\u6ce8\u89e3\n```python\ndef get_user(user_id): # \u4e0d\u826f\u505a\u6cd5\n return db.find(user_id)\n```\n\u4fee\u590d: \u6dfb\u52a0\u7c7b\u578b\u63d0\u793a\n```python\ndef get_user(user_id: str) -> Optional[User]: # \u63a8\u8350\u505a\u6cd5\n return db.find(user_id)\n```\n\n[MEDIUM] \u672a\u4f7f\u7528\u4e0a\u4e0b\u6587\u7ba1\u7406\u5668\n\u6587\u4ef6: app/routes/user.py:55\n\u95ee\u9898: \u5f02\u5e38\u53d1\u751f\u65f6\u6587\u4ef6\u672a\u5173\u95ed\n```python\nf = open(\"config.json\") # \u4e0d\u826f\u505a\u6cd5\ndata = f.read()\nf.close()\n```\n\u4fee\u590d: \u4f7f\u7528\u4e0a\u4e0b\u6587\u7ba1\u7406\u5668\n```python\nwith open(\"config.json\") as f: # \u63a8\u8350\u505a\u6cd5\n data = f.read()\n```\n\n## \u6458\u8981\n- \u4e25\u91cd (CRITICAL): 1\n- \u9ad8 (HIGH): 1\n- \u4e2d (MEDIUM): 2\n\n\u5efa\u8bae: \u274c \u5728\u4fee\u590d\u4e25\u91cd\u95ee\u9898\u524d\u963b\u6b62\u5408\u5e76\n\n## \u9700\u8981\u683c\u5f0f\u5316\n\u8fd0\u884c: `black app/routes/user.py app/services/auth.py`\n```\n\n## \u6279\u51c6\u6807\u51c6\n\n| \u72b6\u6001 | \u6761\u4ef6 |\n|--------|-----------|\n| \u2705 \u6279\u51c6 (Approve) | \u65e0\u201c\u4e25\u91cd\u201d\u6216\u201c\u9ad8\u201d\u7ea7\u522b\u95ee\u9898 |\n| \u26a0\ufe0f \u8b66\u544a (Warning) | \u4ec5\u5b58\u5728\u201c\u4e2d\u201d\u7ea7\u522b\u95ee\u9898\uff08\u8c28\u614e\u5408\u5e76\uff09 |\n| \u274c \u963b\u6b62 (Block) | \u53d1\u73b0\u201c\u4e25\u91cd\u201d\u6216\u201c\u9ad8\u201d\u7ea7\u522b\u95ee\u9898 |\n\n## \u4e0e\u5176\u4ed6\u547d\u4ee4\u7684\u96c6\u6210\n\n- \u5148\u4f7f\u7528 `/python-test` \u786e\u4fdd\u6d4b\u8bd5\u901a\u8fc7\n- \u4f7f\u7528 `/code-review` \u5904\u7406\u975e Python \u4e13\u9879\u7684\u5173\u6ce8\u70b9\n- \u5728\u63d0\u4ea4\uff08commit\uff09\u524d\u4f7f\u7528 `/python-review`\n- \u5982\u679c\u9759\u6001\u5206\u6790\u5de5\u5177\u62a5\u9519\uff0c\u4f7f\u7528 `/build-fix`\n\n## \u6846\u67b6\u4e13\u9879\u5ba1\u67e5\n\n### Django \u9879\u76ee\n\u5ba1\u67e5\u8005\u4f1a\u68c0\u67e5\uff1a\n- N+1 \u67e5\u8be2\u95ee\u9898\uff08\u4f7f\u7528 `select_related` \u548c `prefetch_related`\uff09\n- \u6a21\u578b\u53d8\u66f4\u7f3a\u5931\u8fc1\u79fb\u6587\u4ef6\n- \u5728 ORM \u53ef\u7528\u7684\u60c5\u51b5\u4e0b\u4f7f\u7528\u539f\u751f SQL\n- \u591a\u6b65\u64cd\u4f5c\u7f3a\u5931 `transaction.atomic()`\n\n### FastAPI \u9879\u76ee\n\u5ba1\u67e5\u8005\u4f1a\u68c0\u67e5\uff1a\n- CORS \u914d\u7f6e\u9519\u8bef\n- \u7528\u4e8e\u8bf7\u6c42\u6821\u9a8c\u7684 Pydantic \u6a21\u578b\n- \u54cd\u5e94\u6a21\u578b\u7684\u6b63\u786e\u6027\n- \u6070\u5f53\u7684 async/await \u4f7f\u7528\n- \u4f9d\u8d56\u6ce8\u5165\u6a21\u5f0f\n\n### Flask \u9879\u76ee\n\u5ba1\u67e5\u8005\u4f1a\u68c0\u67e5\uff1a\n- \u4e0a\u4e0b\u6587\u7ba1\u7406\uff08\u5e94\u7528\u4e0a\u4e0b\u6587\u3001\u8bf7\u6c42\u4e0a\u4e0b\u6587\uff09\n- \u6070\u5f53\u7684\u9519\u8bef\u5904\u7406\n- \u84dd\u56fe (Blueprint) \u7ec4\u7ec7\u7ed3\u6784\n- \u914d\u7f6e\u7ba1\u7406\n\n## \u76f8\u5173\n\n- \u667a\u80fd\u4f53 (Agent): `agents/python-reviewer.md`\n- \u6280\u80fd (Skills): `skills/python-patterns/`, `skills/python-testing/`\n\n## \u5e38\u89c1\u4fee\u590d\u65b9\u6848\n\n### \u6dfb\u52a0\u7c7b\u578b\u63d0\u793a\n```python\n# \u4fee\u590d\u524d\ndef calculate(x, y):\n return x + y\n\n# \u4fee\u590d\u540e\nfrom typing import Union\n\ndef calculate(x: Union[int, float], y: Union[int, float]) -> Union[int, float]:\n return x + y\n```\n\n### \u4f7f\u7528\u4e0a\u4e0b\u6587\u7ba1\u7406\u5668\n```python\n# \u4fee\u590d\u524d\nf = open(\"file.txt\")\ndata = f.read()\nf.close()\n\n# \u4fee\u590d\u540e\nwith open(\"file.txt\") as f:\n data = f.read()\n```\n\n### \u4f7f\u7528\u5217\u8868\u63a8\u5bfc\u5f0f\n```python\n# \u4fee\u590d\u524d\nresult = []\nfor item in items:\n if item.active:\n result.append(item.name)\n\n# \u4fee\u590d\u540e\nresult = [item.name for item in items if item.active]\n```\n\n### \u4fee\u590d\u53ef\u53d8\u9ed8\u8ba4\u53c2\u6570\n```python\n# \u4fee\u590d\u524d\ndef append(value, items=[]):\n items.append(value)\n return items\n\n# \u4fee\u590d\u540e\ndef append(value, items=None):\n if items is None:\n items = []\n items.append(value)\n return items\n```\n\n### \u4f7f\u7528 f-strings (Python 3.6+)\n```python\n# \u4fee\u590d\u524d\nname = \"Alice\"\ngreeting = \"Hello, \" + name + \"!\"\ngreeting2 = \"Hello, {}\".format(name)\n\n# \u4fee\u590d\u540e\ngreeting = f\"Hello, {name}!\"\n```\n\n### \u4fee\u590d\u5faa\u73af\u4e2d\u7684\u5b57\u7b26\u4e32\u62fc\u63a5\n```python\n# \u4fee\u590d\u524d\nresult = \"\"\nfor item in items:\n result += str(item)\n\n# \u4fee\u590d\u540e\nresult = \"\".join(str(item) for item in items)\n```\n\n## Python \u7248\u672c\u517c\u5bb9\u6027\n\n\u5ba1\u67e5\u8005\u4f1a\u63d0\u793a\u4ee3\u7801\u4f55\u65f6\u4f7f\u7528\u4e86\u8f83\u65b0 Python \u7248\u672c\u7684\u7279\u6027\uff1a\n\n| \u7279\u6027 | \u6700\u4f4e Python \u7248\u672c |\n|---------|----------------|\n| \u7c7b\u578b\u63d0\u793a (Type hints) | 3.5+ |\n| f-strings | 3.6+ |\n| \u6d77\u8c61\u8fd0\u7b97\u7b26 (Walrus operator `:=`) | 3.8+ |\n| \u4ec5\u9650\u4f4d\u7f6e\u53c2\u6570 (Position-only parameters) | 3.8+ |\n| \u5339\u914d\u8bed\u53e5 (Match statements) | 3.10+ |\n| \u7c7b\u578b\u8054\u5408 (Type unions `x | None`) | 3.10+ |\n\n\u8bf7\u786e\u4fdd\u9879\u76ee\u7684 `pyproject.toml` \u6216 `setup.py` \u6307\u5b9a\u4e86\u6b63\u786e\u7684\u6700\u4f4e Python \u7248\u672c\u3002\n" + }, + "/Users/Library/Applications/xx/code/github/everything-claude-code-zh/commands/skill-create.md": { + "md5": "551b0c52e2996f635fbe16db38fc424a", + "content": "---\nname: skill-create\ndescription: \u5206\u6790\u672c\u5730 Git \u5386\u53f2\u4ee5\u63d0\u53d6\u7f16\u7801\u6a21\u5f0f\u5e76\u751f\u6210 SKILL.md \u6587\u4ef6\u3002Skill Creator GitHub App \u7684\u672c\u5730\u7248\u672c\u3002\nallowed_tools: [\"Bash\", \"Read\", \"Write\", \"Grep\", \"Glob\"]\n---\n\n# /skill-create - \u672c\u5730\u6280\u80fd\u751f\u6210\uff08Local Skill Generation\uff09\n\n\u5206\u6790\u4f60\u4ed3\u5e93\u7684 Git \u5386\u53f2\u8bb0\u5f55\u4ee5\u63d0\u53d6\u7f16\u7801\u6a21\u5f0f\uff0c\u5e76\u751f\u6210 SKILL.md \u6587\u4ef6\uff0c\u4ee5\u4fbf\u8ba9 Claude \u5b66\u4e60\u4f60\u56e2\u961f\u7684\u5de5\u7a0b\u5b9e\u8df5\u3002\n\n## \u7528\u6cd5\uff08Usage\uff09\n\n```bash\n/skill-create # \u5206\u6790\u5f53\u524d\u4ed3\u5e93\n/skill-create --commits 100 # \u5206\u6790\u6700\u8fd1 100 \u6761\u63d0\u4ea4\n/skill-create --output ./skills # \u6307\u5b9a\u81ea\u5b9a\u4e49\u8f93\u51fa\u76ee\u5f55\n/skill-create --instincts # \u540c\u65f6\u4e3a continuous-learning-v2 \u751f\u6210\u76f4\u89c9\uff08instincts\uff09\n```\n\n## \u529f\u80fd\u8bf4\u660e\uff08What It Does\uff09\n\n1. **\u89e3\u6790 Git \u5386\u53f2** - \u5206\u6790\u63d0\u4ea4\uff08commits\uff09\u3001\u6587\u4ef6\u53d8\u66f4\u548c\u6a21\u5f0f\u3002\n2. **\u68c0\u6d4b\u6a21\u5f0f** - \u8bc6\u522b\u5faa\u73af\u51fa\u73b0\u7684\u5de5\u4f5c\u6d41\uff08Workflow\uff09\u548c\u7ea6\u5b9a\u3002\n3. **\u751f\u6210 SKILL.md** - \u521b\u5efa\u6709\u6548\u7684 Claude Code \u6280\u80fd\uff08Skill\uff09\u6587\u4ef6\u3002\n4. **\u53ef\u9009\u751f\u6210\u76f4\u89c9\uff08Instincts\uff09** - \u7528\u4e8e continuous-learning-v2 \u7cfb\u7edf\u3002\n\n## \u5206\u6790\u6b65\u9aa4\uff08Analysis Steps\uff09\n\n### \u7b2c 1 \u6b65\uff1a\u6536\u96c6 Git \u6570\u636e\n\n```bash\n# \u83b7\u53d6\u5e26\u6709\u6587\u4ef6\u53d8\u66f4\u7684\u8fd1\u671f\u63d0\u4ea4\ngit log --oneline -n ${COMMITS:-200} --name-only --pretty=format:\"%H|%s|%ad\" --date=short\n\n# \u83b7\u53d6\u6309\u6587\u4ef6\u7edf\u8ba1\u7684\u63d0\u4ea4\u9891\u7387\ngit log --oneline -n 200 --name-only | grep -v \"^$\" | grep -v \"^[a-f0-9]\" | sort | uniq -c | sort -rn | head -20\n\n# \u83b7\u53d6\u63d0\u4ea4\u4fe1\u606f\u6a21\u5f0f\ngit log --oneline -n 200 | cut -d' ' -f2- | head -50\n```\n\n### \u7b2c 2 \u6b65\uff1a\u68c0\u6d4b\u6a21\u5f0f\n\n\u5bfb\u627e\u4ee5\u4e0b\u6a21\u5f0f\u7c7b\u578b\uff1a\n\n| \u6a21\u5f0f (Pattern) | \u68c0\u6d4b\u65b9\u6cd5 (Detection Method) |\n|---------|-----------------|\n| **\u63d0\u4ea4\u89c4\u8303 (Commit conventions)** | \u5bf9\u63d0\u4ea4\u4fe1\u606f\u4f7f\u7528\u6b63\u5219\u5339\u914d (feat:, fix:, chore:) |\n| **\u6587\u4ef6\u5173\u8054\u53d8\u66f4 (File co-changes)** | \u603b\u662f\u540c\u65f6\u53d1\u751f\u53d8\u5316\u7684\u6587\u4ef6 |\n| **\u5de5\u4f5c\u6d41\u5e8f\u5217 (Workflow sequences)** | \u91cd\u590d\u51fa\u73b0\u7684\u6587\u4ef6\u53d8\u66f4\u6a21\u5f0f |\n| **\u67b6\u6784 (Architecture)** | \u6587\u4ef6\u5939\u7ed3\u6784\u548c\u547d\u540d\u89c4\u8303 |\n| **\u6d4b\u8bd5\u6a21\u5f0f (Testing patterns)** | \u6d4b\u8bd5\u6587\u4ef6\u4f4d\u7f6e\u3001\u547d\u540d\u3001\u8986\u76d6\u7387 |\n\n### \u7b2c 3 \u6b65\uff1a\u751f\u6210 SKILL.md\n\n\u8f93\u51fa\u683c\u5f0f\uff1a\n\n```markdown\n---\nname: {repo-name}-patterns\ndescription: Coding patterns extracted from {repo-name}\nversion: 1.0.0\nsource: local-git-analysis\nanalyzed_commits: {count}\n---\n\n# {Repo Name} \u6a21\u5f0f\n\n## \u63d0\u4ea4\u89c4\u8303\n{\u68c0\u6d4b\u5230\u7684\u63d0\u4ea4\u4fe1\u606f\u6a21\u5f0f}\n\n## \u4ee3\u7801\u67b6\u6784\n{\u68c0\u6d4b\u5230\u7684\u6587\u4ef6\u5939\u7ed3\u6784\u548c\u7ec4\u7ec7\u65b9\u5f0f}\n\n## \u5de5\u4f5c\u6d41\n{\u68c0\u6d4b\u5230\u7684\u91cd\u590d\u6587\u4ef6\u53d8\u66f4\u6a21\u5f0f}\n\n## \u6d4b\u8bd5\u6a21\u5f0f\n{\u68c0\u6d4b\u5230\u7684\u6d4b\u8bd5\u7ea6\u5b9a}\n```\n\n### \u7b2c 4 \u6b65\uff1a\u751f\u6210\u76f4\u89c9 (\u5982\u679c\u4f7f\u7528\u4e86 --instincts)\n\n\u7528\u4e8e continuous-learning-v2 \u96c6\u6210\uff1a\n\n```yaml\n---\nid: {repo}-commit-convention\ntrigger: \"when writing a commit message\"\nconfidence: 0.8\ndomain: git\nsource: local-repo-analysis\n---\n\n# \u4f7f\u7528\u7ea6\u5b9a\u5f0f\u63d0\u4ea4 (Conventional Commits)\n\n## \u64cd\u4f5c (Action)\n\u5728\u63d0\u4ea4\u4fe1\u606f\u524d\u6dfb\u52a0\u524d\u7f00\uff1afeat:, fix:, chore:, docs:, test:, refactor:\n\n## \u8bc1\u636e (Evidence)\n- \u5df2\u5206\u6790 {n} \u6761\u63d0\u4ea4\n- {percentage}% \u9075\u5faa\u7ea6\u5b9a\u5f0f\u63d0\u4ea4\u683c\u5f0f\n```\n\n## \u8f93\u51fa\u793a\u4f8b\n\n\u5728 TypeScript \u9879\u76ee\u4e0a\u8fd0\u884c `/skill-create` \u53ef\u80fd\u4f1a\u4ea7\u751f\uff1a\n\n```markdown\n---\nname: my-app-patterns\ndescription: Coding patterns from my-app repository\nversion: 1.0.0\nsource: local-git-analysis\nanalyzed_commits: 150\n---\n\n# My App \u6a21\u5f0f\n\n## \u63d0\u4ea4\u89c4\u8303 (Commit Conventions)\n\n\u8be5\u9879\u76ee\u4f7f\u7528 **\u7ea6\u5b9a\u5f0f\u63d0\u4ea4 (conventional commits)**\uff1a\n- `feat:` - \u65b0\u529f\u80fd\n- `fix:` - \u9519\u8bef\u4fee\u590d\n- `chore:` - \u7ef4\u62a4\u4efb\u52a1\n- `docs:` - \u6587\u6863\u66f4\u65b0\n\n## \u4ee3\u7801\u67b6\u6784 (Code Architecture)\n\n```\nsrc/\n\u251c\u2500\u2500 components/ # React \u7ec4\u4ef6 (PascalCase.tsx)\n\u251c\u2500\u2500 hooks/ # \u81ea\u5b9a\u4e49 Hooks (use*.ts)\n\u251c\u2500\u2500 utils/ # \u5de5\u5177\u51fd\u6570\n\u251c\u2500\u2500 types/ # TypeScript \u7c7b\u578b\u5b9a\u4e49\n\u2514\u2500\u2500 services/ # API \u548c\u5916\u90e8\u670d\u52a1\n```\n\n## \u5de5\u4f5c\u6d41 (Workflows)\n\n### \u6dfb\u52a0\u65b0\u7ec4\u4ef6\n1. \u521b\u5efa `src/components/ComponentName.tsx`\n2. \u5728 `src/components/__tests__/ComponentName.test.tsx` \u4e2d\u6dfb\u52a0\u6d4b\u8bd5\n3. \u4ece `src/components/index.ts` \u5bfc\u51fa\n\n### \u6570\u636e\u5e93\u8fc1\u79fb\n1. \u4fee\u6539 `src/db/schema.ts`\n2. \u8fd0\u884c `pnpm db:generate`\n3. \u8fd0\u884c `pnpm db:migrate`\n\n## \u6d4b\u8bd5\u6a21\u5f0f (Testing patterns)\n\n- \u6d4b\u8bd5\u6587\u4ef6\uff1a`__tests__/` \u76ee\u5f55\u6216 `.test.ts` \u540e\u7f00\n- \u8986\u76d6\u7387\u76ee\u6807\uff1a80%+\n- \u6846\u67b6\uff1aVitest\n```\n\n## GitHub App \u96c6\u6210\n\n\u5bf9\u4e8e\u9ad8\u7ea7\u529f\u80fd\uff081\u4e07+ \u63d0\u4ea4\u3001\u56e2\u961f\u5171\u4eab\u3001\u81ea\u52a8 PR\uff09\uff0c\u8bf7\u4f7f\u7528 [Skill Creator GitHub App](https://github.com/apps/skill-creator)\uff1a\n\n- \u5b89\u88c5\uff1a[github.com/apps/skill-creator](https://github.com/apps/skill-creator)\n- \u5728\u4efb\u4f55 Issue \u4e0a\u8bc4\u8bba `/skill-creator analyze`\n- \u63a5\u6536\u5305\u542b\u751f\u6210\u7684\u6280\u80fd\u7684 PR\n\n## \u76f8\u5173\u547d\u4ee4\n\n- `/instinct-import` - \u5bfc\u5165\u751f\u6210\u7684\u76f4\u89c9\n- `/instinct-status` - \u67e5\u770b\u5df2\u5b66\u4e60\u7684\u76f4\u89c9\n- `/evolve` - \u5c06\u76f4\u89c9\u805a\u7c7b\u4e3a\u6280\u80fd/\u667a\u80fd\u4f53\n\n---\n\n*\u5c5e\u4e8e [Everything Claude Code](https://github.com/affaan-m/everything-claude-code) \u7684\u4e00\u90e8\u5206*\n" + }, + "/Users/Library/Applications/xx/code/github/everything-claude-code-zh/skills/django-security/SKILL.md": { + "md5": "407fdd4274972aa1720af18e64019ae9", + "content": "---\nname: django-security\ndescription: Django \u5b89\u5168\u6700\u4f73\u5b9e\u8df5\uff0c\u6db5\u76d6\u8eab\u4efd\u8ba4\u8bc1\u3001\u6388\u6743\u3001CSRF \u9632\u62a4\u3001SQL \u6ce8\u5165\u9884\u9632\u3001XSS \u9884\u9632\u4ee5\u53ca\u5b89\u5168\u7684\u90e8\u7f72\u914d\u7f6e\u3002\n---\n\n# Django \u5b89\u5168\u6700\u4f73\u5b9e\u8df5\n\n\u9488\u5bf9 Django \u5e94\u7528\u7a0b\u5e8f\u7684\u5168\u9762\u5b89\u5168\u6307\u5357\uff0c\u65e8\u5728\u62b5\u5fa1\u5e38\u89c1\u7684\u6f0f\u6d1e\u3002\n\n## \u4f55\u65f6\u6fc0\u6d3b\n\n- \u8bbe\u7f6e Django \u8eab\u4efd\u8ba4\u8bc1\uff08Authentication\uff09\u548c\u6388\u6743\uff08Authorization\uff09\u65f6\n- \u5b9e\u73b0\u7528\u6237\u6743\u9650\u548c\u89d2\u8272\u65f6\n- \u914d\u7f6e\u751f\u4ea7\u73af\u5883\u5b89\u5168\u8bbe\u7f6e\u65f6\n- \u5ba1\u67e5 Django \u5e94\u7528\u7a0b\u5e8f\u7684\u5b89\u5168\u95ee\u9898\u65f6\n- \u5c06 Django \u5e94\u7528\u7a0b\u5e8f\u90e8\u7f72\u5230\u751f\u4ea7\u73af\u5883\u65f6\n\n## \u6838\u5fc3\u5b89\u5168\u8bbe\u7f6e\n\n### \u751f\u4ea7\u73af\u5883\u8bbe\u7f6e\u914d\u7f6e\n\n```python\n# settings/production.py\nimport os\n\nDEBUG = False # \u5173\u952e\uff1a\u5207\u52ff\u5728\u751f\u4ea7\u73af\u5883\u4e2d\u4f7f\u7528 True\n\nALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',')\n\n# \u5b89\u5168\u5934\u90e8\uff08Security headers\uff09\nSECURE_SSL_REDIRECT = True\nSESSION_COOKIE_SECURE = True\nCSRF_COOKIE_SECURE = True\nSECURE_HSTS_SECONDS = 31536000 # 1 \u5e74\nSECURE_HSTS_INCLUDE_SUBDOMAINS = True\nSECURE_HSTS_PRELOAD = True\nSECURE_CONTENT_TYPE_NOSNIFF = True\nSECURE_BROWSER_XSS_FILTER = True\nX_FRAME_OPTIONS = 'DENY'\n\n# HTTPS \u4e0e Cookie\nSESSION_COOKIE_HTTPONLY = True\nCSRF_COOKIE_HTTPONLY = True\nSESSION_COOKIE_SAMESITE = 'Lax'\nCSRF_COOKIE_SAMESITE = 'Lax'\n\n# \u5bc6\u94a5\uff08\u5fc5\u987b\u901a\u8fc7\u73af\u5883\u53d8\u91cf\u8bbe\u7f6e\uff09\nSECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')\nif not SECRET_KEY:\n raise ImproperlyConfigured('DJANGO_SECRET_KEY environment variable is required')\n\n# \u5bc6\u7801\u6821\u9a8c\nAUTH_PASSWORD_VALIDATORS = [\n {\n 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',\n },\n {\n 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',\n 'OPTIONS': {\n 'min_length': 12,\n }\n },\n {\n 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',\n },\n {\n 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',\n },\n]\n```\n\n## \u8eab\u4efd\u8ba4\u8bc1\uff08Authentication\uff09\n\n### \u81ea\u5b9a\u4e49\u7528\u6237\u6a21\u578b\uff08Custom User Model\uff09\n\n```python\n# apps/users/models.py\nfrom django.contrib.auth.models import AbstractUser\nfrom django.db import models\n\nclass User(AbstractUser):\n \"\"\"\u4e3a\u4e86\u66f4\u597d\u7684\u5b89\u5168\u6027\u800c\u81ea\u5b9a\u4e49\u7684\u7528\u6237\u6a21\u578b\u3002\"\"\"\n\n email = models.EmailField(unique=True)\n phone = models.CharField(max_length=20, blank=True)\n\n USERNAME_FIELD = 'email' # \u4f7f\u7528\u90ae\u7bb1\u4f5c\u4e3a\u7528\u6237\u540d\n REQUIRED_FIELDS = ['username']\n\n class Meta:\n db_table = 'users'\n verbose_name = 'User'\n verbose_name_plural = 'Users'\n\n def __str__(self):\n return self.email\n\n# settings/base.py\nAUTH_USER_MODEL = 'users.User'\n```\n\n### \u5bc6\u7801\u54c8\u5e0c\uff08Password Hashing\uff09\n\n```python\n# Django \u9ed8\u8ba4\u4f7f\u7528 PBKDF2\u3002\u4e3a\u4e86\u66f4\u5f3a\u7684\u5b89\u5168\u6027\uff1a\nPASSWORD_HASHERS = [\n 'django.contrib.auth.hashers.Argon2PasswordHasher',\n 'django.contrib.auth.hashers.PBKDF2PasswordHasher',\n 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',\n 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',\n]\n```\n\n### \u4f1a\u8bdd\u7ba1\u7406\uff08Session Management\uff09\n\n```python\n# \u4f1a\u8bdd\u914d\u7f6e\nSESSION_ENGINE = 'django.contrib.sessions.backends.cache' # \u6216 'db'\nSESSION_CACHE_ALIAS = 'default'\nSESSION_COOKIE_AGE = 3600 * 24 * 7 # 1 \u5468\nSESSION_SAVE_EVERY_REQUEST = False\nSESSION_EXPIRE_AT_BROWSER_CLOSE = False # \u66f4\u597d\u7684\u7528\u6237\u4f53\u9a8c\uff0c\u4f46\u5b89\u5168\u6027\u7565\u4f4e\n```\n\n## \u6388\u6743\uff08Authorization\uff09\n\n### \u6743\u9650\uff08Permissions\uff09\n\n```python\n# models.py\nfrom django.db import models\nfrom django.contrib.auth.models import Permission\n\nclass Post(models.Model):\n title = models.CharField(max_length=200)\n content = models.TextField()\n author = models.ForeignKey(User, on_delete=models.CASCADE)\n\n class Meta:\n permissions = [\n ('can_publish', '\u53ef\u4ee5\u53d1\u5e03\u5e16\u5b50'),\n ('can_edit_others', '\u53ef\u4ee5\u7f16\u8f91\u4ed6\u4eba\u7684\u5e16\u5b50'),\n ]\n\n def user_can_edit(self, user):\n \"\"\"\u68c0\u67e5\u7528\u6237\u662f\u5426\u53ef\u4ee5\u7f16\u8f91\u6b64\u5e16\u5b50\u3002\"\"\"\n return self.author == user or user.has_perm('app.can_edit_others')\n\n# views.py\nfrom django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin\nfrom django.views.generic import UpdateView\n\nclass PostUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):\n model = Post\n permission_required = 'app.can_edit_others'\n raise_exception = True # \u8fd4\u56de 403 \u800c\u4e0d\u662f\u91cd\u5b9a\u5411\n\n def get_queryset(self):\n \"\"\"\u4ec5\u5141\u8bb8\u7528\u6237\u7f16\u8f91\u81ea\u5df1\u7684\u5e16\u5b50\u3002\"\"\"\n return Post.objects.filter(author=self.request.user)\n```\n\n### \u81ea\u5b9a\u4e49\u6743\u9650\n\n```python\n# permissions.py\nfrom rest_framework import permissions\n\nclass IsOwnerOrReadOnly(permissions.BasePermission):\n \"\"\"\u4ec5\u5141\u8bb8\u6240\u6709\u8005\u7f16\u8f91\u5bf9\u8c61\u3002\"\"\"\n\n def has_object_permission(self, request, view, obj):\n # \u5141\u8bb8\u4efb\u4f55\u8bf7\u6c42\u7684\u8bfb\u53d6\u6743\u9650\n if request.method in permissions.SAFE_METHODS:\n return True\n\n # \u4ec5\u6240\u6709\u8005\u62e5\u6709\u5199\u5165\u6743\u9650\n return obj.author == request.user\n\nclass IsAdminOrReadOnly(permissions.BasePermission):\n \"\"\"\u5141\u8bb8\u7ba1\u7406\u5458\u6267\u884c\u4efb\u4f55\u64cd\u4f5c\uff0c\u5176\u4ed6\u4eba\u53ea\u8bfb\u3002\"\"\"\n\n def has_permission(self, request, view):\n if request.method in permissions.SAFE_METHODS:\n return True\n return request.user and request.user.is_staff\n\nclass IsVerifiedUser(permissions.BasePermission):\n \"\"\"\u4ec5\u5141\u8bb8\u5df2\u9a8c\u8bc1\u7684\u7528\u6237\u3002\"\"\"\n\n def has_permission(self, request, view):\n return request.user and request.user.is_authenticated and request.user.is_verified\n```\n\n### \u57fa\u4e8e\u89d2\u8272\u7684\u8bbf\u95ee\u63a7\u5236\uff08RBAC\uff09\n\n```python\n# models.py\nfrom django.contrib.auth.models import AbstractUser, Group\n\nclass User(AbstractUser):\n ROLE_CHOICES = [\n ('admin', '\u7ba1\u7406\u5458'),\n ('moderator', '\u7248\u4e3b'),\n ('user', '\u666e\u901a\u7528\u6237'),\n ]\n role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='user')\n\n def is_admin(self):\n return self.role == 'admin' or self.is_superuser\n\n def is_moderator(self):\n return self.role in ['admin', 'moderator']\n\n# \u6df7\u5165\u7c7b\uff08Mixins\uff09\nclass AdminRequiredMixin:\n \"\"\"\u8981\u6c42\u7ba1\u7406\u5458\u89d2\u8272\u7684\u6df7\u5165\u7c7b\u3002\"\"\"\n\n def dispatch(self, request, *args, **kwargs):\n if not request.user.is_authenticated or not request.user.is_admin():\n from django.core.exceptions import PermissionDenied\n raise PermissionDenied\n return super().dispatch(request, *args, **kwargs)\n```\n\n## SQL \u6ce8\u5165\u9632\u62a4\n\n### Django ORM \u4fdd\u62a4\n\n```python\n# \u63a8\u8350\uff1aDjango ORM \u81ea\u52a8\u8f6c\u4e49\u53c2\u6570\ndef get_user(username):\n return User.objects.get(username=username) # \u5b89\u5168\n\n# \u63a8\u8350\uff1a\u5728 raw() \u4e2d\u4f7f\u7528\u53c2\u6570\ndef search_users(query):\n return User.objects.raw('SELECT * FROM users WHERE username = %s', [query])\n\n# \u9519\u8bef\uff1a\u5207\u52ff\u76f4\u63a5\u63d2\u503c\u7528\u6237\u8f93\u5165\ndef get_user_bad(username):\n return User.objects.raw(f'SELECT * FROM users WHERE username = {username}') # \u5b58\u5728\u6f0f\u6d1e\uff01\n\n# \u63a8\u8350\uff1a\u4f7f\u7528\u5e26\u6709\u6b63\u786e\u8f6c\u4e49\u7684 filter\ndef get_users_by_email(email):\n return User.objects.filter(email__iexact=email) # \u5b89\u5168\n\n# \u63a8\u8350\uff1a\u5bf9\u590d\u6742\u67e5\u8be2\u4f7f\u7528 Q \u5bf9\u8c61\nfrom django.db.models import Q\ndef search_users_complex(query):\n return User.objects.filter(\n Q(username__icontains=query) |\n Q(email__icontains=query)\n ) # \u5b89\u5168\n```\n\n### \u4f7f\u7528 raw() \u65f6\u7684\u989d\u5916\u5b89\u5168\u63aa\u65bd\n\n```python\n# \u5982\u679c\u5fc5\u987b\u4f7f\u7528\u539f\u751f SQL\uff0c\u8bf7\u52a1\u5fc5\u4f7f\u7528\u53c2\u6570\nUser.objects.raw(\n 'SELECT * FROM users WHERE email = %s AND status = %s',\n [user_input_email, status]\n)\n```\n\n## XSS \u9632\u62a4\n\n### \u6a21\u677f\u8f6c\u4e49\n\n```django\n{# Django \u9ed8\u8ba4\u81ea\u52a8\u8f6c\u4e49\u53d8\u91cf - \u5b89\u5168 #}\n{{ user_input }} {# \u5df2\u8f6c\u4e49\u7684 HTML #}\n\n{# \u4ec5\u5bf9\u53d7\u4fe1\u4efb\u7684\u5185\u5bb9\u663e\u5f0f\u6807\u8bb0\u4e3a safe #}\n{{ trusted_html|safe }} {# \u672a\u8f6c\u4e49 #}\n\n{# \u4f7f\u7528\u6a21\u677f\u8fc7\u6ee4\u5668\u4ee5\u83b7\u5f97\u5b89\u5168\u7684 HTML #}\n{{ user_input|escape }} {# \u4e0e\u9ed8\u8ba4\u503c\u76f8\u540c #}\n{{ user_input|striptags }} {# \u79fb\u9664\u6240\u6709 HTML \u6807\u7b7e #}\n\n{# JavaScript \u8f6c\u4e49 #}\n\n```\n\n### \u5b89\u5168\u5b57\u7b26\u4e32\u5904\u7406\n\n```python\nfrom django.utils.safestring import mark_safe\nfrom django.utils.html import escape\n\n# \u9519\u8bef\uff1a\u5728\u6ca1\u6709\u8f6c\u4e49\u7684\u60c5\u51b5\u4e0b\uff0c\u5207\u52ff\u5c06\u7528\u6237\u8f93\u5165\u6807\u8bb0\u4e3a safe\ndef render_bad(user_input):\n return mark_safe(user_input) # \u5b58\u5728\u6f0f\u6d1e\uff01\n\n# \u63a8\u8350\uff1a\u5148\u8f6c\u4e49\uff0c\u7136\u540e\u6807\u8bb0\u4e3a safe\ndef render_good(user_input):\n return mark_safe(escape(user_input))\n\n# \u63a8\u8350\uff1a\u5bf9\u5305\u542b\u53d8\u91cf\u7684 HTML \u4f7f\u7528 format_html\nfrom django.utils.html import format_html\n\ndef greet_user(username):\n return format_html('{}', escape(username))\n```\n\n### HTTP \u5934\u90e8\n\n```python\n# settings.py\nSECURE_CONTENT_TYPE_NOSNIFF = True # \u9632\u6b62 MIME \u55c5\u63a2\nSECURE_BROWSER_XSS_FILTER = True # \u542f\u7528 XSS \u8fc7\u6ee4\u5668\nX_FRAME_OPTIONS = 'DENY' # \u9632\u6b62\u70b9\u51fb\u52ab\u6301\n\n# \u81ea\u5b9a\u4e49\u4e2d\u95f4\u4ef6\nfrom django.conf import settings\n\nclass SecurityHeaderMiddleware:\n def __init__(self, get_response):\n self.get_response = get_response\n\n def __call__(self, request):\n response = self.get_response(request)\n response['X-Content-Type-Options'] = 'nosniff'\n response['X-Frame-Options'] = 'DENY'\n response['X-XSS-Protection'] = '1; mode=block'\n response['Content-Security-Policy'] = \"default-src 'self'\"\n return response\n```\n\n## CSRF \u9632\u62a4\n\n### \u9ed8\u8ba4 CSRF \u9632\u62a4\n\n```python\n# settings.py - CSRF \u9ed8\u8ba4\u5df2\u542f\u7528\nCSRF_COOKIE_SECURE = True # \u4ec5\u901a\u8fc7 HTTPS \u53d1\u9001\nCSRF_COOKIE_HTTPONLY = True # \u9632\u6b62 JavaScript \u8bbf\u95ee\nCSRF_COOKIE_SAMESITE = 'Lax' # \u5728\u67d0\u4e9b\u60c5\u51b5\u4e0b\u9632\u6b62 CSRF\nCSRF_TRUSTED_ORIGINS = ['https://example.com'] # \u53d7\u4fe1\u4efb\u7684\u57df\n\n# \u6a21\u677f\u7528\u6cd5\n
\n {% csrf_token %}\n {{ form.as_p }}\n \n
\n\n# AJAX \u8bf7\u6c42\nfunction getCookie(name) {\n let cookieValue = null;\n if (document.cookie && document.cookie !== '') {\n const cookies = document.cookie.split(';');\n for (let i = 0; i < cookies.length; i++) {\n const cookie = cookies[i].trim();\n if (cookie.substring(0, name.length + 1) === (name + '=')) {\n cookieValue = decodeURIComponent(cookie.substring(name.length + 1));\n break;\n }\n }\n }\n return cookieValue;\n}\n\nfetch('/api/endpoint/', {\n method: 'POST',\n headers: {\n 'X-CSRFToken': getCookie('csrftoken'),\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(data)\n});\n```\n\n### \u8c41\u514d\u89c6\u56fe\uff08\u8bf7\u8c28\u614e\u4f7f\u7528\uff09\n\n```python\nfrom django.views.decorators.csrf import csrf_exempt\n\n@csrf_exempt # \u4ec5\u5728\u7edd\u5bf9\u5fc5\u8981\u65f6\u4f7f\u7528\uff01\ndef webhook_view(request):\n # \u6765\u81ea\u5916\u90e8\u670d\u52a1\u7684 Webhook\n pass\n```\n\n## \u6587\u4ef6\u4e0a\u4f20\u5b89\u5168\n\n### \u6587\u4ef6\u9a8c\u8bc1\n\n```python\nimport os\nfrom django.core.exceptions import ValidationError\n\ndef validate_file_extension(value):\n \"\"\"\u9a8c\u8bc1\u6587\u4ef6\u6269\u5c55\u540d\u3002\"\"\"\n ext = os.path.splitext(value.name)[1]\n valid_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.pdf']\n if not ext.lower() in valid_extensions:\n raise ValidationError('\u4e0d\u652f\u6301\u7684\u6587\u4ef6\u6269\u5c55\u540d\u3002')\n\ndef validate_file_size(value):\n \"\"\"\u9a8c\u8bc1\u6587\u4ef6\u5927\u5c0f\uff08\u6700\u5927 5MB\uff09\u3002\"\"\"\n filesize = value.size\n if filesize > 5 * 1024 * 1024:\n raise ValidationError('\u6587\u4ef6\u8fc7\u5927\u3002\u6700\u5927\u9650\u5236\u4e3a 5MB\u3002')\n\n# models.py\nclass Document(models.Model):\n file = models.FileField(\n upload_to='documents/',\n validators=[validate_file_extension, validate_file_size]\n )\n```\n\n### \u5b89\u5168\u6587\u4ef6\u5b58\u50a8\n\n```python\n# settings.py\nMEDIA_ROOT = '/var/www/media/'\nMEDIA_URL = '/media/'\n\n# \u751f\u4ea7\u73af\u5883\u4e2d\u4f7f\u7528\u72ec\u7acb\u7684\u5a92\u4f53\u6587\u4ef6\u57df\u540d\nMEDIA_DOMAIN = 'https://media.example.com'\n\n# \u4e0d\u8981\u76f4\u63a5\u63d0\u4f9b\u7528\u6237\u4e0a\u4f20\u7684\u6587\u4ef6\n# \u5bf9\u9759\u6001\u6587\u4ef6\u4f7f\u7528 whitenoise \u6216 CDN\n# \u5bf9\u5a92\u4f53\u6587\u4ef6\u4f7f\u7528\u72ec\u7acb\u670d\u52a1\u5668\u6216 S3\n```\n\n## API \u5b89\u5168\n\n### \u901f\u7387\u9650\u5236\uff08Rate Limiting\uff09\n\n```python\n# settings.py\nREST_FRAMEWORK = {\n 'DEFAULT_THROTTLE_CLASSES': [\n 'rest_framework.throttling.AnonRateThrottle',\n 'rest_framework.throttling.UserRateThrottle'\n ],\n 'DEFAULT_THROTTLE_RATES': {\n 'anon': '100/day',\n 'user': '1000/day',\n 'upload': '10/hour',\n }\n}\n\n# \u81ea\u5b9a\u4e49\u8282\u6d41\uff08Throttle\uff09\nfrom rest_framework.throttling import UserRateThrottle\n\nclass BurstRateThrottle(UserRateThrottle):\n scope = 'burst'\n rate = '60/min'\n\nclass SustainedRateThrottle(UserRateThrottle):\n scope = 'sustained'\n rate = '1000/day'\n```\n\n### API \u8eab\u4efd\u8ba4\u8bc1\n\n```python\n# settings.py\nREST_FRAMEWORK = {\n 'DEFAULT_AUTHENTICATION_CLASSES': [\n 'rest_framework.authentication.TokenAuthentication',\n 'rest_framework.authentication.SessionAuthentication',\n 'rest_framework_simplejwt.authentication.JWTAuthentication',\n ],\n 'DEFAULT_PERMISSION_CLASSES': [\n 'rest_framework.permissions.IsAuthenticated',\n ],\n}\n\n# views.py\nfrom rest_framework.decorators import api_view, permission_classes\nfrom rest_framework.permissions import IsAuthenticated\n\n@api_view(['GET', 'POST'])\n@permission_classes([IsAuthenticated])\ndef protected_view(request):\n return Response({'message': 'You are authenticated'})\n```\n\n## \u5b89\u5168\u5934\u90e8\uff08Security Headers\uff09\n\n### \u5185\u5bb9\u5b89\u5168\u7b56\u7565\uff08CSP\uff09\n\n```python\n# settings.py\nCSP_DEFAULT_SRC = \"'self'\"\nCSP_SCRIPT_SRC = \"'self' https://cdn.example.com\"\nCSP_STYLE_SRC = \"'self' 'unsafe-inline'\"\nCSP_IMG_SRC = \"'self' data: https:\"\nCSP_CONNECT_SRC = \"'self' https://api.example.com\"\n\n# \u4e2d\u95f4\u4ef6\nclass CSPMiddleware:\n def __init__(self, get_response):\n self.get_response = get_response\n\n def __call__(self, request):\n response = self.get_response(request)\n response['Content-Security-Policy'] = (\n f\"default-src {CSP_DEFAULT_SRC}; \"\n f\"script-src {CSP_SCRIPT_SRC}; \"\n f\"style-src {CSP_STYLE_SRC}; \"\n f\"img-src {CSP_IMG_SRC}; \"\n f\"connect-src {CSP_CONNECT_SRC}\"\n )\n return response\n```\n\n## \u73af\u5883\u53d8\u91cf\n\n### \u7ba1\u7406\u5bc6\u94a5\n\n```python\n# \u4f7f\u7528 python-decouple \u6216 django-environ\nimport environ\n\nenv = environ.Env(\n # \u8bbe\u7f6e\u7c7b\u578b\u8f6c\u6362\u3001\u9ed8\u8ba4\u503c\n DEBUG=(bool, False)\n)\n\n# \u8bfb\u53d6 .env \u6587\u4ef6\nenviron.Env.read_env()\n\nSECRET_KEY = env('DJANGO_SECRET_KEY')\nDATABASE_URL = env('DATABASE_URL')\nALLOWED_HOSTS = env.list('ALLOWED_HOSTS')\n\n# .env \u6587\u4ef6\uff08\u5207\u52ff\u63d0\u4ea4\u6b64\u6587\u4ef6\uff09\nDEBUG=False\nSECRET_KEY=your-secret-key-here\nDATABASE_URL=postgresql://user:password@localhost:5432/dbname\nALLOWED_HOSTS=example.com,www.example.com\n```\n\n## \u8bb0\u5f55\u5b89\u5168\u4e8b\u4ef6\n\n```python\n# settings.py\nLOGGING = {\n 'version': 1,\n 'disable_existing_loggers': False,\n 'handlers': {\n 'file': {\n 'level': 'WARNING',\n 'class': 'logging.FileHandler',\n 'filename': '/var/log/django/security.log',\n },\n 'console': {\n 'level': 'INFO',\n 'class': 'logging.StreamHandler',\n },\n },\n 'loggers': {\n 'django.security': {\n 'handlers': ['file', 'console'],\n 'level': 'WARNING',\n 'propagate': True,\n },\n 'django.request': {\n 'handlers': ['file'],\n 'level': 'ERROR',\n 'propagate': False,\n },\n },\n}\n```\n\n## \u5feb\u901f\u5b89\u5168\u81ea\u68c0\u8868\n\n| \u68c0\u67e5\u9879 | \u63cf\u8ff0 |\n|-------|-------------|\n| `DEBUG = False` | \u5207\u52ff\u5728\u751f\u4ea7\u73af\u5883\u4e2d\u5f00\u542f DEBUG |\n| \u4ec5\u9650 HTTPS | \u5f3a\u5236\u4f7f\u7528 SSL\uff0c\u542f\u7528\u5b89\u5168 Cookie |\n| \u5f3a\u5bc6\u94a5 | \u4e3a SECRET_KEY \u4f7f\u7528\u73af\u5883\u53d8\u91cf |\n| \u5bc6\u7801\u6821\u9a8c | \u542f\u7528\u6240\u6709\u5bc6\u7801\u9a8c\u8bc1\u5668 |\n| CSRF \u9632\u62a4 | \u9ed8\u8ba4\u5df2\u542f\u7528\uff0c\u8bf7\u52ff\u7981\u7528 |\n| XSS \u9632\u62a4 | Django \u81ea\u52a8\u8f6c\u4e49\uff0c\u8bf7\u52ff\u5bf9\u7528\u6237\u8f93\u5165\u4f7f\u7528 `|safe` |\n| SQL \u6ce8\u5165 | \u4f7f\u7528 ORM\uff0c\u5207\u52ff\u5728\u67e5\u8be2\u4e2d\u62fc\u63a5\u5b57\u7b26\u4e32 |\n| \u6587\u4ef6\u4e0a\u4f20 | \u9a8c\u8bc1\u6587\u4ef6\u7c7b\u578b\u548c\u5927\u5c0f |\n| \u901f\u7387\u9650\u5236 | \u5bf9 API \u7aef\u70b9\u8fdb\u884c\u8282\u6d41 |\n| \u5b89\u5168\u5934\u90e8 | \u914d\u7f6e CSP, X-Frame-Options, HSTS |\n| \u65e5\u5fd7\u8bb0\u5f55 | \u8bb0\u5f55\u5b89\u5168\u4e8b\u4ef6 |\n| \u66f4\u65b0 | \u4fdd\u6301 Django \u53ca\u5176\u4f9d\u8d56\u9879\u4e3a\u6700\u65b0\u7248\u672c |\n\n\u8bb0\u4f4f\uff1a\u5b89\u5168\u662f\u4e00\u4e2a\u6301\u7eed\u7684\u8fc7\u7a0b\uff0c\u800c\u4e0d\u662f\u4e00\u4e2a\u4ea7\u54c1\u3002\u8bf7\u5b9a\u671f\u5ba1\u67e5\u5e76\u66f4\u65b0\u4f60\u7684\u5b89\u5168\u5b9e\u8df5\u3002\n" + }, + "/Users/Library/Applications/xx/code/github/everything-claude-code-zh/skills/django-patterns/SKILL.md": { + "md5": "9f3de615e13de63294c92eb0d335fb20", + "content": "---\nname: django-patterns\ndescription: Django \u67b6\u6784\u6a21\u5f0f\u3001\u4f7f\u7528 DRF \u7684 REST API \u8bbe\u8ba1\u3001ORM \u6700\u4f73\u5b9e\u8df5\u3001\u7f13\u5b58\u3001\u4fe1\u53f7\uff08Signals\uff09\u3001\u4e2d\u95f4\u4ef6\uff08Middleware\uff09\u4ee5\u53ca\u751f\u4ea7\u7ea7 Django \u5e94\u7528\u3002\n---\n\n# Django \u5f00\u53d1\u6a21\u5f0f\n\n\u9002\u7528\u4e8e\u53ef\u6269\u5c55\u3001\u53ef\u7ef4\u62a4\u5e94\u7528\u7a0b\u5e8f\u7684\u751f\u4ea7\u7ea7 Django \u67b6\u6784\u6a21\u5f0f\u3002\n\n## \u4f55\u65f6\u6fc0\u6d3b\n\n- \u6784\u5efa Django Web \u5e94\u7528\u7a0b\u5e8f\u65f6\n- \u8bbe\u8ba1 Django REST Framework (DRF) API \u65f6\n- \u5904\u7406 Django ORM \u548c\u6a21\u578b\u65f6\n* \u8bbe\u7f6e Django \u9879\u76ee\u7ed3\u6784\u65f6\n* \u5b9e\u73b0\u7f13\u5b58\uff08Caching\uff09\u3001\u4fe1\u53f7\uff08Signals\uff09\u3001\u4e2d\u95f4\u4ef6\uff08Middleware\uff09\u65f6\n\n## \u9879\u76ee\u7ed3\u6784\n\n### \u63a8\u8350\u5e03\u5c40\n\n```\nmyproject/\n\u251c\u2500\u2500 config/\n\u2502 \u251c\u2500\u2500 __init__.py\n\u2502 \u251c\u2500\u2500 settings/\n\u2502 \u2502 \u251c\u2500\u2500 __init__.py\n\u2502 \u2502 \u251c\u2500\u2500 base.py # \u57fa\u7840\u8bbe\u7f6e\n\u2502 \u2502 \u251c\u2500\u2500 development.py # \u5f00\u53d1\u73af\u5883\u8bbe\u7f6e\n\u2502 \u2502 \u251c\u2500\u2500 production.py # \u751f\u4ea7\u73af\u5883\u8bbe\u7f6e\n\u2502 \u2502 \u2514\u2500\u2500 test.py # \u6d4b\u8bd5\u73af\u5883\u8bbe\u7f6e\n\u2502 \u251c\u2500\u2500 urls.py\n\u2502 \u251c\u2500\u2500 wsgi.py\n\u2502 \u2514\u2500\u2500 asgi.py\n\u251c\u2500\u2500 manage.py\n\u2514\u2500\u2500 apps/\n \u251c\u2500\u2500 __init__.py\n \u251c\u2500\u2500 users/\n \u2502 \u251c\u2500\u2500 __init__.py\n \u2502 \u251c\u2500\u2500 models.py\n \u2502 \u251c\u2500\u2500 views.py\n \u2502 \u251c\u2500\u2500 serializers.py\n \u2502 \u251c\u2500\u2500 urls.py\n \u2502 \u251c\u2500\u2500 permissions.py\n \u2502 \u251c\u2500\u2500 filters.py\n \u2502 \u251c\u2500\u2500 services.py\n \u2502 \u2514\u2500\u2500 tests/\n \u2514\u2500\u2500 products/\n \u2514\u2500\u2500 ...\n```\n\n### \u5206\u79bb\u8bbe\u7f6e\u6a21\u5f0f\uff08Split Settings Pattern\uff09\n\n```python\n# config/settings/base.py\nfrom pathlib import Path\n\nBASE_DIR = Path(__file__).resolve().parent.parent.parent\n\nSECRET_KEY = env('DJANGO_SECRET_KEY')\nDEBUG = False\nALLOWED_HOSTS = []\n\nINSTALLED_APPS = [\n 'django.contrib.admin',\n 'django.contrib.auth',\n 'django.contrib.contenttypes',\n 'django.contrib.sessions',\n 'django.contrib.messages',\n 'django.contrib.staticfiles',\n 'rest_framework',\n 'rest_framework.authtoken',\n 'corsheaders',\n # \u672c\u5730\u5e94\u7528\n 'apps.users',\n 'apps.products',\n]\n\nMIDDLEWARE = [\n 'django.middleware.security.SecurityMiddleware',\n 'whitenoise.middleware.WhiteNoiseMiddleware',\n 'django.contrib.sessions.middleware.SessionMiddleware',\n 'corsheaders.middleware.CorsMiddleware',\n 'django.middleware.common.CommonMiddleware',\n 'django.middleware.csrf.CsrfViewMiddleware',\n 'django.contrib.auth.middleware.AuthenticationMiddleware',\n 'django.contrib.messages.middleware.MessageMiddleware',\n 'django.middleware.clickjacking.XFrameOptionsMiddleware',\n]\n\nROOT_URLCONF = 'config.urls'\nWSGI_APPLICATION = 'config.wsgi.application'\n\nDATABASES = {\n 'default': {\n 'ENGINE': 'django.db.backends.postgresql',\n 'NAME': env('DB_NAME'),\n 'USER': env('DB_USER'),\n 'PASSWORD': env('DB_PASSWORD'),\n 'HOST': env('DB_HOST'),\n 'PORT': env('DB_PORT', default='5432'),\n }\n}\n\n# config/settings/development.py\nfrom .base import *\n\nDEBUG = True\nALLOWED_HOSTS = ['localhost', '127.0.0.1']\n\nDATABASES['default']['NAME'] = 'myproject_dev'\n\nINSTALLED_APPS += ['debug_toolbar']\n\nMIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware']\n\nEMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'\n\n# config/settings/production.py\nfrom .base import *\n\nDEBUG = False\nALLOWED_HOSTS = env.list('ALLOWED_HOSTS')\nSECURE_SSL_REDIRECT = True\nSESSION_COOKIE_SECURE = True\nCSRF_COOKIE_SECURE = True\nSECURE_HSTS_SECONDS = 31536000\nSECURE_HSTS_INCLUDE_SUBDOMAINS = True\nSECURE_HSTS_PRELOAD = True\n\n# \u65e5\u5fd7\u914d\u7f6e\nLOGGING = {\n 'version': 1,\n 'disable_existing_loggers': False,\n 'handlers': {\n 'file': {\n 'level': 'WARNING',\n 'class': 'logging.FileHandler',\n 'filename': '/var/log/django/django.log',\n },\n },\n 'loggers': {\n 'django': {\n 'handlers': ['file'],\n 'level': 'WARNING',\n 'propagate': True,\n },\n },\n}\n```\n\n## \u6a21\u578b\u8bbe\u8ba1\u6a21\u5f0f\n\n### \u6a21\u578b\u6700\u4f73\u5b9e\u8df5\n\n```python\nfrom django.db import models\nfrom django.contrib.auth.models import AbstractUser\nfrom django.core.validators import MinValueValidator, MaxValueValidator\n\nclass User(AbstractUser):\n \"\"\"\u6269\u5c55 AbstractUser \u7684\u81ea\u5b9a\u4e49\u7528\u6237\u6a21\u578b\u3002\"\"\"\n email = models.EmailField(unique=True)\n phone = models.CharField(max_length=20, blank=True)\n birth_date = models.DateField(null=True, blank=True)\n\n USERNAME_FIELD = 'email'\n REQUIRED_FIELDS = ['username']\n\n class Meta:\n db_table = 'users'\n verbose_name = 'user'\n verbose_name_plural = 'users'\n ordering = ['-date_joined']\n\n def __str__(self):\n return self.email\n\n def get_full_name(self):\n return f\"{self.first_name} {self.last_name}\".strip()\n\nclass Product(models.Model):\n \"\"\"\u5e26\u6709\u9002\u5f53\u5b57\u6bb5\u914d\u7f6e\u7684\u4ea7\u54c1\u6a21\u578b\u3002\"\"\"\n name = models.CharField(max_length=200)\n slug = models.SlugField(unique=True, max_length=250)\n description = models.TextField(blank=True)\n price = models.DecimalField(\n max_digits=10,\n decimal_places=2,\n validators=[MinValueValidator(0)]\n )\n stock = models.PositiveIntegerField(default=0)\n is_active = models.BooleanField(default=True)\n category = models.ForeignKey(\n 'Category',\n on_delete=models.CASCADE,\n related_name='products'\n )\n tags = models.ManyToManyField('Tag', blank=True, related_name='products')\n created_at = models.DateTimeField(auto_now_add=True)\n updated_at = models.DateTimeField(auto_now=True)\n\n class Meta:\n db_table = 'products'\n ordering = ['-created_at']\n indexes = [\n models.Index(fields=['slug']),\n models.Index(fields=['-created_at']),\n models.Index(fields=['category', 'is_active']),\n ]\n constraints = [\n models.CheckConstraint(\n check=models.Q(price__gte=0),\n name='price_non_negative'\n )\n ]\n\n def __str__(self):\n return self.name\n\n def save(self, *args, **kwargs):\n if not self.slug:\n self.slug = slugify(self.name)\n super().save(*args, **kwargs)\n```\n\n### QuerySet \u6700\u4f73\u5b9e\u8df5\n\n```python\nfrom django.db import models\n\nclass ProductQuerySet(models.QuerySet):\n \"\"\"Product \u6a21\u578b\u7684\u81ea\u5b9a\u4e49 QuerySet\u3002\"\"\"\n\n def active(self):\n \"\"\"\u4ec5\u8fd4\u56de\u5df2\u6fc0\u6d3b\u7684\u4ea7\u54c1\u3002\"\"\"\n return self.filter(is_active=True)\n\n def with_category(self):\n \"\"\"\u4f7f\u7528 select_related \u52a0\u8f7d\u5206\u7c7b\uff0c\u907f\u514d N+1 \u67e5\u8be2\u3002\"\"\"\n return self.select_related('category')\n\n def with_tags(self):\n \"\"\"\u4f7f\u7528 prefetch_related \u9884\u52a0\u8f7d\u591a\u5bf9\u591a\u5173\u7cfb\u7684\u6807\u7b7e\u3002\"\"\"\n return self.prefetch_related('tags')\n\n def in_stock(self):\n \"\"\"\u8fd4\u56de\u5e93\u5b58 > 0 \u7684\u4ea7\u54c1\u3002\"\"\"\n return self.filter(stock__gt=0)\n\n def search(self, query):\n \"\"\"\u6309\u540d\u79f0\u6216\u63cf\u8ff0\u641c\u7d22\u4ea7\u54c1\u3002\"\"\"\n return self.filter(\n models.Q(name__icontains=query) |\n models.Q(description__icontains=query)\n )\n\nclass Product(models.Model):\n # ... \u5b57\u6bb5 ...\n\n objects = ProductQuerySet.as_manager() # \u4f7f\u7528\u81ea\u5b9a\u4e49 QuerySet\n\n# \u7528\u6cd5\nProduct.objects.active().with_category().in_stock()\n```\n\n### \u7ba1\u7406\u5668\uff08Manager\uff09\u65b9\u6cd5\n\n```python\nclass ProductManager(models.Manager):\n \"\"\"\u7528\u4e8e\u590d\u6742\u67e5\u8be2\u7684\u81ea\u5b9a\u4e49\u7ba1\u7406\u5668\u3002\"\"\"\n\n def get_or_none(self, **kwargs):\n \"\"\"\u8fd4\u56de\u5bf9\u8c61\uff0c\u6216\u8005\u5728\u4e0d\u5b58\u5728\u65f6\u8fd4\u56de None \u800c\u4e0d\u662f\u629b\u51fa DoesNotExist\u3002\"\"\"\n try:\n return self.get(**kwargs)\n except self.model.DoesNotExist:\n return None\n\n def create_with_tags(self, name, price, tag_names):\n \"\"\"\u521b\u5efa\u4ea7\u54c1\u5e76\u5173\u8054\u6807\u7b7e\u3002\"\"\"\n product = self.create(name=name, price=price)\n tags = [Tag.objects.get_or_create(name=name)[0] for name in tag_names]\n product.tags.set(tags)\n return product\n\n def bulk_update_stock(self, product_ids, quantity):\n \"\"\"\u6279\u91cf\u66f4\u65b0\u591a\u4e2a\u4ea7\u54c1\u7684\u5e93\u5b58\u3002\"\"\"\n return self.filter(id__in=product_ids).update(stock=quantity)\n\n# \u5728\u6a21\u578b\u4e2d\nclass Product(models.Model):\n # ... \u5b57\u6bb5 ...\n custom = ProductManager()\n```\n\n## Django REST Framework \u6a21\u5f0f\n\n### \u5e8f\u5217\u5316\u5668\uff08Serializer\uff09\u6a21\u5f0f\n\n```python\nfrom rest_framework import serializers\nfrom django.contrib.auth.password_validation import validate_password\nfrom .models import Product, User\n\nclass ProductSerializer(serializers.ModelSerializer):\n \"\"\"Product \u6a21\u578b\u7684\u5e8f\u5217\u5316\u5668\u3002\"\"\"\n\n category_name = serializers.CharField(source='category.name', read_only=True)\n average_rating = serializers.FloatField(read_only=True)\n discount_price = serializers.SerializerMethodField()\n\n class Meta:\n model = Product\n fields = [\n 'id', 'name', 'slug', 'description', 'price',\n 'discount_price', 'stock', 'category_name',\n 'average_rating', 'created_at'\n ]\n read_only_fields = ['id', 'slug', 'created_at']\n\n def get_discount_price(self, obj):\n \"\"\"\u5982\u679c\u9002\u7528\uff0c\u8ba1\u7b97\u6298\u6263\u4ef7\u3002\"\"\"\n if hasattr(obj, 'discount') and obj.discount:\n return obj.price * (1 - obj.discount.percent / 100)\n return obj.price\n\n def validate_price(self, value):\n \"\"\"\u786e\u4fdd\u4ef7\u683c\u975e\u8d1f\u3002\"\"\"\n if value < 0:\n raise serializers.ValidationError(\"\u4ef7\u683c\u4e0d\u80fd\u4e3a\u8d1f\u6570\u3002\")\n return value\n\nclass ProductCreateSerializer(serializers.ModelSerializer):\n \"\"\"\u7528\u4e8e\u521b\u5efa\u4ea7\u54c1\u7684\u5e8f\u5217\u5316\u5668\u3002\"\"\"\n\n class Meta:\n model = Product\n fields = ['name', 'description', 'price', 'stock', 'category']\n\n def validate(self, data):\n \"\"\"\u591a\u5b57\u6bb5\u7684\u81ea\u5b9a\u4e49\u6821\u9a8c\u3002\"\"\"\n if data['price'] > 10000 and data['stock'] > 100:\n raise serializers.ValidationError(\n \"\u9ad8\u4ef7\u503c\u4ea7\u54c1\u4e0d\u80fd\u6709\u5927\u91cf\u5e93\u5b58\u3002\"\n )\n return data\n\nclass UserRegistrationSerializer(serializers.ModelSerializer):\n \"\"\"\u7528\u6237\u6ce8\u518c\u5e8f\u5217\u5316\u5668\u3002\"\"\"\n\n password = serializers.CharField(\n write_only=True,\n required=True,\n validators=[validate_password],\n style={'input_type': 'password'}\n )\n password_confirm = serializers.CharField(write_only=True, style={'input_type': 'password'})\n\n class Meta:\n model = User\n fields = ['email', 'username', 'password', 'password_confirm']\n\n def validate(self, data):\n \"\"\"\u6821\u9a8c\u4e24\u6b21\u8f93\u5165\u7684\u5bc6\u7801\u662f\u5426\u4e00\u81f4\u3002\"\"\"\n if data['password'] != data['password_confirm']:\n raise serializers.ValidationError({\n \"password_confirm\": \"\u5bc6\u7801\u5b57\u6bb5\u4e0d\u5339\u914d\u3002\"\n })\n return data\n\n def create(self, validated_data):\n \"\"\"\u521b\u5efa\u5e76\u4fdd\u5b58\u52a0\u5bc6\u540e\u7684\u5bc6\u7801\u3002\"\"\"\n validated_data.pop('password_confirm')\n password = validated_data.pop('password')\n user = User.objects.create(**validated_data)\n user.set_password(password)\n user.save()\n return user\n```\n\n### \u89c6\u56fe\u96c6\uff08ViewSet\uff09\u6a21\u5f0f\n\n```python\nfrom rest_framework import viewsets, status, filters\nfrom rest_framework.decorators import action\nfrom rest_framework.response import Response\nfrom rest_framework.permissions import IsAuthenticated, IsAdminUser\nfrom django_filters.rest_framework import DjangoFilterBackend\nfrom .models import Product\nfrom .serializers import ProductSerializer, ProductCreateSerializer\nfrom .permissions import IsOwnerOrReadOnly\nfrom .filters import ProductFilter\nfrom .filters import ProductFilter\nfrom .services import ProductService\n\nclass ProductViewSet(viewsets.ModelViewSet):\n \"\"\"Product \u6a21\u578b\u7684\u89c6\u56fe\u96c6\u3002\"\"\"\n\n queryset = Product.objects.select_related('category').prefetch_related('tags')\n permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]\n filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]\n filterset_class = ProductFilter\n search_fields = ['name', 'description']\n ordering_fields = ['price', 'created_at', 'name']\n ordering = ['-created_at']\n\n def get_serializer_class(self):\n \"\"\"\u6839\u636e\u64cd\u4f5c\uff08action\uff09\u8fd4\u56de\u9002\u5f53\u7684\u5e8f\u5217\u5316\u5668\u7c7b\u3002\"\"\"\n if self.action == 'create':\n return ProductCreateSerializer\n return ProductSerializer\n\n def perform_create(self, serializer):\n \"\"\"\u5728\u4fdd\u5b58\u65f6\u5173\u8054\u5f53\u524d\u7528\u6237\u4e0a\u4e0b\u6587\u3002\"\"\"\n serializer.save(created_by=self.request.user)\n\n @action(detail=False, methods=['get'])\n def featured(self, request):\n \"\"\"\u8fd4\u56de\u63a8\u8350\u4ea7\u54c1\u3002\"\"\"\n featured = self.queryset.filter(is_featured=True)[:10]\n serializer = self.get_serializer(featured, many=True)\n return Response(serializer.data)\n\n @action(detail=True, methods=['post'])\n def purchase(self, request, pk=None):\n \"\"\"\u8d2d\u4e70\u4ea7\u54c1\u3002\"\"\"\n product = self.get_object()\n service = ProductService()\n result = service.purchase(product, request.user)\n return Response(result, status=status.HTTP_201_CREATED)\n\n @action(detail=False, methods=['get'], permission_classes=[IsAuthenticated])\n def my_products(self, request):\n \"\"\"\u8fd4\u56de\u7531\u5f53\u524d\u7528\u6237\u521b\u5efa\u7684\u4ea7\u54c1\u3002\"\"\"\n products = self.queryset.filter(created_by=request.user)\n page = self.paginate_queryset(products)\n serializer = self.get_serializer(page, many=True)\n return self.get_paginated_response(serializer.data)\n```\n\n### \u81ea\u5b9a\u4e49\u64cd\u4f5c\n\n```python\nfrom rest_framework.decorators import api_view, permission_classes\nfrom rest_framework.permissions import IsAuthenticated\nfrom rest_framework.response import Response\n\n@api_view(['POST'])\n@permission_classes([IsAuthenticated])\ndef add_to_cart(request):\n \"\"\"\u5c06\u4ea7\u54c1\u6dfb\u52a0\u5230\u7528\u6237\u8d2d\u7269\u8f66\u3002\"\"\"\n product_id = request.data.get('product_id')\n quantity = request.data.get('quantity', 1)\n\n try:\n product = Product.objects.get(id=product_id)\n except Product.DoesNotExist:\n return Response(\n {'error': '\u4ea7\u54c1\u672a\u627e\u5230'},\n status=status.HTTP_404_NOT_FOUND\n )\n\n cart, _ = Cart.objects.get_or_create(user=request.user)\n CartItem.objects.create(\n cart=cart,\n product=product,\n quantity=quantity\n )\n\n return Response({'message': '\u5df2\u6dfb\u52a0\u5230\u8d2d\u7269\u8f66'}, status=status.HTTP_201_CREATED)\n```\n\n## \u670d\u52a1\u5c42\u6a21\u5f0f\uff08Service Layer Pattern\uff09\n\n```python\n# apps/orders/services.py\nfrom typing import Optional\nfrom django.db import transaction\nfrom .models import Order, OrderItem\n\nclass OrderService:\n \"\"\"\u8ba2\u5355\u76f8\u5173\u4e1a\u52a1\u903b\u8f91\u7684\u670d\u52a1\u5c42\u3002\"\"\"\n\n @staticmethod\n @transaction.atomic\n def create_order(user, cart: Cart) -> Order:\n \"\"\"\u4ece\u8d2d\u7269\u8f66\u521b\u5efa\u8ba2\u5355\u3002\"\"\"\n order = Order.objects.create(\n user=user,\n total_price=cart.total_price\n )\n\n for item in cart.items.all():\n OrderItem.objects.create(\n order=order,\n product=item.product,\n quantity=item.quantity,\n price=item.product.price\n )\n\n # \u6e05\u7a7a\u8d2d\u7269\u8f66\n cart.items.all().delete()\n\n return order\n\n @staticmethod\n def process_payment(order: Order, payment_data: dict) -> bool:\n \"\"\"\u5904\u7406\u8ba2\u5355\u652f\u4ed8\u3002\"\"\"\n # \u4e0e\u652f\u4ed8\u7f51\u5173\u96c6\u6210\n payment = PaymentGateway.charge(\n amount=order.total_price,\n token=payment_data['token']\n )\n\n if payment.success:\n order.status = Order.Status.PAID\n order.save()\n # \u53d1\u9001\u786e\u8ba4\u90ae\u4ef6\n OrderService.send_confirmation_email(order)\n return True\n\n return False\n\n @staticmethod\n def send_confirmation_email(order: Order):\n \"\"\"\u53d1\u9001\u8ba2\u5355\u786e\u8ba4\u90ae\u4ef6\u3002\"\"\"\n # \u90ae\u4ef6\u53d1\u9001\u903b\u8f91\n pass\n```\n\n## \u7f13\u5b58\u7b56\u7565\uff08Caching Strategies\uff09\n\n### \u89c6\u56fe\u7ea7\u7f13\u5b58\n\n```python\nfrom django.views.decorators.cache import cache_page\nfrom django.utils.decorators import method_decorator\n\n@method_decorator(cache_page(60 * 15), name='dispatch') # 15 \u5206\u949f\nclass ProductListView(generic.ListView):\n model = Product\n template_name = 'products/list.html'\n context_object_name = 'products'\n```\n\n### \u6a21\u677f\u7247\u6bb5\u7f13\u5b58\n\n```django\n{% load cache %}\n{% cache 500 sidebar %}\n ... \u8017\u65f6\u7684\u4fa7\u8fb9\u680f\u5185\u5bb9 ...\n{% endcache %}\n```\n\n### \u4f4e\u7ea7\u7f13\u5b58\n\n```python\nfrom django.core.cache import cache\n\ndef get_featured_products():\n \"\"\"\u901a\u8fc7\u7f13\u5b58\u83b7\u53d6\u63a8\u8350\u4ea7\u54c1\u3002\"\"\"\n cache_key = 'featured_products'\n products = cache.get(cache_key)\n\n if products is None:\n products = list(Product.objects.filter(is_featured=True))\n cache.set(cache_key, products, timeout=60 * 15) # 15 \u5206\u949f\n\n return products\n```\n\n### QuerySet \u7f13\u5b58\n\n```python\nfrom django.core.cache import cache\n\ndef get_popular_categories():\n cache_key = 'popular_categories'\n categories = cache.get(cache_key)\n\n if categories is None:\n categories = list(Category.objects.annotate(\n product_count=Count('products')\n ).filter(product_count__gt=10).order_by('-product_count')[:20])\n cache.set(cache_key, categories, timeout=60 * 60) # 1 \u5c0f\u65f6\n\n return categories\n```\n\n## \u4fe1\u53f7\uff08Signals\uff09\n\n### \u4fe1\u53f7\u6a21\u5f0f\n\n```python\n# apps/users/signals.py\nfrom django.db.models.signals import post_save\nfrom django.dispatch import receiver\nfrom django.contrib.auth import get_user_model\nfrom .models import Profile\n\nUser = get_user_model()\n\n@receiver(post_save, sender=User)\ndef create_user_profile(sender, instance, created, **kwargs):\n \"\"\"\u5f53\u7528\u6237\u521b\u5efa\u65f6\u540c\u6b65\u521b\u5efa Profile\u3002\"\"\"\n if created:\n Profile.objects.create(user=instance)\n\n@receiver(post_save, sender=User)\ndef save_user_profile(sender, instance, **kwargs):\n \"\"\"\u5f53\u7528\u6237\u4fdd\u5b58\u65f6\u540c\u6b65\u4fdd\u5b58 Profile\u3002\"\"\"\n instance.profile.save()\n\n# apps/users/apps.py\nfrom django.apps import AppConfig\n\nclass UsersConfig(AppConfigConfig):\n default_auto_field = 'django.db.models.BigAutoField'\n name = 'apps.users'\n\n def ready(self):\n \"\"\"\u5728\u5e94\u7528\u51c6\u5907\u5c31\u7eea\u65f6\u5bfc\u5165\u4fe1\u53f7\u3002\"\"\"\n import apps.users.signals\n```\n\n## \u4e2d\u95f4\u4ef6\uff08Middleware\uff09\n\n### \u81ea\u5b9a\u4e49\u4e2d\u95f4\u4ef6\n\n```python\n# middleware/active_user_middleware.py\nimport time\nfrom django.utils.deprecation import MiddlewareMixin\n\nclass ActiveUserMiddleware(MiddlewareMixin):\n \"\"\"\u7528\u4e8e\u8ddf\u8e2a\u6d3b\u8dc3\u7528\u6237\u7684\u4e2d\u95f4\u4ef6\u3002\"\"\"\n\n def process_request(self, request):\n \"\"\"\u5904\u7406\u4f20\u5165\u8bf7\u6c42\u3002\"\"\"\n if request.user.is_authenticated:\n # \u66f4\u65b0\u6700\u540e\u6d3b\u8dc3\u65f6\u95f4\n request.user.last_active = timezone.now()\n request.user.save(update_fields=['last_active'])\n\nclass RequestLoggingMiddleware(MiddlewareMixin):\n \"\"\"\u7528\u4e8e\u8bb0\u5f55\u8bf7\u6c42\u65e5\u5fd7\u7684\u4e2d\u95f4\u4ef6\u3002\"\"\"\n\n def process_request(self, request):\n \"\"\"\u8bb0\u5f55\u8bf7\u6c42\u5f00\u59cb\u65f6\u95f4\u3002\"\"\"\n request.start_time = time.time()\n\n def process_response(self, request, response):\n \"\"\"\u8bb0\u5f55\u8bf7\u6c42\u8017\u65f6\u3002\"\"\"\n if hasattr(request, 'start_time'):\n duration = time.time() - request.start_time\n logger.info(f'{request.method} {request.path} - {response.status_code} - {duration:.3f}s')\n return response\n```\n\n## \u6027\u80fd\u4f18\u5316\uff08Performance Optimization\uff09\n\n### \u9632\u6b62 N+1 \u67e5\u8be2\n\n```python\n# \u5dee\u52b2 - N+1 \u67e5\u8be2\nproducts = Product.objects.all()\nfor product in products:\n print(product.category.name) # \u4e3a\u6bcf\u4e2a\u4ea7\u54c1\u5355\u72ec\u8fdb\u884c\u4e00\u6b21\u67e5\u8be2\n\n# \u4f18\u79c0 - \u4f7f\u7528 select_related \u8fdb\u884c\u5355\u6b21\u67e5\u8be2\nproducts = Product.objects.select_related('category').all()\nfor product in products:\n print(product.category.name)\n\n# \u4f18\u79c0 - \u5bf9\u591a\u5bf9\u591a\u5173\u7cfb\u4f7f\u7528 prefetch_related\nproducts = Product.objects.prefetch_related('tags').all()\nfor product in products:\n for tag in product.tags.all():\n print(tag.name)\n```\n\n### \u6570\u636e\u5e93\u7d22\u5f15\n\n```python\nclass Product(models.Model):\n name = models.CharField(max_length=200, db_index=True)\n slug = models.SlugField(unique=True)\n category = models.ForeignKey('Category', on_delete=models.CASCADE)\n created_at = models.DateTimeField(auto_now_add=True)\n\n class Meta:\n indexes = [\n models.Index(fields=['name']),\n models.Index(fields=['-created_at']),\n models.Index(fields=['category', 'created_at']),\n ]\n```\n\n### \u6279\u91cf\u64cd\u4f5c\n\n```python\n# \u6279\u91cf\u521b\u5efa\nProduct.objects.bulk_create([\n Product(name=f'Product {i}', price=10.00)\n for i in range(1000)\n])\n\n# \u6279\u91cf\u66f4\u65b0\nproducts = Product.objects.all()[:100]\nfor product in products:\n product.is_active = True\nProduct.objects.bulk_update(products, ['is_active'])\n\n# \u6279\u91cf\u5220\u9664\nProduct.objects.filter(stock=0).delete()\n```\n\n## \u5feb\u901f\u53c2\u8003\n\n| \u6a21\u5f0f | \u63cf\u8ff0 |\n|---------|-------------|\n| \u5206\u79bb\u8bbe\u7f6e\uff08Split settings\uff09 | \u533a\u5206\u5f00\u53d1/\u751f\u4ea7/\u6d4b\u8bd5\u73af\u5883\u914d\u7f6e |\n| \u81ea\u5b9a\u4e49 QuerySet | \u53ef\u590d\u7528\u7684\u67e5\u8be2\u65b9\u6cd5 |\n| \u670d\u52a1\u5c42\uff08Service Layer\uff09 | \u4e1a\u52a1\u903b\u8f91\u89e3\u8026 |\n| \u89c6\u56fe\u96c6\uff08ViewSet\uff09 | REST API \u7aef\u70b9\u5c01\u88c5 |\n| \u5e8f\u5217\u5316\u5668\u6821\u9a8c\uff08Serializer validation\uff09 | \u8bf7\u6c42/\u54cd\u5e94\u6570\u636e\u7684\u8f6c\u6362\u4e0e\u9a8c\u8bc1 |\n| select_related | \u5916\u952e\u5173\u7cfb\u4f18\u5316 |\n| prefetch_related | \u591a\u5bf9\u591a\u5173\u7cfb\u4f18\u5316 |\n| \u7f13\u5b58\u4f18\u5148\uff08Cache first\uff09 | \u5bf9\u8017\u65f6\u64cd\u4f5c\u8fdb\u884c\u7f13\u5b58 |\n| \u4fe1\u53f7\uff08Signals\uff09 | \u4e8b\u4ef6\u9a71\u52a8\u7684\u64cd\u4f5c |\n| \u4e2d\u95f4\u4ef6\uff08Middleware\uff09 | \u8bf7\u6c42/\u54cd\u5e94\u62e6\u622a\u5904\u7406 |\n\n\u8bb0\u4f4f\uff1aDjango \u63d0\u4f9b\u4e86\u8bb8\u591a\u6377\u5f84\uff0c\u4f46\u5bf9\u4e8e\u751f\u4ea7\u7ea7\u5e94\u7528\uff0c\u67b6\u6784\u548c\u7ec4\u7ec7\u7ed3\u6784\u6bd4\u7b80\u6d01\u7684\u4ee3\u7801\u66f4\u91cd\u8981\u3002\u8bf7\u4e3a\u53ef\u7ef4\u62a4\u6027\u800c\u6784\u5efa\u3002\n" + }, + "/Users/Library/Applications/xx/code/github/everything-claude-code-zh/skills/python-testing/SKILL.md": { + "md5": "2f656e55cdc1ed8b97b1e80819639dff", + "content": "---\nname: python-testing\ndescription: \u4f7f\u7528 pytest\u3001TDD \u65b9\u6cd5\u8bba\u3001fixtures\u3001mocking\u3001\u53c2\u6570\u5316\u548c\u4ee3\u7801\u8986\u76d6\u7387\u8981\u6c42\u7684 Python \u6d4b\u8bd5\u7b56\u7565\u3002\n---\n\n# Python \u6d4b\u8bd5\u6a21\u5f0f\uff08Python Testing Patterns\uff09\n\n\u4f7f\u7528 pytest\u3001\u6d4b\u8bd5\u9a71\u52a8\u5f00\u53d1\uff08TDD\uff09\u65b9\u6cd5\u8bba\u53ca\u6700\u4f73\u5b9e\u8df5\u7684 Python \u5e94\u7528\u7a0b\u5e8f\u5168\u9762\u6d4b\u8bd5\u7b56\u7565\u3002\n\n## \u4f55\u65f6\u6fc0\u6d3b\n\n- \u7f16\u5199\u65b0\u7684 Python \u4ee3\u7801\u65f6\uff08\u9075\u5faa TDD\uff1a\u7ea2\u3001\u7eff\u3001\u91cd\u6784\uff09\n- \u4e3a Python \u9879\u76ee\u8bbe\u8ba1\u6d4b\u8bd5\u5957\u4ef6\u65f6\n- \u5ba1\u67e5 Python \u6d4b\u8bd5\u8986\u76d6\u7387\u65f6\n- \u642d\u5efa\u6d4b\u8bd5\u57fa\u7840\u8bbe\u65bd\u65f6\n\n## \u6838\u5fc3\u6d4b\u8bd5\u7406\u5ff5\n\n### \u6d4b\u8bd5\u9a71\u52a8\u5f00\u53d1\uff08TDD\uff09\n\n\u59cb\u7ec8\u9075\u5faa TDD \u5faa\u73af\uff1a\n\n1. **\u7ea2\uff08RED\uff09**\uff1a\u4e3a\u671f\u671b\u7684\u884c\u4e3a\u7f16\u5199\u4e00\u4e2a\u5931\u8d25\u7684\u6d4b\u8bd5\n2. **\u7eff\uff08GREEN\uff09**\uff1a\u7f16\u5199\u6700\u5c11\u91cf\u7684\u4ee3\u7801\u4f7f\u6d4b\u8bd5\u901a\u8fc7\n3. **\u91cd\u6784\uff08REFACTOR\uff09**\uff1a\u5728\u4fdd\u6301\u6d4b\u8bd5\u901a\u8fc7\u7684\u524d\u63d0\u4e0b\u4f18\u5316\u4ee3\u7801\n\n```python\n# \u6b65\u9aa4 1\uff1a\u7f16\u5199\u5931\u8d25\u7684\u6d4b\u8bd5 (RED)\ndef test_add_numbers():\n result = add(2, 3)\n assert result == 5\n\n# \u6b65\u9aa4 2\uff1a\u7f16\u5199\u6700\u5c0f\u5b9e\u73b0 (GREEN)\ndef add(a, b):\n return a + b\n\n# \u6b65\u9aa4 3\uff1a\u6839\u636e\u9700\u8981\u8fdb\u884c\u91cd\u6784 (REFACTOR)\n```\n\n### \u8986\u76d6\u7387\u8981\u6c42\n\n- **\u76ee\u6807**\uff1a80% \u4ee5\u4e0a\u7684\u4ee3\u7801\u8986\u76d6\u7387\n- **\u5173\u952e\u8def\u5f84**\uff1a\u5fc5\u987b\u8fbe\u5230 100% \u8986\u76d6\u7387\n- \u4f7f\u7528 `pytest --cov` \u6765\u8861\u91cf\u8986\u76d6\u7387\n\n```bash\npytest --cov=mypackage --cov-report=term-missing --cov-report=html\n```\n\n## pytest \u57fa\u7840\n\n### \u57fa\u672c\u6d4b\u8bd5\u7ed3\u6784\n\n```python\nimport pytest\n\ndef test_addition():\n \"\"\"\u6d4b\u8bd5\u57fa\u7840\u52a0\u6cd5\u3002\"\"\"\n assert 2 + 2 == 4\n\ndef test_string_uppercase():\n \"\"\"\u6d4b\u8bd5\u5b57\u7b26\u4e32\u5927\u5199\u8f6c\u6362\u3002\"\"\"\n text = \"hello\"\n assert text.upper() == \"HELLO\"\n\ndef test_list_append():\n \"\"\"\u6d4b\u8bd5\u5217\u8868\u8ffd\u52a0\u3002\"\"\"\n items = [1, 2, 3]\n items.append(4)\n assert 4 in items\n assert len(items) == 4\n```\n\n### \u65ad\u8a00\uff08Assertions\uff09\n\n```python\n# \u76f8\u7b49\u6027\nassert result == expected\n\n# \u4e0d\u7b49\u6027\nassert result != unexpected\n\n# \u771f\u503c\nassert result # Truthy\nassert not result # Falsy\nassert result is True # \u7cbe\u786e\u4e3a True\nassert result is False # \u7cbe\u786e\u4e3a False\nassert result is None # \u7cbe\u786e\u4e3a None\n\n# \u6210\u5458\u8d44\u683c\nassert item in collection\nassert item not in collection\n\n# \u6bd4\u8f83\nassert result > 0\nassert 0 <= result <= 100\n\n# \u7c7b\u578b\u68c0\u67e5\nassert isinstance(result, str)\n\n# \u5f02\u5e38\u6d4b\u8bd5\uff08\u63a8\u8350\u505a\u6cd5\uff09\nwith pytest.raises(ValueError):\n raise ValueError(\"error message\")\n\n# \u68c0\u67e5\u5f02\u5e38\u6d88\u606f\nwith pytest.raises(ValueError, match=\"invalid input\"):\n raise ValueError(\"invalid input provided\")\n\n# \u68c0\u67e5\u5f02\u5e38\u5c5e\u6027\nwith pytest.raises(ValueError) as exc_info:\n raise ValueError(\"error message\")\nassert str(exc_info.value) == \"error message\"\n```\n\n## Fixtures\n\n### \u57fa\u7840 Fixture \u7528\u6cd5\n\n```python\nimport pytest\n\n@pytest.fixture\ndef sample_data():\n \"\"\"\u63d0\u4f9b\u793a\u4f8b\u6570\u636e\u7684 Fixture\u3002\"\"\"\n return {\"name\": \"Alice\", \"age\": 30}\n\ndef test_sample_data(sample_data):\n \"\"\"\u4f7f\u7528 fixture \u7684\u6d4b\u8bd5\u3002\"\"\"\n assert sample_data[\"name\"] == \"Alice\"\n assert sample_data[\"age\"] == 30\n```\n\n### \u5e26\u6709\u8bbe\u7f6e\uff08Setup\uff09\u548c\u6e05\u7406\uff08Teardown\uff09\u7684 Fixture\n\n```python\n@pytest.fixture\ndef database():\n \"\"\"\u5e26\u6709\u8bbe\u7f6e\u548c\u6e05\u7406\u903b\u8f91\u7684 Fixture\u3002\"\"\"\n # \u8bbe\u7f6e (Setup)\n db = Database(\":memory:\")\n db.create_tables()\n db.insert_test_data()\n\n yield db # \u63d0\u4f9b\u7ed9\u6d4b\u8bd5\u4f7f\u7528\n\n # \u6e05\u7406 (Teardown)\n db.close()\n\ndef test_database_query(database):\n \"\"\"\u6d4b\u8bd5\u6570\u636e\u5e93\u64cd\u4f5c\u3002\"\"\"\n result = database.query(\"SELECT * FROM users\")\n assert len(result) > 0\n```\n\n### Fixture \u4f5c\u7528\u57df\uff08Scopes\uff09\n\n```python\n# \u51fd\u6570\u7ea7\u4f5c\u7528\u57df (\u9ed8\u8ba4) - \u6bcf\u4e2a\u6d4b\u8bd5\u8fd0\u884c\u4e00\u6b21\n@pytest.fixture\ndef temp_file():\n with open(\"temp.txt\", \"w\") as f:\n yield f\n os.remove(\"temp.txt\")\n\n# \u6a21\u5757\u7ea7\u4f5c\u7528\u57df - \u6bcf\u4e2a\u6a21\u5757\u8fd0\u884c\u4e00\u6b21\n@pytest.fixture(scope=\"module\")\ndef module_db():\n db = Database(\":memory:\")\n db.create_tables()\n yield db\n db.close()\n\n# \u4f1a\u8bdd\u7ea7\u4f5c\u7528\u57df - \u6574\u4e2a\u6d4b\u8bd5\u4f1a\u8bdd\u8fd0\u884c\u4e00\u6b21\n@pytest.fixture(scope=\"session\")\ndef shared_resource():\n resource = ExpensiveResource()\n yield resource\n resource.cleanup()\n```\n\n### \u5e26\u53c2\u6570\u7684 Fixture\n\n```python\n@pytest.fixture(params=[1, 2, 3])\ndef number(request):\n \"\"\"\u53c2\u6570\u5316 Fixture\u3002\"\"\"\n return request.param\n\ndef test_numbers(number):\n \"\"\"\u6d4b\u8bd5\u5c06\u8fd0\u884c 3 \u6b21\uff0c\u6bcf\u4e2a\u53c2\u6570\u4e00\u6b21\u3002\"\"\"\n assert number > 0\n```\n\n### \u4f7f\u7528\u591a\u4e2a Fixture\n\n```python\n@pytest.fixture\ndef user():\n return User(id=1, name=\"Alice\")\n\n@pytest.fixture\ndef admin():\n return User(id=2, name=\"Admin\", role=\"admin\")\n\ndef test_user_admin_interaction(user, admin):\n \"\"\"\u540c\u65f6\u4f7f\u7528\u591a\u4e2a fixture \u7684\u6d4b\u8bd5\u3002\"\"\"\n assert admin.can_manage(user)\n```\n\n### \u81ea\u52a8\u4f7f\u7528\uff08Autouse\uff09 Fixture\n\n```python\n@pytest.fixture(autouse=True)\ndef reset_config():\n \"\"\"\u5728\u6bcf\u4e2a\u6d4b\u8bd5\u524d\u81ea\u52a8\u8fd0\u884c\u3002\"\"\"\n Config.reset()\n yield\n Config.cleanup()\n\ndef test_without_fixture_call():\n # reset_config \u4f1a\u81ea\u52a8\u8fd0\u884c\n assert Config.get_setting(\"debug\") is False\n```\n\n### \u7528\u4e8e\u5171\u4eab Fixture \u7684 conftest.py\n\n```python\n# tests/conftest.py\nimport pytest\n\n@pytest.fixture\ndef client():\n \"\"\"\u4f9b\u6240\u6709\u6d4b\u8bd5\u5171\u4eab\u7684 fixture\u3002\"\"\"\n app = create_app(testing=True)\n with app.test_client() as client:\n yield client\n\n@pytest.fixture\ndef auth_headers(client):\n \"\"\"\u4e3a API \u6d4b\u8bd5\u751f\u6210\u8ba4\u8bc1\u5934\u3002\"\"\"\n response = client.post(\"/api/login\", json={\n \"username\": \"test\",\n \"password\": \"test\"\n })\n token = response.json[\"token\"]\n return {\"Authorization\": f\"Bearer {token}\"}\n```\n\n## \u53c2\u6570\u5316\uff08Parametrization\uff09\n\n### \u57fa\u7840\u53c2\u6570\u5316\n\n```python\n@pytest.mark.parametrize(\"input,expected\", [\n (\"hello\", \"HELLO\"),\n (\"world\", \"WORLD\"),\n (\"PyThOn\", \"PYTHON\"),\n])\ndef test_uppercase(input, expected):\n \"\"\"\u6d4b\u8bd5\u5c06\u4f7f\u7528\u4e0d\u540c\u7684\u8f93\u5165\u8fd0\u884c 3 \u6b21\u3002\"\"\"\n assert input.upper() == expected\n```\n\n### \u591a\u4e2a\u53c2\u6570\n\n```python\n@pytest.mark.parametrize(\"a,b,expected\", [\n (2, 3, 5),\n (0, 0, 0),\n (-1, 1, 0),\n (100, 200, 300),\n])\ndef test_add(a, b, expected):\n \"\"\"\u4f7f\u7528\u591a\u7ec4\u8f93\u5165\u6d4b\u8bd5\u52a0\u6cd5\u3002\"\"\"\n assert add(a, b) == expected\n```\n\n### \u5e26 ID \u7684\u53c2\u6570\u5316\n\n```python\n@pytest.mark.parametrize(\"input,expected\", [\n (\"valid@email.com\", True),\n (\"invalid\", False),\n (\"@no-domain.com\", False),\n], ids=[\"valid-email\", \"missing-at\", \"missing-domain\"])\ndef test_email_validation(input, expected):\n \"\"\"\u901a\u8fc7\u53ef\u8bfb\u7684\u6d4b\u8bd5 ID \u6d4b\u8bd5\u7535\u5b50\u90ae\u4ef6\u9a8c\u8bc1\u3002\"\"\"\n assert is_valid_email(input) is expected\n```\n\n### \u53c2\u6570\u5316 Fixtures\n\n```python\n@pytest.fixture(params=[\"sqlite\", \"postgresql\", \"mysql\"])\ndef db(request):\n \"\"\"\u9488\u5bf9\u591a\u4e2a\u6570\u636e\u5e93\u540e\u7aef\u8fdb\u884c\u6d4b\u8bd5\u3002\"\"\"\n if request.param == \"sqlite\":\n return Database(\":memory:\")\n elif request.param == \"postgresql\":\n return Database(\"postgresql://localhost/test\")\n elif request.param == \"mysql\":\n return Database(\"mysql://localhost/test\")\n\ndef test_database_operations(db):\n \"\"\"\u6d4b\u8bd5\u5c06\u8fd0\u884c 3 \u6b21\uff0c\u6bcf\u4e2a\u6570\u636e\u5e93\u4e00\u6b21\u3002\"\"\"\n result = db.query(\"SELECT 1\")\n assert result is not None\n```\n\n## \u6807\u8bb0\uff08Markers\uff09\u4e0e\u6d4b\u8bd5\u9009\u62e9\n\n### \u81ea\u5b9a\u4e49\u6807\u8bb0\n\n```python\n# \u6807\u8bb0\u6162\u901f\u6d4b\u8bd5\n@pytest.mark.slow\ndef test_slow_operation():\n time.sleep(5)\n\n# \u6807\u8bb0\u96c6\u6210\u6d4b\u8bd5\n@pytest.mark.integration\ndef test_api_integration():\n response = requests.get(\"https://api.example.com\")\n assert response.status_code == 200\n\n# \u6807\u8bb0\u5355\u5143\u6d4b\u8bd5\n@pytest.mark.unit\ndef test_unit_logic():\n assert calculate(2, 3) == 5\n```\n\n### \u8fd0\u884c\u7279\u5b9a\u6d4b\u8bd5\n\n```bash\n# \u4ec5\u8fd0\u884c\u975e\u6162\u901f\u6d4b\u8bd5\npytest -m \"not slow\"\n\n# \u4ec5\u8fd0\u884c\u96c6\u6210\u6d4b\u8bd5\npytest -m integration\n\n# \u8fd0\u884c\u96c6\u6210\u6d4b\u8bd5\u6216\u6162\u901f\u6d4b\u8bd5\npytest -m \"integration or slow\"\n\n# \u8fd0\u884c\u6807\u8bb0\u4e3a\u5355\u5143\u6d4b\u8bd5\u4e14\u975e\u6162\u901f\u7684\u6d4b\u8bd5\npytest -m \"unit and not slow\"\n```\n\n### \u5728 pytest.ini \u4e2d\u914d\u7f6e\u6807\u8bb0\n\n```ini\n[pytest]\nmarkers =\n slow: \u5c06\u6d4b\u8bd5\u6807\u8bb0\u4e3a\u6162\u901f\n integration: \u5c06\u6d4b\u8bd5\u6807\u8bb0\u4e3a\u96c6\u6210\u6d4b\u8bd5\n unit: \u5c06\u6d4b\u8bd5\u6807\u8bb0\u4e3a\u5355\u5143\u6d4b\u8bd5\n django: \u5c06\u6d4b\u8bd5\u6807\u8bb0\u4e3a\u9700\u8981 Django \u73af\u5883\n```\n\n## Mocking \u4e0e Patching\n\n### Mock \u51fd\u6570\n\n```python\nfrom unittest.mock import patch, Mock\n\n@patch(\"mypackage.external_api_call\")\ndef test_with_mock(api_call_mock):\n \"\"\"\u4f7f\u7528 mock \u7684\u5916\u90e8 API \u8fdb\u884c\u6d4b\u8bd5\u3002\"\"\"\n api_call_mock.return_value = {\"status\": \"success\"}\n\n result = my_function()\n\n api_call_mock.assert_called_once()\n assert result[\"status\"] == \"success\"\n```\n\n### Mock \u8fd4\u56de\u503c\n\n```python\n@patch(\"mypackage.Database.connect\")\ndef test_database_connection(connect_mock):\n \"\"\"\u4f7f\u7528 mock \u7684\u6570\u636e\u5e93\u8fde\u63a5\u8fdb\u884c\u6d4b\u8bd5\u3002\"\"\"\n connect_mock.return_value = MockConnection()\n\n db = Database()\n db.connect()\n\n connect_mock.assert_called_once_with(\"localhost\")\n```\n\n### Mock \u5f02\u5e38\n\n```python\n@patch(\"mypackage.api_call\")\ndef test_api_error_handling(api_call_mock):\n \"\"\"\u4f7f\u7528 mock \u5f02\u5e38\u6d4b\u8bd5\u9519\u8bef\u5904\u7406\u3002\"\"\"\n api_call_mock.side_effect = ConnectionError(\"Network error\")\n\n with pytest.raises(ConnectionError):\n api_call()\n\n api_call_mock.assert_called_once()\n```\n\n### Mock \u4e0a\u4e0b\u6587\u7ba1\u7406\u5668\uff08Context Managers\uff09\n\n```python\n@patch(\"builtins.open\", new_callable=mock_open)\ndef test_file_reading(mock_file):\n \"\"\"\u4f7f\u7528 mock \u7684 open \u6d4b\u8bd5\u6587\u4ef6\u8bfb\u53d6\u3002\"\"\"\n mock_file.return_value.read.return_value = \"file content\"\n\n result = read_file(\"test.txt\")\n\n mock_file.assert_called_once_with(\"test.txt\", \"r\")\n assert result == \"file content\"\n```\n\n### \u4f7f\u7528 Autospec\n\n```python\n@patch(\"mypackage.DBConnection\", autospec=True)\ndef test_autospec(db_mock):\n \"\"\"\u4f7f\u7528 autospec \u6355\u83b7 API \u6ee5\u7528\u3002\"\"\"\n db = db_mock.return_value\n db.query(\"SELECT * FROM users\")\n\n # \u5982\u679c DBConnection \u6ca1\u6709 query \u65b9\u6cd5\uff0c\u6b64\u5904\u5c06\u5931\u8d25\n db_mock.assert_called_once()\n```\n\n### Mock \u7c7b\u5b9e\u4f8b\n\n```python\nclass TestUserService:\n @patch(\"mypackage.UserRepository\")\n def test_create_user(self, repo_mock):\n \"\"\"\u4f7f\u7528 mock \u7684\u4ed3\u5e93\u8fdb\u884c\u7528\u6237\u521b\u5efa\u6d4b\u8bd5\u3002\"\"\"\n repo_mock.return_value.save.return_value = User(id=1, name=\"Alice\")\n\n service = UserService(repo_mock.return_value)\n user = service.create_user(name=\"Alice\")\n\n assert user.name == \"Alice\"\n repo_mock.return_value.save.assert_called_once()\n```\n\n### Mock \u5c5e\u6027\uff08Property\uff09\n\n```python\n@pytest.fixture\ndef mock_config():\n \"\"\"\u521b\u5efa\u4e00\u4e2a\u5e26\u6709\u5c5e\u6027\u7684 mock\u3002\"\"\"\n config = Mock()\n type(config).debug = PropertyMock(return_value=True)\n type(config).api_key = PropertyMock(return_value=\"test-key\")\n return config\n\ndef test_with_mock_config(mock_config):\n \"\"\"\u4f7f\u7528 mock \u914d\u7f6e\u5c5e\u6027\u8fdb\u884c\u6d4b\u8bd5\u3002\"\"\"\n assert mock_config.debug is True\n assert mock_config.api_key == \"test-key\"\n```\n\n## \u6d4b\u8bd5\u5f02\u6b65\u4ee3\u7801\n\n### \u4f7f\u7528 pytest-asyncio \u8fdb\u884c\u5f02\u6b65\u6d4b\u8bd5\n\n```python\nimport pytest\n\n@pytest.mark.asyncio\nasync def test_async_function():\n \"\"\"\u6d4b\u8bd5\u5f02\u6b65\u51fd\u6570\u3002\"\"\"\n result = await async_add(2, 3)\n assert result == 5\n\n@pytest.mark.asyncio\nasync def test_async_with_fixture(async_client):\n \"\"\"\u5728\u5f02\u6b65 fixture \u4e0b\u8fdb\u884c\u5f02\u6b65\u6d4b\u8bd5\u3002\"\"\"\n response = await async_client.get(\"/api/users\")\n assert response.status_code == 200\n```\n\n### \u5f02\u6b65 Fixture\n\n```python\n@pytest.fixture\nasync def async_client():\n \"\"\"\u63d0\u4f9b\u5f02\u6b65\u6d4b\u8bd5\u5ba2\u6237\u7aef\u7684\u5f02\u6b65 Fixture\u3002\"\"\"\n app = create_app()\n async with app.test_client() as client:\n yield client\n\n@pytest.mark.asyncio\nasync def test_api_endpoint(async_client):\n \"\"\"\u4f7f\u7528\u5f02\u6b65 fixture \u8fdb\u884c\u6d4b\u8bd5\u3002\"\"\"\n response = await async_client.get(\"/api/data\")\n assert response.status_code == 200\n```\n\n### Mock \u5f02\u6b65\u51fd\u6570\n\n```python\n@pytest.mark.asyncio\n@patch(\"mypackage.async_api_call\")\nasync def test_async_mock(api_call_mock):\n \"\"\"\u4f7f\u7528 mock \u6d4b\u8bd5\u5f02\u6b65\u51fd\u6570\u3002\"\"\"\n api_call_mock.return_value = {\"status\": \"ok\"}\n\n result = await my_async_function()\n\n api_call_mock.assert_awaited_once()\n assert result[\"status\"] == \"ok\"\n```\n\n## \u6d4b\u8bd5\u5f02\u5e38\n\n### \u6d4b\u8bd5\u9884\u671f\u7684\u5f02\u5e38\n\n```python\ndef test_divide_by_zero():\n \"\"\"\u6d4b\u8bd5\u9664\u4ee5\u96f6\u662f\u5426\u629b\u51fa ZeroDivisionError\u3002\"\"\"\n with pytest.raises(ZeroDivisionError):\n divide(10, 0)\n\ndef test_custom_exception():\n \"\"\"\u4f7f\u7528\u6d88\u606f\u6d4b\u8bd5\u81ea\u5b9a\u4e49\u5f02\u5e38\u3002\"\"\"\n with pytest.raises(ValueError, match=\"invalid input\"):\n validate_input(\"invalid\")\n```\n\n### \u6d4b\u8bd5\u5f02\u5e38\u5c5e\u6027\n\n```python\ndef test_exception_with_details():\n \"\"\"\u6d4b\u8bd5\u5e26\u6709\u81ea\u5b9a\u4e49\u5c5e\u6027\u7684\u5f02\u5e38\u3002\"\"\"\n with pytest.raises(CustomError) as exc_info:\n raise CustomError(\"error\", code=400)\n\n assert exc_info.value.code == 400\n assert \"error\" in str(exc_info.value)\n```\n\n## \u6d4b\u8bd5\u526f\u4f5c\u7528\uff08Side Effects\uff09\n\n### \u6d4b\u8bd5\u6587\u4ef6\u64cd\u4f5c\n\n```python\nimport tempfile\nimport os\n\ndef test_file_processing():\n \"\"\"\u4f7f\u7528\u4e34\u65f6\u6587\u4ef6\u6d4b\u8bd5\u6587\u4ef6\u5904\u7406\u3002\"\"\"\n with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:\n f.write(\"test content\")\n temp_path = f.name\n\n try:\n result = process_file(temp_path)\n assert result == \"processed: test content\"\n finally:\n os.unlink(temp_path)\n```\n\n### \u4f7f\u7528 pytest \u7684 tmp_path Fixture \u8fdb\u884c\u6d4b\u8bd5\n\n```python\ndef test_with_tmp_path(tmp_path):\n \"\"\"\u4f7f\u7528 pytest \u5185\u7f6e\u7684\u4e34\u65f6\u8def\u5f84 fixture \u8fdb\u884c\u6d4b\u8bd5\u3002\"\"\"\n test_file = tmp_path / \"test.txt\"\n test_file.write_text(\"hello world\")\n\n result = process_file(str(test_file))\n assert result == \"hello world\"\n # tmp_path \u4f1a\u81ea\u52a8\u6e05\u7406\n```\n\n### \u4f7f\u7528 tmpdir Fixture \u8fdb\u884c\u6d4b\u8bd5\n\n```python\ndef test_with_tmpdir(tmpdir):\n \"\"\"\u4f7f\u7528 pytest \u7684 tmpdir fixture \u8fdb\u884c\u6d4b\u8bd5\u3002\"\"\"\n test_file = tmpdir.join(\"test.txt\")\n test_file.write(\"data\")\n\n result = process_file(str(test_file))\n assert result == \"data\"\n```\n\n## \u6d4b\u8bd5\u7ec4\u7ec7\n\n### \u76ee\u5f55\u7ed3\u6784\n\n```\ntests/\n\u251c\u2500\u2500 conftest.py # \u5171\u4eab\u7684 fixture\n\u251c\u2500\u2500 __init__.py\n\u251c\u2500\u2500 unit/ # \u5355\u5143\u6d4b\u8bd5\n\u2502 \u251c\u2500\u2500 __init__.py\n\u2502 \u251c\u2500\u2500 test_models.py\n\u2502 \u251c\u2500\u2500 test_utils.py\n\u2502 \u2514\u2500\u2500 test_services.py\n\u251c\u2500\u2500 integration/ # \u96c6\u6210\u6d4b\u8bd5\n\u2502 \u251c\u2500\u2500 __init__.py\n\u2502 \u251c\u2500\u2500 test_api.py\n\u2502 \u2514\u2500\u2500 test_database.py\n\u2514\u2500\u2500 e2e/ # \u7aef\u5230\u7aef\u6d4b\u8bd5\n \u251c\u2500\u2500 __init__.py\n \u2514\u2500\u2500 test_user_flow.py\n```\n\n### \u6d4b\u8bd5\u7c7b\n\n```python\nclass TestUserService:\n \"\"\"\u5728\u7c7b\u4e2d\u7ec4\u7ec7\u76f8\u5173\u7684\u6d4b\u8bd5\u3002\"\"\"\n\n @pytest.fixture(autouse=True)\n def setup(self):\n \"\"\"\u5728\u8be5\u7c7b\u7684\u6bcf\u4e2a\u6d4b\u8bd5\u8fd0\u884c\u524d\u6267\u884c\u8bbe\u7f6e\u3002\"\"\"\n self.service = UserService()\n\n def test_create_user(self):\n \"\"\"\u6d4b\u8bd5\u7528\u6237\u521b\u5efa\u3002\"\"\"\n user = self.service.create_user(\"Alice\")\n assert user.name == \"Alice\"\n\n def test_delete_user(self):\n \"\"\"\u6d4b\u8bd5\u7528\u6237\u5220\u9664\u3002\"\"\"\n user = User(id=1, name=\"Bob\")\n self.service.delete_user(user)\n assert not self.service.user_exists(1)\n```\n\n## \u6700\u4f73\u5b9e\u8df5\n\n### \u5e94\u8be5\u505a\uff08DO\uff09\n\n- **\u9075\u5faa TDD**\uff1a\u5148\u5199\u6d4b\u8bd5\u518d\u5199\u4ee3\u7801\uff08\u7ea2-\u7eff-\u91cd\u6784\uff09\n- **\u53ea\u6d4b\u8bd5\u4e00\u4ef6\u4e8b**\uff1a\u6bcf\u4e2a\u6d4b\u8bd5\u5e94\u8be5\u53ea\u9a8c\u8bc1\u4e00\u79cd\u884c\u4e3a\n- **\u4f7f\u7528\u63cf\u8ff0\u6027\u7684\u540d\u79f0**\uff1a\u5982 `test_user_login_with_invalid_credentials_fails`\n- **\u4f7f\u7528 Fixtures**\uff1a\u901a\u8fc7 fixture \u6d88\u9664\u91cd\u590d\u4ee3\u7801\n- **Mock \u5916\u90e8\u4f9d\u8d56**\uff1a\u4e0d\u8981\u4f9d\u8d56\u5916\u90e8\u670d\u52a1\n- **\u6d4b\u8bd5\u8fb9\u7f18\u60c5\u51b5**\uff1a\u7a7a\u8f93\u5165\u3001None \u503c\u3001\u8fb9\u754c\u6761\u4ef6\n- **\u4ee5 80% \u4ee5\u4e0a\u7684\u8986\u76d6\u7387\u4e3a\u76ee\u6807**\uff1a\u4f18\u5148\u8986\u76d6\u5173\u952e\u8def\u5f84\n- **\u4fdd\u6301\u6d4b\u8bd5\u8fd0\u884c\u8fc5\u901f**\uff1a\u4f7f\u7528\u6807\u8bb0\u533a\u5206\u6162\u901f\u6d4b\u8bd5\n\n### \u4e0d\u8be5\u505a\uff08DON'T\uff09\n\n- **\u4e0d\u8981\u6d4b\u8bd5\u5b9e\u73b0\u7ec6\u8282**\uff1a\u6d4b\u8bd5\u884c\u4e3a\u800c\u975e\u5185\u90e8\u5b9e\u73b0\n- **\u4e0d\u8981\u5728\u6d4b\u8bd5\u4e2d\u4f7f\u7528\u590d\u6742\u7684\u6761\u4ef6\u5224\u65ad**\uff1a\u4fdd\u6301\u6d4b\u8bd5\u903b\u8f91\u7b80\u5355\n- **\u4e0d\u8981\u5ffd\u89c6\u5931\u8d25\u7684\u6d4b\u8bd5**\uff1a\u6240\u6709\u6d4b\u8bd5\u5fc5\u987b\u901a\u8fc7\n- **\u4e0d\u8981\u6d4b\u8bd5\u7b2c\u4e09\u65b9\u4ee3\u7801**\uff1a\u76f8\u4fe1\u5e93\u672c\u8eab\u662f\u5de5\u4f5c\u7684\n- **\u4e0d\u8981\u5728\u6d4b\u8bd5\u95f4\u5171\u4eab\u72b6\u6001**\uff1a\u6d4b\u8bd5\u5e94\u8be5\u662f\u76f8\u4e92\u72ec\u7acb\u7684\n- **\u4e0d\u8981\u5728\u6d4b\u8bd5\u4e2d\u6355\u83b7\u5f02\u5e38**\uff1a\u4f7f\u7528 `pytest.raises`\n- **\u4e0d\u8981\u4f7f\u7528 print \u8bed\u53e5**\uff1a\u4f7f\u7528\u65ad\u8a00\u548c pytest \u7684\u8f93\u51fa\u673a\u5236\n- **\u4e0d\u8981\u7f16\u5199\u8fc7\u4e8e\u8106\u5f31\u7684\u6d4b\u8bd5**\uff1a\u907f\u514d\u8fc7\u5ea6\u7279\u5f02\u5316\u7684 mock\n\n## \u5e38\u89c1\u6a21\u5f0f\n\n### \u6d4b\u8bd5 API \u7aef\u70b9 (FastAPI/Flask)\n\n```python\n@pytest.fixture\ndef client():\n app = create_app(testing=True)\n return app.test_client()\n\ndef test_get_user(client):\n response = client.get(\"/api/users/1\")\n assert response.status_code == 200\n assert response.json[\"id\"] == 1\n\ndef test_create_user(client):\n response = client.post(\"/api/users\", json={\n \"name\": \"Alice\",\n \"email\": \"alice@example.com\"\n })\n assert response.status_code == 201\n assert response.json[\"name\"] == \"Alice\"\n```\n\n### \u6d4b\u8bd5\u6570\u636e\u5e93\u64cd\u4f5c\n\n```python\n@pytest.fixture\ndef db_session():\n \"\"\"\u521b\u5efa\u6d4b\u8bd5\u6570\u636e\u5e93\u4f1a\u8bdd\u3002\"\"\"\n session = Session(bind=engine)\n session.begin_nested()\n yield session\n session.rollback()\n session.close()\n\ndef test_create_user(db_session):\n user = User(name=\"Alice\", email=\"alice@example.com\")\n db_session.add(user)\n db_session.commit()\n\n retrieved = db_session.query(User).filter_by(name=\"Alice\").first()\n assert retrieved.email == \"alice@example.com\"\n```\n\n### \u6d4b\u8bd5\u7c7b\u65b9\u6cd5\n\n```python\nclass TestCalculator:\n @pytest.fixture\n def calculator(self):\n return Calculator()\n\n def test_add(self, calculator):\n assert calculator.add(2, 3) == 5\n\n def test_divide_by_zero(self, calculator):\n with pytest.raises(ZeroDivisionError):\n calculator.divide(10, 0)\n```\n\n## pytest \u914d\u7f6e\n\n### pytest.ini\n\n```ini\n[pytest]\ntestpaths = tests\npython_files = test_*.py\npython_classes = Test*\npython_functions = test_*\naddopts =\n --strict-markers\n --disable-warnings\n --cov=mypackage\n --cov-report=term-missing\n --cov-report=html\nmarkers =\n slow: \u5c06\u6d4b\u8bd5\u6807\u8bb0\u4e3a\u6162\u901f\n integration: \u5c06\u6d4b\u8bd5\u6807\u8bb0\u4e3a\u96c6\u6210\u6d4b\u8bd5\n unit: \u5c06\u6d4b\u8bd5\u6807\u8bb0\u4e3a\u5355\u5143\u6d4b\u8bd5\n```\n\n### pyproject.toml\n\n```toml\n[tool.pytest.ini_options]\ntestpaths = [\"tests\"]\npython_files = [\"test_*.py\"]\npython_classes = [\"Test*\"]\npython_functions = [\"test_*\"]\naddopts = [\n \"--strict-markers\",\n \"--cov=mypackage\",\n \"--cov-report=term-missing\",\n \"--cov-report=html\",\n]\nmarkers = [\n \"slow: \u5c06\u6d4b\u8bd5\u6807\u8bb0\u4e3a\u6162\u901f\",\n \"integration: \u5c06\u6d4b\u8bd5\u6807\u8bb0\u4e3a\u96c6\u6210\u6d4b\u8bd5\",\n \"unit: \u5c06\u6d4b\u8bd5\u6807\u8bb0\u4e3a\u5355\u5143\u6d4b\u8bd5\",\n]\n```\n\n## \u8fd0\u884c\u6d4b\u8bd5\n\n```bash\n# \u8fd0\u884c\u6240\u6709\u6d4b\u8bd5\npytest\n\n# \u8fd0\u884c\u7279\u5b9a\u6587\u4ef6\npytest tests/test_utils.py\n\n# \u8fd0\u884c\u7279\u5b9a\u6d4b\u8bd5\u51fd\u6570\npytest tests/test_utils.py::test_function\n\n# \u8fd0\u884c\u5e76\u8f93\u51fa\u8be6\u7ec6\u7ed3\u679c\npytest -v\n\n# \u8fd0\u884c\u5e76\u751f\u6210\u8986\u76d6\u7387\u62a5\u544a\npytest --cov=mypackage --cov-report=html\n\n# \u4ec5\u8fd0\u884c\u975e\u6162\u901f\u6d4b\u8bd5\npytest -m \"not slow\"\n\n# \u8fd0\u884c\u5e76\u5728\u7b2c\u4e00\u6b21\u5931\u8d25\u65f6\u505c\u6b62\npytest -x\n\n# \u8fd0\u884c\u5e76\u5728\u53d1\u751f N \u6b21\u5931\u8d25\u540e\u505c\u6b62\npytest --maxfail=3\n\n# \u8fd0\u884c\u4e0a\u6b21\u5931\u8d25\u7684\u6d4b\u8bd5\npytest --lf\n\n# \u8fd0\u884c\u5339\u914d\u6a21\u5f0f\u7684\u6d4b\u8bd5\npytest -k \"test_user\"\n\n# \u5931\u8d25\u65f6\u542f\u52a8\u8c03\u8bd5\u5668\npytest --pdb\n```\n\n## \u5feb\u901f\u53c2\u8003\n\n| \u6a21\u5f0f | \u7528\u6cd5 |\n|---------|-------|\n| `pytest.raises()` | \u6d4b\u8bd5\u9884\u671f\u7684\u5f02\u5e38 |\n| `@pytest.fixture()` | \u521b\u5efa\u53ef\u91cd\u7528\u7684\u6d4b\u8bd5 fixture |\n| `@pytest.mark.parametrize()` | \u4f7f\u7528\u591a\u7ec4\u8f93\u5165\u8fd0\u884c\u6d4b\u8bd5 |\n| `@pytest.mark.slow` | \u6807\u8bb0\u6162\u901f\u6d4b\u8bd5 |\n| `pytest -m \"not slow\"` | \u8df3\u8fc7\u6162\u901f\u6d4b\u8bd5 |\n| `@patch()` | Mock \u51fd\u6570\u548c\u7c7b |\n| `tmp_path` fixture | \u81ea\u52a8\u521b\u5efa\u4e34\u65f6\u76ee\u5f55 |\n| `pytest --cov` | \u751f\u6210\u8986\u76d6\u7387\u62a5\u544a |\n| `assert` | \u7b80\u5355\u4e14\u53ef\u8bfb\u7684\u65ad\u8a00 |\n\n**\u8bf7\u8bb0\u4f4f**\uff1a\u6d4b\u8bd5\u4ee3\u7801\u4e5f\u662f\u4ee3\u7801\u3002\u4fdd\u6301\u5b83\u4eec\u6574\u6d01\u3001\u53ef\u8bfb\u4e14\u53ef\u7ef4\u62a4\u3002\u597d\u7684\u6d4b\u8bd5\u80fd\u6355\u83b7 Bug\uff1b\u4f1f\u5927\u7684\u6d4b\u8bd5\u80fd\u9632\u6b62 Bug \u4ea7\u751f\u3002\n" + }, + "/Users/Library/Applications/xx/code/github/everything-claude-code-zh/skills/springboot-patterns/SKILL.md": { + "md5": "147a8fe142bd88f7696500a6a5fd7632", + "content": "---\nname: springboot-patterns\ndescription: Spring Boot \u67b6\u6784\u6a21\u5f0f\u3001REST API \u8bbe\u8ba1\u3001\u5206\u5c42\u670d\u52a1\u3001\u6570\u636e\u8bbf\u95ee\u3001\u7f13\u5b58\u3001\u5f02\u6b65\u5904\u7406\u548c\u65e5\u5fd7\u8bb0\u5f55\u3002\u9002\u7528\u4e8e Java Spring Boot \u540e\u7aef\u5f00\u53d1\u5de5\u4f5c\u3002\n---\n\n# Spring Boot \u5f00\u53d1\u6a21\u5f0f (Spring Boot Development Patterns)\n\n\u9002\u7528\u4e8e\u53ef\u6269\u5c55\u3001\u751f\u4ea7\u7ea7\u670d\u52a1\u7684 Spring Boot \u67b6\u6784\u4e0e API \u6a21\u5f0f\u3002\n\n## REST API \u7ed3\u6784\n\n```java\n@RestController\n@RequestMapping(\"/api/markets\")\n@Validated\nclass MarketController {\n private final MarketService marketService;\n\n MarketController(MarketService marketService) {\n this.marketService = marketService;\n }\n\n @GetMapping\n ResponseEntity> list(\n @RequestParam(defaultValue = \"0\") int page,\n @RequestParam(defaultValue = \"20\") int size) {\n Page markets = marketService.list(PageRequest.of(page, size));\n return ResponseEntity.ok(markets.map(MarketResponse::from));\n }\n\n @PostMapping\n ResponseEntity create(@Valid @RequestBody CreateMarketRequest request) {\n Market market = marketService.create(request);\n return ResponseEntity.status(HttpStatus.CREATED).body(MarketResponse.from(market));\n }\n}\n```\n\n## \u4ed3\u50a8\u6a21\u5f0f (Repository Pattern - Spring Data JPA)\n\n```java\npublic interface MarketRepository extends JpaRepository {\n @Query(\"select m from MarketEntity m where m.status = :status order by m.volume desc\")\n List findActive(@Param(\"status\") MarketStatus status, Pageable pageable);\n}\n```\n\n## \u5e26\u4e8b\u52a1\u7684\u670d\u52a1\u5c42 (Service Layer with Transactions)\n\n```java\n@Service\npublic class MarketService {\n private final MarketRepository repo;\n\n public MarketService(MarketRepository repo) {\n this.repo = repo;\n }\n\n @Transactional\n public Market create(CreateMarketRequest request) {\n MarketEntity entity = MarketEntity.from(request);\n MarketEntity saved = repo.save(entity);\n return Market.from(saved);\n }\n}\n```\n\n## DTO \u4e0e\u6821\u9a8c (DTOs and Validation)\n\n```java\npublic record CreateMarketRequest(\n @NotBlank @Size(max = 200) String name,\n @NotBlank @Size(max = 2000) String description,\n @NotNull @FutureOrPresent Instant endDate,\n @NotEmpty List<@NotBlank String> categories) {}\n\npublic record MarketResponse(Long id, String name, MarketStatus status) {\n static MarketResponse from(Market market) {\n return new MarketResponse(market.id(), market.name(), market.status());\n }\n}\n```\n\n## \u5f02\u5e38\u5904\u7406 (Exception Handling)\n\n```java\n@ControllerAdvice\nclass GlobalExceptionHandler {\n @ExceptionHandler(MethodArgumentNotValidException.class)\n ResponseEntity handleValidation(MethodArgumentNotValidException ex) {\n String message = ex.getBindingResult().getFieldErrors().stream()\n .map(e -> e.getField() + \": \" + e.getDefaultMessage())\n .collect(Collectors.joining(\", \"));\n return ResponseEntity.badRequest().body(ApiError.validation(message));\n }\n\n @ExceptionHandler(AccessDeniedException.class)\n ResponseEntity handleAccessDenied() {\n return ResponseEntity.status(HttpStatus.FORBIDDEN).body(ApiError.of(\"Forbidden\"));\n }\n\n @ExceptionHandler(Exception.class)\n ResponseEntity handleGeneric(Exception ex) {\n // \u8bb0\u5f55\u5e26\u6709\u5806\u6808\u8ddf\u8e2a\u7684\u975e\u9884\u671f\u9519\u8bef\n return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)\n .body(ApiError.of(\"Internal server error\"));\n }\n}\n```\n\n## \u7f13\u5b58 (Caching)\n\n\u9700\u8981\u5728\u914d\u7f6e\u7c7b\u4e0a\u6dfb\u52a0 `@EnableCaching`\u3002\n\n```java\n@Service\npublic class MarketCacheService {\n private final MarketRepository repo;\n\n public MarketCacheService(MarketRepository repo) {\n this.repo = repo;\n }\n\n @Cacheable(value = \"market\", key = \"#id\")\n public Market getById(Long id) {\n return repo.findById(id)\n .map(Market::from)\n .orElseThrow(() -> new EntityNotFoundException(\"Market not found\"));\n }\n\n @CacheEvict(value = \"market\", key = \"#id\")\n public void evict(Long id) {}\n}\n```\n\n## \u5f02\u6b65\u5904\u7406 (Async Processing)\n\n\u9700\u8981\u5728\u914d\u7f6e\u7c7b\u4e0a\u6dfb\u52a0 `@EnableAsync`\u3002\n\n```java\n@Service\npublic class NotificationService {\n @Async\n public CompletableFuture sendAsync(Notification notification) {\n // \u53d1\u9001 \u90ae\u4ef6/\u77ed\u4fe1\n return CompletableFuture.completedFuture(null);\n }\n}\n```\n\n## \u65e5\u5fd7\u8bb0\u5f55 (Logging - SLF4J)\n\n```java\n@Service\npublic class ReportService {\n private static final Logger log = LoggerFactory.getLogger(ReportService.class);\n\n public Report generate(Long marketId) {\n log.info(\"generate_report marketId={}\", marketId);\n try {\n // \u4e1a\u52a1\u903b\u8f91\n } catch (Exception ex) {\n log.error(\"generate_report_failed marketId={}\", marketId, ex);\n throw ex;\n }\n return new Report();\n }\n}\n```\n\n## \u4e2d\u95f4\u4ef6 / \u8fc7\u6ee4\u5668 (Middleware / Filters)\n\n```java\n@Component\npublic class RequestLoggingFilter extends OncePerRequestFilter {\n private static final Logger log = LoggerFactory.getLogger(RequestLoggingFilter.class);\n\n @Override\n protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,\n FilterChain filterChain) throws ServletException, IOException {\n long start = System.currentTimeMillis();\n try {\n filterChain.doFilter(request, response);\n } finally {\n long duration = System.currentTimeMillis() - start;\n log.info(\"req method={} uri={} status={} durationMs={}\",\n request.getMethod(), request.getRequestURI(), response.getStatus(), duration);\n }\n }\n}\n```\n\n## \u5206\u9875\u4e0e\u6392\u5e8f (Pagination and Sorting)\n\n```java\nPageRequest page = PageRequest.of(pageNumber, pageSize, Sort.by(\"createdAt\").descending());\nPage results = marketService.list(page);\n```\n\n## \u5bb9\u9519\u6027\u5916\u90e8\u8c03\u7528 (Error-Resilient External Calls)\n\n```java\npublic T withRetry(Supplier supplier, int maxRetries) {\n int attempts = 0;\n while (true) {\n try {\n return supplier.get();\n } catch (Exception ex) {\n attempts++;\n if (attempts >= maxRetries) {\n throw ex;\n }\n try {\n Thread.sleep((long) Math.pow(2, attempts) * 100L);\n } catch (InterruptedException ie) {\n Thread.currentThread().interrupt();\n throw ex;\n }\n }\n }\n}\n```\n\n## \u9650\u6d41 (Filter + Bucket4j)\n\n**\u5b89\u5168\u6ce8\u610f\u4e8b\u9879**\uff1a`X-Forwarded-For` \u8bf7\u6c42\u5934\u9ed8\u8ba4\u662f\u4e0d\u53ef\u4fe1\u7684\uff0c\u56e0\u4e3a\u5ba2\u6237\u7aef\u53ef\u4ee5\u4f2a\u9020\u5b83\u3002\n\u4ec5\u5728\u4ee5\u4e0b\u60c5\u51b5\u4e0b\u4f7f\u7528\u8f6c\u53d1\u8bf7\u6c42\u5934\uff1a\n1. \u4f60\u7684\u5e94\u7528\u4f4d\u4e8e\u53d7\u4fe1\u4efb\u7684\u53cd\u5411\u4ee3\u7406\uff08nginx\u3001AWS ALB \u7b49\uff09\u4e4b\u540e\n2. \u4f60\u5df2\u5c06 `ForwardedHeaderFilter` \u6ce8\u518c\u4e3a Bean\n3. \u4f60\u5728 application \u5c5e\u6027\u4e2d\u914d\u7f6e\u4e86 `server.forward-headers-strategy=NATIVE` \u6216 `FRAMEWORK`\n4. \u4f60\u7684\u4ee3\u7406\u914d\u7f6e\u4e3a\u8986\u76d6\uff08\u800c\u975e\u8ffd\u52a0\uff09`X-Forwarded-For` \u8bf7\u6c42\u5934\n\n\u5f53 `ForwardedHeaderFilter` \u914d\u7f6e\u6b63\u786e\u65f6\uff0c`request.getRemoteAddr()` \u5c06\u81ea\u52a8\u4ece\u8f6c\u53d1\u5934\u4e2d\u8fd4\u56de\u6b63\u786e\u7684\u5ba2\u6237\u7aef IP\u3002\u5982\u679c\u6ca1\u6709\u6b64\u914d\u7f6e\uff0c\u8bf7\u76f4\u63a5\u4f7f\u7528 `request.getRemoteAddr()`\u2014\u2014\u5b83\u8fd4\u56de\u76f4\u63a5\u8fde\u63a5\u7684 IP\uff0c\u8fd9\u662f\u552f\u4e00\u53ef\u4fe1\u7684\u503c\u3002\n\n```java\n@Component\npublic class RateLimitFilter extends OncePerRequestFilter {\n private final Map buckets = new ConcurrentHashMap<>();\n\n /*\n * \u5b89\u5168\u63d0\u793a\uff1a\u6b64\u8fc7\u6ee4\u5668\u4f7f\u7528 request.getRemoteAddr() \u6765\u8bc6\u522b\u7528\u4e8e\u9650\u6d41\u7684\u5ba2\u6237\u7aef\u3002\n *\n * \u5982\u679c\u4f60\u7684\u5e94\u7528\u4f4d\u4e8e\u53cd\u5411\u4ee3\u7406\uff08nginx\u3001AWS ALB \u7b49\uff09\u4e4b\u540e\uff0c\u4f60\u5fc5\u987b\u914d\u7f6e Spring \n * \u6b63\u786e\u5904\u7406\u8f6c\u53d1\u5934\uff0c\u4ee5\u4fbf\u51c6\u786e\u68c0\u6d4b\u5ba2\u6237\u7aef IP\uff1a\n *\n * 1. \u5728 application.properties/yaml \u4e2d\u8bbe\u7f6e server.forward-headers-strategy=NATIVE \n * (\u9002\u7528\u4e8e\u4e91\u5e73\u53f0) \u6216 FRAMEWORK\n * 2. \u5982\u679c\u4f7f\u7528 FRAMEWORK \u7b56\u7565\uff0c\u8bf7\u6ce8\u518c ForwardedHeaderFilter\uff1a\n *\n * @Bean\n * ForwardedHeaderFilter forwardedHeaderFilter() {\n * return new ForwardedHeaderFilter();\n * }\n *\n * 3. \u786e\u4fdd\u4f60\u7684\u4ee3\u7406\u8986\u76d6\uff08\u800c\u4e0d\u662f\u8ffd\u52a0\uff09X-Forwarded-For \u8bf7\u6c42\u5934\u4ee5\u9632\u6b62\u4f2a\u9020\n * 4. \u4e3a\u4f60\u7684\u5bb9\u5668\u914d\u7f6e server.tomcat.remoteip.trusted-proxies \u6216\u7b49\u6548\u914d\u7f6e\n *\n * \u5982\u679c\u6ca1\u6709\u8fd9\u4e9b\u914d\u7f6e\uff0crequest.getRemoteAddr() \u5c06\u8fd4\u56de\u4ee3\u7406\u670d\u52a1\u5668\u7684 IP\uff0c\u800c\u975e\u5ba2\u6237\u7aef IP\u3002\n * \u4e0d\u8981\u76f4\u63a5\u8bfb\u53d6 X-Forwarded-For \u2014\u2014 \u5728\u6ca1\u6709\u53d7\u4fe1\u4efb\u4ee3\u7406\u5904\u7406\u7684\u60c5\u51b5\u4e0b\uff0c\u5b83\u662f\u6781\u6613\u4f2a\u9020\u7684\u3002\n */\n @Override\n protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,\n FilterChain filterChain) throws ServletException, IOException {\n // \u4f7f\u7528 getRemoteAddr()\uff0c\u5b83\u5728\u914d\u7f6e\u4e86 ForwardedHeaderFilter \u65f6\u8fd4\u56de\u6b63\u786e\u7684\u5ba2\u6237\u7aef IP\uff0c\n // \u5426\u5219\u8fd4\u56de\u76f4\u63a5\u8fde\u63a5\u7684 IP\u3002\u5728\u6ca1\u6709\u6b63\u786e\u4ee3\u7406\u914d\u7f6e\u7684\u60c5\u51b5\u4e0b\uff0c\u5207\u52ff\u76f4\u63a5\u4fe1\u4efb X-Forwarded-For \u5934\u3002\n String clientIp = request.getRemoteAddr();\n\n Bucket bucket = buckets.computeIfAbsent(clientIp,\n k -> Bucket.builder()\n .addLimit(Bandwidth.classic(100, Refill.greedy(100, Duration.ofMinutes(1))))\n .build());\n\n if (bucket.tryConsume(1)) {\n filterChain.doFilter(request, response);\n } else {\n response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());\n }\n }\n}\n```\n\n## \u540e\u53f0\u4efb\u52a1 (Background Jobs)\n\n\u4f7f\u7528 Spring \u7684 `@Scheduled` \u6216\u96c6\u6210\u961f\u5217\uff08\u5982 Kafka\u3001SQS\u3001RabbitMQ\uff09\u3002\u4fdd\u6301\u5904\u7406\u7a0b\u5e8f\u5177\u6709\u5e42\u7b49\u6027\u548c\u53ef\u89c2\u6d4b\u6027\u3002\n\n## \u53ef\u89c2\u6d4b\u6027 (Observability)\n\n- \u901a\u8fc7 Logback encoder \u5b9e\u73b0\u7ed3\u6784\u5316\u65e5\u5fd7\uff08JSON\uff09\n- \u6307\u6807\uff08Metrics\uff09\uff1aMicrometer + Prometheus/OTel\n- \u94fe\u8def\u8ffd\u8e2a\uff08Tracing\uff09\uff1a\u4f7f\u7528 OpenTelemetry \u6216 Brave \u540e\u7aef\u7684 Micrometer Tracing\n\n## \u751f\u4ea7\u73af\u5883\u9ed8\u8ba4\u5b9e\u8df5 (Production Defaults)\n\n- \u4f18\u5148\u4f7f\u7528\u6784\u9020\u51fd\u6570\u6ce8\u5165\uff0c\u907f\u514d\u5b57\u6bb5\u6ce8\u5165\n- \u4e3a RFC 7807 \u9519\u8bef\u542f\u7528 `spring.mvc.problemdetails.enabled=true` (Spring Boot 3+)\n- \u6839\u636e\u5de5\u4f5c\u8d1f\u8f7d\u914d\u7f6e HikariCP \u8fde\u63a5\u6c60\u5927\u5c0f\u5e76\u8bbe\u7f6e\u8d85\u65f6\n- \u4e3a\u67e5\u8be2\u4f7f\u7528 `@Transactional(readOnly = true)`\n- \u901a\u8fc7 `@NonNull` \u548c `Optional` \u5728\u9002\u5f53\u65f6\u5f3a\u5236\u6267\u884c\u7a7a\u5b89\u5168\uff08null-safety\uff09\n\n**\u5207\u8bb0**\uff1a\u4fdd\u6301\u63a7\u5236\u5668\uff08Controller\uff09\u8584\u3001\u670d\u52a1\uff08Service\uff09\u4e13\u6ce8\u3001\u4ed3\u50a8\uff08Repository\uff09\u7b80\u5355\uff0c\u5e76\u96c6\u4e2d\u5904\u7406\u9519\u8bef\u3002\u9488\u5bf9\u53ef\u7ef4\u62a4\u6027\u548c\u53ef\u6d4b\u8bd5\u6027\u8fdb\u884c\u4f18\u5316\u3002\n" + }, + "/Users/Library/Applications/xx/code/github/everything-claude-code-zh/skills/springboot-security/SKILL.md": { + "md5": "55d6c53064128c7a0ba22b7251c08589", + "content": "---\nname: springboot-security\ndescription: Spring Boot \u670d\u52a1\u4e2d\u5173\u4e8e\u8eab\u4efd\u8ba4\u8bc1/\u6388\u6743\uff08authn/authz\uff09\u3001\u6821\u9a8c\u3001CSRF\u3001\u5bc6\u94a5\u7ba1\u7406\u3001\u54cd\u5e94\u5934\u3001\u9650\u6d41\u53ca\u4f9d\u8d56\u5b89\u5168\u7684 Spring Security \u6700\u4f73\u5b9e\u8df5\u3002\n---\n\n# Spring Boot \u5b89\u5168\u5ba1\u67e5\n\n\u5728\u6dfb\u52a0\u8ba4\u8bc1\u3001\u5904\u7406\u8f93\u5165\u3001\u521b\u5efa\u7aef\u70b9\u6216\u5904\u7406\u5bc6\u94a5\u65f6\u4f7f\u7528\u3002\n\n## \u8eab\u4efd\u8ba4\u8bc1\uff08Authentication\uff09\n\n- \u4f18\u5148\u4f7f\u7528\u65e0\u72b6\u6001 JWT \u6216\u5e26\u6709\u64a4\u56de\u5217\u8868\uff08Revocation List\uff09\u7684\u4e0d\u900f\u660e\u4ee4\u724c\uff08Opaque Tokens\uff09\n- \u4e3a\u4f1a\u8bdd\uff08Session\uff09\u4f7f\u7528 `httpOnly`\u3001`Secure`\u3001`SameSite=Strict` \u7684 Cookie\n- \u4f7f\u7528 `OncePerRequestFilter` \u6216\u8d44\u6e90\u670d\u52a1\u5668\u9a8c\u8bc1\u4ee4\u724c\n\n```java\n@Component\npublic class JwtAuthFilter extends OncePerRequestFilter {\n private final JwtService jwtService;\n\n public JwtAuthFilter(JwtService jwtService) {\n this.jwtService = jwtService;\n }\n\n @Override\n protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,\n FilterChain chain) throws ServletException, IOException {\n String header = request.getHeader(HttpHeaders.AUTHORIZATION);\n if (header != null && header.startsWith(\"Bearer \")) {\n String token = header.substring(7);\n Authentication auth = jwtService.authenticate(token);\n SecurityContextHolder.getContext().setAuthentication(auth);\n }\n chain.doFilter(request, response);\n }\n}\n```\n\n## \u6388\u6743\uff08Authorization\uff09\n\n- \u542f\u7528\u65b9\u6cd5\u7ea7\u5b89\u5168\uff1a`@EnableMethodSecurity`\n- \u4f7f\u7528 `@PreAuthorize(\"hasRole('ADMIN')\")` \u6216 `@PreAuthorize(\"@authz.canEdit(#id)\")`\n- \u9ed8\u8ba4\u62d2\u7edd\uff08Deny by default\uff09\uff1b\u4ec5\u66b4\u9732\u5fc5\u8981\u7684\u6743\u9650\u8303\u56f4\uff08Scopes\uff09\n\n## \u8f93\u5165\u6821\u9a8c\uff08Input Validation\uff09\n\n- \u5728\u63a7\u5236\u5668\uff08Controller\uff09\u4e0a\u914d\u5408\u4f7f\u7528 Bean Validation \u4e0e `@Valid`\n- \u5728 DTO \u4e0a\u5e94\u7528\u7ea6\u675f\uff1a`@NotBlank`\u3001`@Email`\u3001`@Size` \u4ee5\u53ca\u81ea\u5b9a\u4e49\u6821\u9a8c\u5668\n- \u5728\u6e32\u67d3\u4e4b\u524d\uff0c\u901a\u8fc7\u767d\u540d\u5355\u5bf9\u4efb\u4f55 HTML \u8fdb\u884c\u51c0\u5316\uff08Sanitize\uff09\n\n## \u9632\u6b62 SQL \u6ce8\u5165\n\n- \u4f7f\u7528 Spring Data \u5b58\u50a8\u5e93\uff08Repositories\uff09\u6216\u53c2\u6570\u5316\u67e5\u8be2\n- \u5bf9\u4e8e\u539f\u751f\u67e5\u8be2\uff0c\u4f7f\u7528 `:param` \u7ed1\u5b9a\uff1b\u4e25\u7981\u62fc\u63a5\u5b57\u7b26\u4e32\n\n## CSRF \u9632\u62a4\n\n- \u5bf9\u4e8e\u57fa\u4e8e\u6d4f\u89c8\u5668\u4f1a\u8bdd\u7684\u5e94\u7528\uff0c\u4fdd\u6301\u542f\u7528 CSRF\uff1b\u5728\u8868\u5355/\u8bf7\u6c42\u5934\u4e2d\u5305\u542b\u4ee4\u724c\n- \u5bf9\u4e8e\u4f7f\u7528 Bearer \u4ee4\u724c\u7684\u7eaf API\uff0c\u7981\u7528 CSRF \u5e76\u4f9d\u8d56\u65e0\u72b6\u6001\u8ba4\u8bc1\n\n```java\nhttp\n .csrf(csrf -> csrf.disable())\n .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));\n```\n\n## \u5bc6\u94a5\u7ba1\u7406\uff08Secrets Management\uff09\n\n- \u6e90\u4ee3\u7801\u4e2d\u4e0d\u4fdd\u7559\u5bc6\u94a5\uff1b\u4ece\u73af\u5883\u53d8\u91cf\u6216 Vault \u52a0\u8f7d\n- \u786e\u4fdd `application.yml` \u4e2d\u6ca1\u6709\u51ed\u636e\uff1b\u4f7f\u7528\u5360\u4f4d\u7b26\n- \u5b9a\u671f\u8f6e\u6362\u4ee4\u724c\u548c\u6570\u636e\u5e93\u51ed\u636e\n\n## \u5b89\u5168\u54cd\u5e94\u5934\uff08Security Headers\uff09\n\n```java\nhttp\n .headers(headers -> headers\n .contentSecurityPolicy(csp -> csp\n .policyDirectives(\"default-src 'self'\"))\n .frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)\n .xssProtection(Customizer.withDefaults())\n .referrerPolicy(rp -> rp.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.NO_REFER_ER)));\n```\n\n## \u9650\u6d41\uff08Rate Limiting\uff09\n\n- \u5728\u9ad8\u5f00\u9500\u7aef\u70b9\u4e0a\u5e94\u7528 Bucket4j \u6216\u7f51\u5173\u7ea7\u9650\u6d41\n- \u5bf9\u7a81\u53d1\u6d41\u91cf\u8fdb\u884c\u65e5\u5fd7\u8bb0\u5f55\u5e76\u544a\u8b66\uff1b\u8fd4\u56de 429 \u72b6\u6001\u7801\u53ca\u91cd\u8bd5\u63d0\u793a\n\n## \u4f9d\u8d56\u5b89\u5168\uff08Dependency Security\uff09\n\n- \u5728 CI \u4e2d\u8fd0\u884c OWASP Dependency Check / Snyk\n- \u4fdd\u6301 Spring Boot \u548c Spring Security \u5904\u4e8e\u53d7\u652f\u6301\u7684\u7248\u672c\n- \u53d1\u73b0\u5df2\u77e5 CVE \u65f6\u6784\u5efa\u5931\u8d25\n\n## \u65e5\u5fd7\u4e0e\u4e2a\u4eba\u654f\u611f\u4fe1\u606f\uff08PII\uff09\n\n- \u4e25\u7981\u5728\u65e5\u5fd7\u4e2d\u8bb0\u5f55\u5bc6\u94a5\u3001\u4ee4\u724c\u3001\u5bc6\u7801\u6216\u5b8c\u6574\u7684\u94f6\u884c\u5361\u53f7\uff08PAN\uff09\u6570\u636e\n- \u8131\u654f\u654f\u611f\u5b57\u6bb5\uff1b\u4f7f\u7528\u7ed3\u6784\u5316 JSON \u65e5\u5fd7\u8bb0\u5f55\n\n## \u6587\u4ef6\u4e0a\u4f20\n\n- \u6821\u9a8c\u5927\u5c0f\u3001\u5185\u5bb9\u7c7b\u578b\uff08Content Type\uff09\u548c\u6269\u5c55\u540d\n- \u5b58\u50a8\u5728 Web \u6839\u76ee\u5f55\u4e4b\u5916\uff1b\u5fc5\u8981\u65f6\u8fdb\u884c\u626b\u63cf\n\n## \u53d1\u5e03\u524d\u81ea\u68c0\u6e05\u5355\n\n- [ ] \u8eab\u4efd\u8ba4\u8bc1\u4ee4\u724c\u5df2\u6b63\u786e\u9a8c\u8bc1\u5e76\u914d\u7f6e\u8fc7\u671f\u65f6\u95f4\n- [ ] \u6bcf\u4e2a\u654f\u611f\u8def\u5f84\u90fd\u6709\u6388\u6743\u4fdd\u62a4\n- [ ] \u6240\u6709\u8f93\u5165\u5747\u5df2\u6821\u9a8c\u5e76\u51c0\u5316\n- [ ] \u6ca1\u6709\u5b57\u7b26\u4e32\u62fc\u63a5\u7684 SQL\n- [ ] CSRF \u914d\u7f6e\u7b26\u5408\u5e94\u7528\u7c7b\u578b\n- [ ] \u5bc6\u94a5\u5df2\u5916\u90e8\u5316\uff1b\u672a\u63d0\u4ea4\u4efb\u4f55\u5bc6\u94a5\n- [ ] \u5b89\u5168\u54cd\u5e94\u5934\u5df2\u914d\u7f6e\n- [ ] API \u5df2\u914d\u7f6e\u9650\u6d41\n- [ ] \u4f9d\u8d56\u5df2\u626b\u63cf\u4e14\u4e3a\u6700\u65b0\n- [ ] \u65e5\u5fd7\u4e2d\u4e0d\u5305\u542b\u654f\u611f\u6570\u636e\n\n**\u8bb0\u4f4f**\uff1a\u9ed8\u8ba4\u62d2\u7edd\u3001\u6821\u9a8c\u8f93\u5165\u3001\u6700\u5c0f\u6743\u9650\u539f\u5219\uff0c\u4ee5\u53ca\u914d\u7f6e\u4f18\u5148\u7684\u5b89\u5168\u6027\u3002\n" + }, + "/Users/Library/Applications/xx/code/github/everything-claude-code-zh/skills/jpa-patterns/SKILL.md": { + "md5": "c5484f0bb284efe50b188a3bf99bc51c", + "content": "---\nname: jpa-patterns\ndescription: Spring Boot \u4e2d\u7528\u4e8e\u5b9e\u4f53\u8bbe\u8ba1\u3001\u5173\u8054\u5173\u7cfb\u3001\u67e5\u8be2\u4f18\u5316\u3001\u4e8b\u52a1\u3001\u5ba1\u8ba1\u3001\u7d22\u5f15\u3001\u5206\u9875\u548c\u8fde\u63a5\u6c60\u7684 JPA/Hibernate \u6a21\u5f0f\u3002\n---\n\n# JPA/Hibernate \u6a21\u5f0f\n\n\u7528\u4e8e Spring Boot \u4e2d\u7684\u6570\u636e\u5efa\u6a21\u3001\u5b58\u50a8\u5c42\uff08Repositories\uff09\u5f00\u53d1\u548c\u6027\u80fd\u8c03\u4f18\u3002\n\n## \u5b9e\u4f53\u8bbe\u8ba1\uff08Entity Design\uff09\n\n```java\n@Entity\n@Table(name = \"markets\", indexes = {\n @Index(name = \"idx_markets_slug\", columnList = \"slug\", unique = true)\n})\n@EntityListeners(AuditingEntityListener.class)\npublic class MarketEntity {\n @Id @GeneratedValue(strategy = GenerationType.IDENTITY)\n private Long id;\n\n @Column(nullable = false, length = 200)\n private String name;\n\n @Column(nullable = false, unique = true, length = 120)\n private String slug;\n\n @Enumerated(EnumType.STRING)\n private MarketStatus status = MarketStatus.ACTIVE;\n\n @CreatedDate private Instant createdAt;\n @LastModifiedDate private Instant updatedAt;\n}\n```\n\n\u542f\u7528\u5ba1\u8ba1\uff08Auditing\uff09\uff1a\n```java\n@Configuration\n@EnableJpaAuditing\nclass JpaConfig {}\n```\n\n## \u5173\u8054\u5173\u7cfb\u4e0e N+1 \u95ee\u9898\u9884\u9632\n\n```java\n@OneToMany(mappedBy = \"market\", cascade = CascadeType.ALL, orphanRemoval = true)\nprivate List positions = new ArrayList<>();\n```\n\n- \u9ed8\u8ba4\u4f7f\u7528\u61d2\u52a0\u8f7d\uff08Lazy loading\uff09\uff1b\u5fc5\u8981\u65f6\u5728\u67e5\u8be2\u4e2d\u4f7f\u7528 `JOIN FETCH`\n- \u907f\u514d\u5728\u96c6\u5408\u4e0a\u4f7f\u7528\u7acb\u5373\u52a0\u8f7d\uff08`EAGER`\uff09\uff1b\u5bf9\u4e8e\u8bfb\u53d6\u8def\u5f84\uff0c\u4f18\u5148\u4f7f\u7528 DTO \u6295\u5f71\uff08Projections\uff09\n\n```java\n@Query(\"select m from MarketEntity m left join fetch m.positions where m.id = :id\")\nOptional findWithPositions(@Param(\"id\") Long id);\n```\n\n## \u5b58\u50a8\u5c42\u6a21\u5f0f\uff08Repository Patterns\uff09\n\n```java\npublic interface MarketRepository extends JpaRepository {\n Optional findBySlug(String slug);\n\n @Query(\"select m from MarketEntity m where m.status = :status\")\n Page findByStatus(@Param(\"status\") MarketStatus status, Pageable pageable);\n}\n```\n\n- \u4f7f\u7528\u6295\u5f71\uff08Projections\uff09\u8fdb\u884c\u8f7b\u91cf\u7ea7\u67e5\u8be2\uff1a\n```java\npublic interface MarketSummary {\n Long getId();\n String getName();\n MarketStatus getStatus();\n}\nPage findAllBy(Pageable pageable);\n```\n\n## \u4e8b\u52a1\uff08Transactions\uff09\n\n- \u4f7f\u7528 `@Transactional` \u6ce8\u89e3 Service \u65b9\u6cd5\n- \u5728\u8bfb\u53d6\u8def\u5f84\u4e0a\u4f7f\u7528 `@Transactional(readOnly = true)` \u8fdb\u884c\u4f18\u5316\n- \u8c28\u614e\u9009\u62e9\u4f20\u64ad\u884c\u4e3a\uff08Propagation\uff09\uff1b\u907f\u514d\u957f\u4e8b\u52a1\n\n```java\n@Transactional\npublic Market updateStatus(Long id, MarketStatus status) {\n MarketEntity entity = repo.findById(id)\n .orElseThrow(() -> new EntityNotFoundException(\"Market\"));\n entity.setStatus(status);\n return Market.from(entity);\n}\n```\n\n## \u5206\u9875\uff08Pagination\uff09\n\n```java\nPageRequest page = PageRequest.of(pageNumber, pageSize, Sort.by(\"createdAt\").descending());\nPage markets = repo.findByStatus(MarketStatus.ACTIVE, page);\n```\n\n\u5bf9\u4e8e\u6e38\u6807\u5f0f\u5206\u9875\uff08Cursor-like pagination\uff09\uff0c\u8bf7\u5728 JPQL \u4e2d\u5305\u542b `id > :lastId` \u5e76\u914d\u5408\u6392\u5e8f\u3002\n\n## \u7d22\u5f15\u4e0e\u6027\u80fd\n\n- \u4e3a\u5e38\u7528\u8fc7\u6ee4\u5668\uff08`status`\u3001`slug`\u3001\u5916\u952e\uff09\u6dfb\u52a0\u7d22\u5f15\uff08Indexing\uff09\n- \u4f7f\u7528\u5339\u914d\u67e5\u8be2\u6a21\u5f0f\u7684\u590d\u5408\u7d22\u5f15\uff08Composite indexes\uff0c\u5982 `status, created_at`\uff09\n- \u907f\u514d\u4f7f\u7528 `select *`\uff1b\u4ec5\u6295\u5f71\u6240\u9700\u7684\u5217\n- \u4f7f\u7528 `saveAll` \u5e76\u914d\u7f6e `hibernate.jdbc.batch_size` \u8fdb\u884c\u6279\u91cf\u5199\u5165\n\n## \u8fde\u63a5\u6c60\uff08Connection Pooling - HikariCP\uff09\n\n\u63a8\u8350\u5c5e\u6027\uff1a\n```\nspring.datasource.hikari.maximum-pool-size=20\nspring.datasource.hikari.minimum-idle=5\nspring.datasource.hikari.connection-timeout=30000\nspring.datasource.hikari.validation-timeout=5000\n```\n\n\u5bf9\u4e8e PostgreSQL \u7684 LOB \u5904\u7406\uff0c\u8bf7\u6dfb\u52a0\uff1a\n```\nspring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true\n```\n\n## \u7f13\u5b58\uff08Caching\uff09\n\n- \u4e00\u7ea7\u7f13\u5b58\uff081st-level cache\uff09\u662f\u57fa\u4e8e EntityManager \u7684\uff1b\u907f\u514d\u8de8\u4e8b\u52a1\u4fdd\u7559\u5b9e\u4f53\n- \u5bf9\u4e8e\u8bfb\u591a\u5199\u5c11\u7684\u5b9e\u4f53\uff0c\u8c28\u614e\u8003\u8651\u4e8c\u7ea7\u7f13\u5b58\uff082nd-level cache\uff09\uff1b\u9a8c\u8bc1\u9010\u51fa\u7b56\u7565\uff08Eviction strategy\uff09\n\n## \u6570\u636e\u8fc1\u79fb\uff08Migrations\uff09\n\n- \u4f7f\u7528 Flyway \u6216 Liquibase\uff1b\u5728\u751f\u4ea7\u73af\u5883\u4e2d\u7edd\u4e0d\u8981\u4f9d\u8d56 Hibernate \u7684\u81ea\u52a8 DDL\n- \u4fdd\u6301\u8fc1\u79fb\u662f\u5e42\u7b49\uff08Idempotent\uff09\u4e14\u5177\u6709\u589e\u91cf\u6027\u7684\uff1b\u907f\u514d\u5728\u6ca1\u6709\u8ba1\u5212\u7684\u60c5\u51b5\u4e0b\u5220\u9664\u5217\n\n## \u6d4b\u8bd5\u6570\u636e\u8bbf\u95ee\n\n- \u4f18\u5148\u4f7f\u7528 `@DataJpaTest` \u914d\u5408 Testcontainers \u6765\u6a21\u62df\u751f\u4ea7\u73af\u5883\n- \u4f7f\u7528\u65e5\u5fd7\u65ad\u8a00 SQL \u6548\u7387\uff1a\u8bbe\u7f6e `logging.level.org.hibernate.SQL=DEBUG` \u4ee5\u53ca `logging.level.org.hibernate.orm.jdbc.bind=TRACE` \u4ee5\u67e5\u770b\u53c2\u6570\u503c\n\n**\u8bb0\u4f4f**\uff1a\u4fdd\u6301\u5b9e\u4f53\u7cbe\u7b80\u3001\u67e5\u8be2\u610f\u56fe\u660e\u786e\u4e14\u4e8b\u52a1\u7b80\u77ed\u3002\u901a\u8fc7\u6293\u53d6\u7b56\u7565\uff08Fetch strategies\uff09\u548c\u6295\u5f71\uff08Projections\uff09\u6765\u9632\u6b62 N+1 \u95ee\u9898\uff0c\u5e76\u9488\u5bf9\u8bfb/\u5199\u8def\u5f84\u5efa\u7acb\u7d22\u5f15\u3002\n" + }, + "/Users/Library/Applications/xx/code/github/everything-claude-code-zh/skills/springboot-verification/SKILL.md": { + "md5": "7af68f3240d10677733cf0609e943c84", + "content": "---\nname: springboot-verification\ndescription: Spring Boot \u9879\u76ee\u7684\u9a8c\u8bc1\u5faa\u73af\uff08Verification loop\uff09\uff1a\u5305\u542b\u6784\u5efa\u3001\u9759\u6001\u5206\u6790\u3001\u5e26\u8986\u76d6\u7387\u7684\u6d4b\u8bd5\u3001\u5b89\u5168\u626b\u63cf\uff0c\u4ee5\u53ca\u5728\u53d1\u5e03\u6216 PR \u524d\u7684\u5dee\u5f02\u8bc4\u5ba1\uff08diff review\uff09\u3002\n---\n\n# Spring Boot \u9a8c\u8bc1\u5faa\u73af\uff08Verification Loop\uff09\n\n\u5728\u63d0\u4ea4 PR \u524d\u3001\u53d1\u751f\u91cd\u5927\u53d8\u66f4\u540e\u4ee5\u53ca\u9884\u90e8\u7f72\u9636\u6bb5\u8fd0\u884c\u6b64\u6d41\u7a0b\u3002\n\n## \u9636\u6bb5 1\uff1a\u6784\u5efa\uff08Build\uff09\n\n```bash\nmvn -T 4 clean verify -DskipTests\n# \u6216\u8005\n./gradlew clean assemble -x test\n```\n\n\u5982\u679c\u6784\u5efa\u5931\u8d25\uff0c\u8bf7\u505c\u6b62\u5e76\u4fee\u590d\u3002\n\n## \u9636\u6bb5 2\uff1a\u9759\u6001\u5206\u6790\uff08Static Analysis\uff09\n\nMaven\uff08\u5e38\u7528\u63d2\u4ef6\uff09\uff1a\n```bash\nmvn -T 4 spotbugs:check pmd:check checkstyle:check\n```\n\nGradle\uff08\u5982\u679c\u5df2\u914d\u7f6e\uff09\uff1a\n```bash\n./gradlew checkstyleMain pmdMain spotbugsMain\n```\n\n## \u9636\u6bb5 3\uff1a\u6d4b\u8bd5 + \u8986\u76d6\u7387\uff08Tests + Coverage\uff09\n\n```bash\nmvn -T 4 test\nmvn jacoco:report # \u9a8c\u8bc1 80% \u4ee5\u4e0a\u7684\u8986\u76d6\u7387\n# \u6216\u8005\n./gradlew test jacocoTestReport\n```\n\n\u62a5\u544a\u6307\u6807\uff1a\n- \u6d4b\u8bd5\u603b\u6570\u3001\u901a\u8fc7/\u5931\u8d25\u6570\u91cf\n- \u8986\u76d6\u7387 %\uff08\u884c/\u5206\u652f\uff09\n\n## \u9636\u6bb5 4\uff1a\u5b89\u5168\u626b\u63cf\uff08Security Scan\uff09\n\n```bash\n# \u4f9d\u8d56\u9879 CVE \u6f0f\u6d1e\u626b\u63cf\nmvn org.owasp:dependency-check-maven:check\n# \u6216\u8005\n./gradlew dependencyCheckAnalyze\n\n# \u5bc6\u94a5\uff08Secrets\uff09\u626b\u63cf (git)\ngit secrets --scan # \u5982\u679c\u5df2\u914d\u7f6e\n```\n\n## \u9636\u6bb5 5\uff1a\u4ee3\u7801\u89c4\u8303/\u683c\u5f0f\u5316\uff08Lint/Format\uff0c\u53ef\u9009\u9608\u503c\uff09\n\n```bash\nmvn spotless:apply # \u5982\u679c\u4f7f\u7528\u4e86 Spotless \u63d2\u4ef6\n./gradlew spotlessApply\n```\n\n## \u9636\u6bb5 6\uff1a\u5dee\u5f02\u8bc4\u5ba1\uff08Diff Review\uff09\n\n```bash\ngit diff --stat\ngit diff\n```\n\n\u81ea\u67e5\u6e05\u5355\uff08Checklist\uff09\uff1a\n- \u672a\u6b8b\u7559\u8c03\u8bd5\u65e5\u5fd7\uff08\u5982 `System.out`\uff0c\u6216\u7f3a\u5c11\u9632\u62a4\u68c0\u67e5\u7684 `log.debug`\uff09\n- \u9519\u8bef\u4fe1\u606f\u548c HTTP \u72b6\u6001\u7801\u5177\u6709\u660e\u786e\u8bed\u4e49\n- \u5728\u5fc5\u8981\u5904\u5df2\u5305\u542b\u4e8b\u52a1\uff08Transactions\uff09\u548c\u6821\u9a8c\uff08Validation\uff09\n- \u914d\u7f6e\u53d8\u66f4\u5df2\u8bb0\u5f55\u5728\u6587\u6863\u4e2d\n\n## \u8f93\u51fa\u6a21\u7248\uff08Output Template\uff09\n\n```\n\u9a8c\u8bc1\u62a5\u544a (VERIFICATION REPORT)\n===================\n\u6784\u5efa (Build): [\u901a\u8fc7/\u5931\u8d25]\n\u9759\u6001\u5206\u6790 (Static): [\u901a\u8fc7/\u5931\u8d25] (spotbugs/pmd/checkstyle)\n\u6d4b\u8bd5 (Tests): [\u901a\u8fc7/\u5931\u8d25] (\u901a\u8fc7 X/Y\uff0c\u8986\u76d6\u7387 Z%)\n\u5b89\u5168 (Security): [\u901a\u8fc7/\u5931\u8d25] (CVE \u53d1\u73b0\u6570\u91cf: N)\n\u5dee\u5f02 (Diff): [X \u4e2a\u6587\u4ef6\u5df2\u53d8\u66f4]\n\n\u7ed3\u8bba (Overall): [\u5c31\u7eea / \u672a\u5c31\u7eea]\n\n\u5f85\u4fee\u590d\u95ee\u9898:\n1. ...\n2. ...\n```\n\n## \u6301\u7eed\u6a21\u5f0f\uff08Continuous Mode\uff09\n\n- \u5728\u53d1\u751f\u663e\u8457\u53d8\u66f4\u65f6\uff0c\u6216\u5728\u957f\u4f1a\u8bdd\u4e2d\u6bcf 30\u201360 \u5206\u949f\u91cd\u65b0\u8fd0\u884c\u5404\u9636\u6bb5\u3002\n- \u4fdd\u6301\u77ed\u53cd\u9988\u5faa\u73af\uff1a\u8fd0\u884c `mvn -T 4 test` + spotbugs \u4ee5\u83b7\u5f97\u5feb\u901f\u53cd\u9988\u3002\n\n**\u8bb0\u4f4f**\uff1a\u5feb\u901f\u53cd\u9988\u4f18\u4e8e\u540e\u671f\u60ca\u8bb6\u3002\u4fdd\u6301\u4e25\u683c\u7684\u51c6\u5165\u95e8\u69db\u2014\u2014\u5728\u751f\u4ea7\u7cfb\u7edf\u4e2d\uff0c\u5c06\u8b66\u544a\uff08Warnings\uff09\u89c6\u4e3a\u7f3a\u9677\uff08Defects\uff09\u3002\n" + }, + "/Users/Library/Applications/xx/code/github/everything-claude-code-zh/skills/django-verification/SKILL.md": { + "md5": "e7b594d10b68d40883c7b6903e64a6ba", + "content": "---\nname: django-verification\ndescription: Django \u9879\u76ee\u7684\u9a8c\u8bc1\u5faa\u73af\uff08Verification loop\uff09\uff1a\u5305\u542b\u6570\u636e\u5e93\u8fc1\u79fb\u3001\u4ee3\u7801\u68c0\u67e5\u3001\u5e26\u8986\u76d6\u7387\u7684\u6d4b\u8bd5\u3001\u5b89\u5168\u626b\u63cf\uff0c\u4ee5\u53ca\u5728\u53d1\u5e03\u6216 PR \u524d\u7684\u90e8\u7f72\u5c31\u7eea\u68c0\u67e5\u3002\n---\n\n# Django \u9a8c\u8bc1\u5faa\u73af\uff08Verification Loop\uff09\n\n\u5728\u63d0\u4ea4 PR \u4e4b\u524d\u3001\u91cd\u5927\u53d8\u66f4\u4e4b\u540e\u4ee5\u53ca\u9884\u90e8\u7f72\uff08Pre-deploy\uff09\u4e4b\u524d\u8fd0\u884c\uff0c\u4ee5\u786e\u4fdd Django \u5e94\u7528\u7a0b\u5e8f\u7684\u8d28\u91cf\u548c\u5b89\u5168\u6027\u3002\n\n## \u9636\u6bb5 1\uff1a\u73af\u5883\u68c0\u67e5 (Phase 1: Environment Check)\n\n```bash\n# \u9a8c\u8bc1 Python \u7248\u672c\npython --version # \u5e94\u7b26\u5408\u9879\u76ee\u8981\u6c42\n\n# \u68c0\u67e5\u865a\u62df\u73af\u5883\nwhich python\npip list --outdated\n\n# \u9a8c\u8bc1\u73af\u5883\u53d8\u91cf\npython -c \"import os; import environ; print('DJANGO_SECRET_KEY set' if os.environ.get('DJANGO_SECRET_KEY') else 'MISSING: DJANGO_SECRET_KEY')\"\n```\n\n\u5982\u679c\u73af\u5883\u914d\u7f6e\u9519\u8bef\uff0c\u8bf7\u505c\u6b62\u5e76\u4fee\u590d\u3002\n\n## \u9636\u6bb5 2\uff1a\u4ee3\u7801\u8d28\u91cf\u4e0e\u683c\u5f0f\u5316 (Phase 2: Code Quality & Formatting)\n\n```bash\n# \u7c7b\u578b\u68c0\u67e5\nmypy . --config-file pyproject.toml\n\n# \u4f7f\u7528 ruff \u8fdb\u884c Lint \u68c0\u67e5\nruff check . --fix\n\n# \u4f7f\u7528 black \u8fdb\u884c\u683c\u5f0f\u5316\nblack . --check\nblack . # \u81ea\u52a8\u4fee\u590d\n\n# \u5bfc\u5165\u8bed\u53e5\u6392\u5e8f\nisort . --check-only\nisort . # \u81ea\u52a8\u4fee\u590d\n\n# Django \u7279\u6709\u7684\u68c0\u67e5\npython manage.py check --deploy\n```\n\n\u5e38\u89c1\u95ee\u9898\uff1a\n- \u516c\u5171\u51fd\u6570\u7f3a\u5931\u7c7b\u578b\u63d0\u793a\uff08Type hints\uff09\n- \u8fdd\u53cd PEP 8 \u683c\u5f0f\u89c4\u8303\n- \u672a\u6392\u5e8f\u7684\u5bfc\u5165\u8bed\u53e5\n- \u751f\u4ea7\u73af\u5883\u914d\u7f6e\u4e2d\u9057\u7559\u4e86\u8c03\u8bd5\u8bbe\u7f6e\n\n## \u9636\u6bb5 3\uff1a\u6570\u636e\u5e93\u8fc1\u79fb (Phase 3: Migrations)\n\n```bash\n# \u68c0\u67e5\u662f\u5426\u5b58\u5728\u672a\u5e94\u7528\u7684\u8fc1\u79fb\npython manage.py showmigrations\n\n# \u521b\u5efa\u7f3a\u5931\u7684\u8fc1\u79fb\npython manage.py makemigrations --check\n\n# \u6a21\u62df\u8fc1\u79fb\u5e94\u7528\uff08Dry-run\uff09\npython manage.py migrate --plan\n\n# \u5e94\u7528\u8fc1\u79fb\uff08\u6d4b\u8bd5\u73af\u5883\uff09\npython manage.py migrate\n\n# \u68c0\u67e5\u8fc1\u79fb\u51b2\u7a81\npython manage.py makemigrations --merge # \u4ec5\u5728\u5b58\u5728\u51b2\u7a81\u65f6\u8fd0\u884c\n```\n\n\u62a5\u544a\u5185\u5bb9\uff1a\n- \u5f85\u5904\u7406\u8fc1\u79fb\u7684\u6570\u91cf\n- \u4efb\u4f55\u8fc1\u79fb\u51b2\u7a81\n- \u5b58\u5728\u6a21\u578b\u53d8\u66f4\u4f46\u672a\u751f\u6210\u8fc1\u79fb\n\n## \u9636\u6bb5 4\uff1a\u6d4b\u8bd5\u4e0e\u8986\u76d6\u7387 (Phase 4: Tests + Coverage)\n\n```bash\n# \u4f7f\u7528 pytest \u8fd0\u884c\u6240\u6709\u6d4b\u8bd5\npytest --cov=apps --cov-report=html --cov-report=term-missing --reuse-db\n\n# \u8fd0\u884c\u7279\u5b9a\u5e94\u7528\u7684\u6d4b\u8bd5\npytest apps/users/tests/\n\n# \u4f7f\u7528\u6807\u8bb0\u8fd0\u884c\npytest -m \"not slow\" # \u8df3\u8fc7\u6162\u901f\u6d4b\u8bd5\npytest -m integration # \u4ec5\u8fd0\u884c\u96c6\u6210\u6d4b\u8bd5\n\n# \u8986\u76d6\u7387\u62a5\u544a\nopen htmlcov/index.html\n```\n\n\u62a5\u544a\u5185\u5bb9\uff1a\n- \u6d4b\u8bd5\u7edf\u8ba1\uff1aX \u901a\u8fc7\uff0cY \u5931\u8d25\uff0cZ \u8df3\u8fc7\n- \u603b\u4f53\u8986\u76d6\u7387\uff1aXX%\n- \u5404\u5e94\u7528\u7684\u8986\u76d6\u7387\u660e\u7ec6\n\n\u8986\u76d6\u7387\u76ee\u6807\uff1a\n\n| \u7ec4\u4ef6 | \u76ee\u6807 |\n|-----------|--------|\n| \u6a21\u578b (Models) | 90%+ |\n| \u5e8f\u5217\u5316\u5668 (Serializers) | 85%+ |\n| \u89c6\u56fe (Views) | 80%+ |\n| \u670d\u52a1 (Services) | 90%+ |\n| \u603b\u4f53 | 80%+ |\n\n## \u9636\u6bb5 5\uff1a\u5b89\u5168\u626b\u63cf (Phase 5: Security Scan)\n\n```bash\n# \u4f9d\u8d56\u9879\u6f0f\u6d1e\u626b\u63cf\npip-audit\nsafety check --full-report\n\n# Django \u5b89\u5168\u68c0\u67e5\npython manage.py check --deploy\n\n# Bandit \u5b89\u5168 Linter\nbandit -r . -f json -o bandit-report.json\n\n# \u5bc6\u94a5\u626b\u63cf\uff08\u5982\u679c\u5b89\u88c5\u4e86 gitleaks\uff09\ngitleaks detect --source . --verbose\n\n# \u73af\u5883\u53d8\u91cf\u68c0\u67e5\npython -c \"from django.core.exceptions import ImproperlyConfigured; from django.conf import settings; settings.DEBUG\"\n```\n\n\u62a5\u544a\u5185\u5bb9\uff1a\n- \u53d1\u73b0\u7684\u6709\u6f0f\u6d1e\u7684\u4f9d\u8d56\u9879\n- \u5b89\u5168\u914d\u7f6e\u95ee\u9898\n- \u68c0\u6d4b\u5230\u7684\u786c\u7f16\u7801\u5bc6\u94a5\n- DEBUG \u6a21\u5f0f\u72b6\u6001\uff08\u5728\u751f\u4ea7\u73af\u5883\u4e2d\u5e94\u4e3a False\uff09\n\n## \u9636\u6bb5 6\uff1aDjango \u7ba1\u7406\u547d\u4ee4 (Phase 6: Django Management Commands)\n\n```bash\n# \u68c0\u67e5\u6a21\u578b\u95ee\u9898\npython manage.py check\n\n# \u6536\u96c6\u9759\u6001\u6587\u4ef6\npython manage.py collectstatic --noinput --clear\n\n# \u521b\u5efa\u8d85\u7ea7\u7528\u6237\uff08\u5982\u679c\u6d4b\u8bd5\u9700\u8981\uff09\necho \"from apps.users.models import User; User.objects.create_superuser('admin@example.com', 'admin')\" | python manage.py shell\n\n# \u6570\u636e\u5e93\u5b8c\u6574\u6027\u68c0\u67e5\npython manage.py check --database default\n\n# \u7f13\u5b58\u9a8c\u8bc1\uff08\u5982\u679c\u4f7f\u7528 Redis\uff09\npython -c \"from django.core.cache import cache; cache.set('test', 'value', 10); print(cache.get('test'))\"\n```\n\n## \u9636\u6bb5 7\uff1a\u6027\u80fd\u68c0\u67e5 (Phase 7: Performance Checks)\n\n```bash\n# Django Debug Toolbar \u8f93\u51fa\uff08\u68c0\u67e5 N+1 \u67e5\u8be2\uff09\n# \u5728 DEBUG=True \u7684\u5f00\u53d1\u6a21\u5f0f\u4e0b\u8fd0\u884c\u5e76\u8bbf\u95ee\u9875\u9762\n# \u5728 SQL \u9762\u677f\u4e2d\u67e5\u627e\u91cd\u590d\u67e5\u8be2\n\n# \u67e5\u8be2\u8ba1\u6570\u5206\u6790\ndjango-admin debugsqlshell # \u5982\u679c\u5b89\u88c5\u4e86 django-debug-sqlshell\n\n# \u68c0\u67e5\u7f3a\u5931\u7684\u7d22\u5f15\npython manage.py shell << EOF\nfrom django.db import connection\nwith connection.cursor() as cursor:\n cursor.execute(\"SELECT table_name, index_name FROM information_schema.statistics WHERE table_schema = 'public'\")\n print(cursor.fetchall())\nEOF\n```\n\n\u62a5\u544a\u5185\u5bb9\uff1a\n- \u6bcf\u4e2a\u9875\u9762\u7684\u67e5\u8be2\u6570\u91cf\uff08\u5178\u578b\u9875\u9762\u5e94 < 50\uff09\n- \u7f3a\u5931\u7684\u6570\u636e\u5e93\u7d22\u5f15\n- \u68c0\u6d4b\u5230\u7684\u91cd\u590d\u67e5\u8be2\n\n## \u9636\u6bb5 8\uff1a\u9759\u6001\u8d44\u6e90 (Phase 8: Static Assets)\n\n```bash\n# \u68c0\u67e5 npm \u4f9d\u8d56\u9879\uff08\u5982\u679c\u4f7f\u7528 npm\uff09\nnpm audit\nnpm audit fix\n\n# \u6784\u5efa\u9759\u6001\u6587\u4ef6\uff08\u5982\u679c\u4f7f\u7528 webpack/vite\uff09\nnpm run build\n\n# \u9a8c\u8bc1\u9759\u6001\u6587\u4ef6\nls -la staticfiles/\npython manage.py findstatic css/style.css\n```\n\n## \u9636\u6bb5 9\uff1a\u914d\u7f6e\u5ba1\u67e5 (Phase 9: Configuration Review)\n\n```python\n# \u5728 Python shell \u4e2d\u8fd0\u884c\u4ee5\u9a8c\u8bc1\u8bbe\u7f6e\npython manage.py shell << EOF\nfrom django.conf import settings\nimport os\n\n# \u5173\u952e\u68c0\u67e5\u9879\nchecks = {\n 'DEBUG is False': not settings.DEBUG,\n 'SECRET_KEY set': bool(settings.SECRET_KEY and len(settings.SECRET_KEY) > 30),\n 'ALLOWED_HOSTS set': len(settings.ALLOWED_HOSTS) > 0,\n 'HTTPS enabled': getattr(settings, 'SECURE_SSL_REDIRECT', False),\n 'HSTS enabled': getattr(settings, 'SECURE_HSTS_SECONDS', 0) > 0,\n 'Database configured': settings.DATABASES['default']['ENGINE'] != 'django.db.backends.sqlite3',\n}\n\nfor check, result in checks.items():\n status = '\u2713' if result else '\u2717'\n print(f\"{status} {check}\")\nEOF\n```\n\n## \u9636\u6bb5 10\uff1a\u65e5\u5fd7\u914d\u7f6e (Phase 10: Logging Configuration)\n\n```bash\n# \u6d4b\u8bd5\u65e5\u5fd7\u8f93\u51fa\npython manage.py shell << EOF\nimport logging\nlogger = logging.getLogger('django')\nlogger.warning('Test warning message')\nlogger.error('Test error message')\nEOF\n\n# \u68c0\u67e5\u65e5\u5fd7\u6587\u4ef6\uff08\u5982\u679c\u5df2\u914d\u7f6e\uff09\ntail -f /var/log/django/django.log\n```\n\n## \u9636\u6bb5 11\uff1aAPI \u6587\u6863 (Phase 11: API Documentation)\n\n```bash\n# \u751f\u6210 Schema\npython manage.py generateschema --format openapi-json > schema.json\n\n# \u9a8c\u8bc1 Schema\n# \u68c0\u67e5 schema.json \u662f\u5426\u4e3a\u6709\u6548\u7684 JSON\npython -c \"import json; json.load(open('schema.json'))\"\n\n# \u8bbf\u95ee Swagger UI\uff08\u5982\u679c\u4f7f\u7528 drf-yasg\uff09\n# \u5728\u6d4f\u89c8\u5668\u4e2d\u8bbf\u95ee http://localhost:8000/swagger/\n```\n\n## \u9636\u6bb5 12\uff1a\u5dee\u5f02\u5ba1\u67e5 (Phase 12: Diff Review)\n\n```bash\n# \u663e\u793a diff \u7edf\u8ba1\u4fe1\u606f\ngit diff --stat\n\n# \u663e\u793a\u5b9e\u9645\u53d8\u66f4\ngit diff\n\n# \u663e\u793a\u5df2\u53d8\u66f4\u7684\u6587\u4ef6\ngit diff --name-only\n\n# \u68c0\u67e5\u5e38\u89c1\u95ee\u9898\ngit diff | grep -i \"todo\\|fixme\\|hack\\|xxx\"\ngit diff | grep \"print(\" # \u8c03\u8bd5\u8bed\u53e5\ngit diff | grep \"DEBUG = True\" # \u8c03\u8bd5\u6a21\u5f0f\ngit diff | grep \"import pdb\" # \u8c03\u8bd5\u5668\n```\n\n\u68c0\u67e5\u6e05\u5355\uff1a\n- \u65e0\u8c03\u8bd5\u8bed\u53e5\uff08print, pdb, breakpoint()\uff09\n- \u5173\u952e\u4ee3\u7801\u4e2d\u65e0 TODO/FIXME \u6ce8\u91ca\n- \u65e0\u786c\u7f16\u7801\u7684\u5bc6\u94a5\u6216\u51ed\u636e\n- \u6a21\u578b\u53d8\u66f4\u5df2\u5305\u542b\u6570\u636e\u5e93\u8fc1\u79fb\n- \u914d\u7f6e\u53d8\u66f4\u5df2\u8bb0\u5f55\u6587\u6863\n- \u5916\u90e8\u8c03\u7528\u5df2\u5305\u542b\u9519\u8bef\u5904\u7406\n- \u89c6\u9700\u8981\u5305\u542b\u4e8b\u52a1\u7ba1\u7406\uff08Transaction management\uff09\n\n## \u8f93\u51fa\u6a21\u677f (Output Template)\n\n```\nDJANGO \u9a8c\u8bc1\u62a5\u544a\n==========================\n\n\u9636\u6bb5 1\uff1a\u73af\u5883\u68c0\u67e5 (Phase 1: Environment Check)\n \u2713 Python 3.11.5\n \u2713 \u865a\u62df\u73af\u5883\u5df2\u6fc0\u6d3b\n \u2713 \u6240\u6709\u73af\u5883\u53d8\u91cf\u5df2\u8bbe\u7f6e\n\n\u9636\u6bb5 2\uff1a\u4ee3\u7801\u8d28\u91cf (Phase 2: Code Quality)\n \u2713 mypy: \u65e0\u7c7b\u578b\u9519\u8bef\n \u2717 ruff: \u53d1\u73b0 3 \u4e2a\u95ee\u9898\uff08\u5df2\u81ea\u52a8\u4fee\u590d\uff09\n \u2713 black: \u65e0\u683c\u5f0f\u5316\u95ee\u9898\n \u2713 isort: \u5bfc\u5165\u8bed\u53e5\u5df2\u6392\u5e8f\n \u2713 manage.py check: \u65e0\u95ee\u9898\n\n\u9636\u6bb5 3\uff1a\u6570\u636e\u5e93\u8fc1\u79fb (Phase 3: Migrations)\n \u2713 \u65e0\u672a\u5e94\u7528\u7684\u8fc1\u79fb\n \u2713 \u65e0\u8fc1\u79fb\u51b2\u7a81\n \u2713 \u6240\u6709\u6a21\u578b\u5747\u6709\u8fc1\u79fb\n\n\u9636\u6bb5 4\uff1a\u6d4b\u8bd5\u4e0e\u8986\u76d6\u7387 (Phase 4: Tests + Coverage)\n \u6d4b\u8bd5\uff1a247 \u901a\u8fc7\uff0c0 \u5931\u8d25\uff0c5 \u8df3\u8fc7\n \u8986\u76d6\u7387\uff1a\n \u603b\u4f53\uff1a87%\n users: 92%\n products: 89%\n orders: 85%\n payments: 91%\n\n\u9636\u6bb5 5\uff1a\u5b89\u5168\u626b\u63cf (Phase 5: Security Scan)\n \u2717 pip-audit: \u53d1\u73b0 2 \u4e2a\u6f0f\u6d1e\uff08\u9700\u8981\u4fee\u590d\uff09\n \u2713 safety check: \u65e0\u95ee\u9898\n \u2713 bandit: \u65e0\u5b89\u5168\u95ee\u9898\n \u2713 \u672a\u68c0\u6d4b\u5230\u5bc6\u94a5\n \u2713 DEBUG = False\n\n\u9636\u6bb5 6\uff1aDjango \u547d\u4ee4 (Phase 6: Django Commands)\n \u2713 collectstatic \u5df2\u5b8c\u6210\n \u2713 \u6570\u636e\u5e93\u5b8c\u6574\u6027\u6b63\u5e38\n \u2713 \u7f13\u5b58\u540e\u7aef\u53ef\u8fde\u63a5\n\n\u9636\u6bb5 7\uff1a\u6027\u80fd (Phase 7: Performance)\n \u2713 \u672a\u68c0\u6d4b\u5230 N+1 \u67e5\u8be2\n \u2713 \u6570\u636e\u5e93\u7d22\u5f15\u5df2\u914d\u7f6e\n \u2713 \u67e5\u8be2\u8ba1\u6570\u5728\u53ef\u63a5\u53d7\u8303\u56f4\u5185\n\n\u9636\u6bb5 8\uff1a\u9759\u6001\u8d44\u6e90 (Phase 8: Static Assets)\n \u2713 npm audit: \u65e0\u6f0f\u6d1e\n \u2713 \u8d44\u6e90\u6784\u5efa\u6210\u529f\n \u2713 \u9759\u6001\u6587\u4ef6\u5df2\u6536\u96c6\n\n\u9636\u6bb5 9\uff1a\u914d\u7f6e\u5ba1\u67e5 (Phase 9: Configuration)\n \u2713 DEBUG = False\n \u2713 SECRET_KEY \u5df2\u914d\u7f6e\n \u2713 ALLOWED_HOSTS \u5df2\u8bbe\u7f6e\n \u2713 HTTPS \u5df2\u542f\u7528\n \u2713 HSTS \u5df2\u542f\u7528\n \u2713 \u6570\u636e\u5e93\u5df2\u914d\u7f6e\n\n\u9636\u6bb5 10\uff1a\u65e5\u5fd7 (Phase 10: Logging)\n \u2713 \u65e5\u5fd7\u5df2\u914d\u7f6e\n \u2713 \u65e5\u5fd7\u6587\u4ef6\u53ef\u5199\n\n\u9636\u6bb5 11\uff1aAPI \u6587\u6863 (Phase 11: API Documentation)\n \u2713 Schema \u5df2\u751f\u6210\n \u2713 Swagger UI \u53ef\u8bbf\u95ee\n\n\u9636\u6bb5 12\uff1a\u5dee\u5f02\u5ba1\u67e5 (Phase 12: Diff Review)\n \u6587\u4ef6\u53d8\u66f4\uff1a12\n +450, -120 \u884c\n \u2713 \u65e0\u8c03\u8bd5\u8bed\u53e5\n \u2713 \u65e0\u786c\u7f16\u7801\u5bc6\u94a5\n \u2713 \u5df2\u5305\u542b\u8fc1\u79fb\n\n\u5efa\u8bae\uff1a\u26a0\ufe0f \u5728\u90e8\u7f72\u524d\u4fee\u590d pip-audit \u6f0f\u6d1e\n\n\u540e\u7eed\u6b65\u9aa4\uff1a\n1. \u66f4\u65b0\u6709\u6f0f\u6d1e\u7684\u4f9d\u8d56\u9879\n2. \u91cd\u65b0\u8fd0\u884c\u5b89\u5168\u626b\u63cf\n3. \u90e8\u7f72\u5230\u9884\u53d1\u5e03\u73af\u5883\u8fdb\u884c\u6700\u7ec8\u6d4b\u8bd5\n```\n\n## \u9884\u90e8\u7f72\u68c0\u67e5\u6e05\u5355 (Pre-Deployment Checklist)\n\n- [ ] \u6240\u6709\u6d4b\u8bd5\u5747\u901a\u8fc7\n- [ ] \u8986\u76d6\u7387 \u2265 80%\n- [ ] \u65e0\u5b89\u5168\u6f0f\u6d1e\n- [ ] \u65e0\u672a\u5e94\u7528\u7684\u8fc1\u79fb\n- [ ] \u751f\u4ea7\u73af\u5883\u8bbe\u7f6e\u4e2d DEBUG = False\n- [ ] SECRET_KEY \u5df2\u6b63\u786e\u914d\u7f6e\n- [ ] ALLOWED_HOSTS \u5df2\u6b63\u786e\u8bbe\u7f6e\n- [ ] \u6570\u636e\u5e93\u5907\u4efd\u5df2\u542f\u7528\n- [ ] \u9759\u6001\u6587\u4ef6\u5df2\u6536\u96c6\u5e76\u63d0\u4f9b\u670d\u52a1\n- [ ] \u65e5\u5fd7\u5df2\u914d\u7f6e\u5e76\u6b63\u5e38\u5de5\u4f5c\n- [ ] \u9519\u8bef\u76d1\u63a7\uff08Sentry \u7b49\uff09\u5df2\u914d\u7f6e\n- [ ] CDN \u5df2\u914d\u7f6e\uff08\u5982\u9002\u7528\uff09\n- [ ] Redis/\u7f13\u5b58\u540e\u7aef\u5df2\u914d\u7f6e\n- [ ] Celery worker \u6b63\u5728\u8fd0\u884c\uff08\u5982\u9002\u7528\uff09\n- [ ] HTTPS/SSL \u5df2\u914d\u7f6e\n- [ ] \u73af\u5883\u53d8\u91cf\u5df2\u8bb0\u5f55\u6587\u6863\n\n## \u6301\u7eed\u96c6\u6210 (Continuous Integration)\n\n### GitHub Actions \u793a\u4f8b\n\n```yaml\n# .github/workflows/django-verification.yml\nname: Django Verification\n\non: [push, pull_request]\n\njobs:\n verify:\n runs-on: ubuntu-latest\n services:\n postgres:\n image: postgres:14\n env:\n POSTGRES_PASSWORD: postgres\n options: >-\n --health-cmd pg_isready\n --health-interval 10s\n --health-timeout 5s\n --health-retries 5\n\n steps:\n - uses: actions/checkout@v3\n\n - name: Set up Python\n uses: actions/setup-python@v4\n with:\n python-version: '3.11'\n\n - name: Cache pip\n uses: actions/cache@v3\n with:\n path: ~/.cache/pip\n key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}\n\n - name: Install dependencies\n run: |\n pip install -r requirements.txt\n pip install ruff black mypy pytest pytest-django pytest-cov bandit safety pip-audit\n\n - name: Code quality checks\n run: |\n ruff check .\n black . --check\n isort . --check-only\n mypy .\n\n - name: Security scan\n run: |\n bandit -r . -f json -o bandit-report.json\n safety check --full-report\n pip-audit\n\n - name: Run tests\n env:\n DATABASE_URL: postgres://postgres:postgres@localhost:5432/test\n DJANGO_SECRET_KEY: test-secret-key\n run: |\n pytest --cov=apps --cov-report=xml --cov-report=term-missing\n\n - name: Upload coverage\n uses: codecov/codecov-action@v3\n```\n\n## \u5feb\u901f\u53c2\u8003 (Quick Reference)\n\n| \u68c0\u67e5\u9879 | \u547d\u4ee4 |\n|-------|---------|\n| \u73af\u5883 (Environment) | `python --version` |\n| \u7c7b\u578b\u68c0\u67e5 (Type checking) | `mypy .` |\n| \u4ee3\u7801\u68c0\u67e5 (Linting) | `ruff check .` |\n| \u683c\u5f0f\u5316 (Formatting) | `black . --check` |\n| \u6570\u636e\u5e93\u8fc1\u79fb (Migrations) | `python manage.py makemigrations --check` |\n| \u6d4b\u8bd5 (Tests) | `pytest --cov=apps` |\n| \u5b89\u5168 (Security) | `pip-audit && bandit -r .` |\n| Django \u68c0\u67e5 (Django check) | `python manage.py check --deploy` |\n| \u6536\u96c6\u9759\u6001\u8d44\u6e90 (Collectstatic) | `python manage.py collectstatic --noinput` |\n| \u5dee\u5f02\u7edf\u8ba1 (Diff stats) | `git diff --stat` |\n\n\u8bb0\u4f4f\uff1a\u81ea\u52a8\u5316\u9a8c\u8bc1\u53ef\u4ee5\u6355\u6349\u5e38\u89c1\u95ee\u9898\uff0c\u4f46\u4e0d\u80fd\u53d6\u4ee3\u624b\u52a8\u4ee3\u7801\u5ba1\u67e5\u548c\u5728\u9884\u53d1\u5e03\u73af\u5883\uff08Staging environment\uff09\u4e2d\u7684\u6d4b\u8bd5\u3002\n" + }, + "/Users/Library/Applications/xx/code/github/everything-claude-code-zh/skills/java-coding-standards/SKILL.md": { + "md5": "2965b1ddf407a055a8f1f413c17aef12", + "content": "---\nname: java-coding-standards\ndescription: Spring Boot \u670d\u52a1\u7684 Java \u7f16\u7801\u89c4\u8303\uff1a\u547d\u540d\u3001\u4e0d\u53ef\u53d8\u6027\uff08Immutability\uff09\u3001Optional \u4f7f\u7528\u3001\u6d41\uff08Streams\uff09\u3001\u5f02\u5e38\u5904\u7406\u3001\u6cdb\u578b\uff08Generics\uff09\u548c\u9879\u76ee\u5e03\u5c40\u3002\n---\n\n# Java \u7f16\u7801\u89c4\u8303\n\n\u9002\u7528\u4e8e Spring Boot \u670d\u52a1\u4e2d\u6613\u8bfb\u3001\u53ef\u7ef4\u62a4\u7684 Java (17+) \u4ee3\u7801\u89c4\u8303\u3002\n\n## \u6838\u5fc3\u539f\u5219\n\n- \u6e05\u6670\u80dc\u8fc7\u5947\u5de7\n- \u9ed8\u8ba4\u4e0d\u53ef\u53d8\uff08Immutable\uff09\uff1b\u5c3d\u91cf\u51cf\u5c11\u5171\u4eab\u7684\u53ef\u53d8\u72b6\u6001\n- \u5feb\u901f\u5931\u8d25\u5e76\u629b\u51fa\u6709\u610f\u4e49\u7684\u5f02\u5e38\n- \u4fdd\u6301\u4e00\u81f4\u7684\u547d\u540d\u548c\u5305\u7ed3\u6784\n\n## \u547d\u540d\n\n```java\n// \u2705 \u7c7b\uff08Classes\uff09/ \u8bb0\u5f55\uff08Records\uff09\uff1a\u5927\u9a7c\u5cf0\u5f0f\uff08PascalCase\uff09\npublic class MarketService {}\npublic record Money(BigDecimal amount, Currency currency) {}\n\n// \u2705 \u65b9\u6cd5/\u5b57\u6bb5\uff1a\u5c0f\u9a7c\u5cf0\u5f0f\uff08camelCase\uff09\nprivate final MarketRepository marketRepository;\npublic Market findBySlug(String slug) {}\n\n// \u2705 \u5e38\u91cf\uff1a\u5927\u5199\u4e0b\u5212\u7ebf\u547d\u540d\u5f0f\uff08UPPER_SNAKE_CASE\uff09\nprivate static final int MAX_PAGE_SIZE = 100;\n```\n\n## \u4e0d\u53ef\u53d8\u6027\uff08Immutability\uff09\n\n```java\n// \u2705 \u4f18\u5148\u4f7f\u7528\u8bb0\u5f55\uff08Records\uff09\u548c final \u5b57\u6bb5\npublic record MarketDto(Long id, String name, MarketStatus status) {}\n\npublic class Market {\n private final Long id;\n private final String name;\n // \u4ec5\u63d0\u4f9b getter\uff0c\u4e0d\u63d0\u4f9b setter\n}\n```\n\n## Optional \u4f7f\u7528\n\n```java\n// \u2705 find* \u65b9\u6cd5\u8fd4\u56de Optional\nOptional market = marketRepository.findBySlug(slug);\n\n// \u2705 \u4f7f\u7528 map/flatMap \u800c\u4e0d\u662f get()\nreturn market\n .map(MarketResponse::from)\n .orElseThrow(() -> new EntityNotFoundException(\"Market not found\"));\n```\n\n## \u6d41\uff08Streams\uff09\u6700\u4f73\u5b9e\u8df5\n\n```java\n// \u2705 \u4f7f\u7528\u6d41\u8fdb\u884c\u8f6c\u6362\uff0c\u4fdd\u6301\u6d41\u6c34\u7ebf\u7b80\u77ed\nList names = markets.stream()\n .map(Market::name)\n .filter(Objects::nonNull)\n .toList();\n\n// \u274c \u907f\u514d\u590d\u6742\u7684\u5d4c\u5957\u6d41\uff1b\u4e3a\u4e86\u6e05\u6670\u8d77\u89c1\uff0c\u4f18\u5148\u4f7f\u7528\u5faa\u73af\n```\n\n## \u5f02\u5e38\u5904\u7406\n\n- \u9886\u57df\u9519\u8bef\u4f7f\u7528\u975e\u53d7\u68c0\u5f02\u5e38\uff08Unchecked Exceptions\uff09\uff1b\u4e3a\u6280\u672f\u5f02\u5e38\u5305\u88c5\u4e0a\u4e0b\u6587\u4fe1\u606f\n- \u521b\u5efa\u9886\u57df\u7279\u5b9a\u7684\u5f02\u5e38\uff08\u4f8b\u5982 `MarketNotFoundException`\uff09\n- \u907f\u514d\u5bbd\u6cdb\u7684 `catch (Exception ex)`\uff0c\u9664\u975e\u662f\u8fdb\u884c\u96c6\u4e2d\u5f0f\u91cd\u629b\uff08Rethrow\uff09\u6216\u65e5\u5fd7\u8bb0\u5f55\n\n```java\nthrow new MarketNotFoundException(slug);\n```\n\n## \u6cdb\u578b\u4e0e\u7c7b\u578b\u5b89\u5168\n\n- \u907f\u514d\u539f\u59cb\u7c7b\u578b\uff08Raw Types\uff09\uff1b\u58f0\u660e\u6cdb\u578b\u53c2\u6570\n- \u53ef\u590d\u7528\u5de5\u5177\u7c7b\u4f18\u5148\u4f7f\u7528\u6709\u754c\u6cdb\u578b\uff08Bounded Generics\uff09\n\n```java\npublic Map indexById(Collection items) { ... }\n```\n\n## \u9879\u76ee\u7ed3\u6784 (Maven/Gradle)\n\n```\nsrc/main/java/com/example/app/\n config/\n controller/\n service/\n repository/\n domain/\n dto/\n util/\nsrc/main/resources/\n application.yml\nsrc/test/java/... (\u7ed3\u6784\u4e0e main \u4fdd\u6301\u955c\u50cf)\n```\n\n## \u683c\u5f0f\u4e0e\u98ce\u683c\n\n- \u4e00\u81f4\u5730\u4f7f\u7528 2 \u6216 4 \u4e2a\u7a7a\u683c\uff08\u9075\u5faa\u9879\u76ee\u6807\u51c6\uff09\n- \u6bcf\u4e2a\u6587\u4ef6\u4ec5\u5305\u542b\u4e00\u4e2a\u516c\u5171\u9876\u5c42\u7c7b\u578b\n- \u4fdd\u6301\u65b9\u6cd5\u7b80\u77ed\u4e14\u805a\u7126\uff1b\u63d0\u53d6\u8f85\u52a9\u65b9\u6cd5\uff08Helper methods\uff09\n- \u6210\u5458\u6392\u5e8f\uff1a\u5e38\u91cf\u3001\u5b57\u6bb5\u3001\u6784\u9020\u51fd\u6570\u3001\u516c\u5171\u65b9\u6cd5\u3001\u53d7\u4fdd\u62a4\u65b9\u6cd5\u3001\u79c1\u6709\u65b9\u6cd5\n\n## \u9700\u907f\u514d\u7684\u4ee3\u7801\u574f\u5473\u9053\uff08Code Smells\uff09\n\n- \u957f\u53c2\u6570\u5217\u8868 \u2192 \u4f7f\u7528 DTO/\u6784\u5efa\u5668\uff08Builders\uff09\n- \u6df1\u5c42\u5d4c\u5957 \u2192 \u5c3d\u65e9\u8fd4\u56de\uff08Early returns\uff09\n- \u9b54\u6cd5\u6570\u5b57 \u2192 \u5177\u540d\u5e38\u91cf\n- \u9759\u6001\u53ef\u53d8\u72b6\u6001 \u2192 \u4f18\u5148\u4f7f\u7528\u4f9d\u8d56\u6ce8\u5165\uff08Dependency Injection\uff09\n- \u9759\u9ed8\u7684 catch \u5757 \u2192 \u8bb0\u5f55\u65e5\u5fd7\u5e76\u5904\u7406\u6216\u91cd\u629b\n\n## \u65e5\u5fd7\u8bb0\u5f55\n\n```java\nprivate static final Logger log = LoggerFactory.getLogger(MarketService.class);\nlog.info(\"fetch_market slug={}\", slug);\nlog.error(\"failed_fetch_market slug={}\", slug, ex);\n```\n\n## \u7a7a\u503c\u5904\u7406\uff08Null Handling\uff09\n\n- \u4ec5\u5728\u4e0d\u53ef\u907f\u514d\u65f6\u63a5\u53d7 `@Nullable`\uff1b\u5426\u5219\u4f7f\u7528 `@NonNull`\n- \u5728\u8f93\u5165\u4e0a\u4f7f\u7528 Bean \u6821\u9a8c\uff08`@NotNull`, `@NotBlank`\uff09\n\n## \u6d4b\u8bd5\u9884\u671f\n\n- \u4f7f\u7528 JUnit 5 + AssertJ \u8fdb\u884c\u6d41\u5f0f\u65ad\u8a00\uff08Fluent Assertions\uff09\n- \u4f7f\u7528 Mockito \u8fdb\u884c\u6a21\u62df\uff08Mocking\uff09\uff1b\u5c3d\u53ef\u80fd\u907f\u514d\u90e8\u5206\u6a21\u62df\uff08Partial Mocks\uff09\n- \u503e\u5411\u4e8e\u786e\u5b9a\u6027\u6d4b\u8bd5\uff1b\u4e0d\u8981\u5305\u542b\u9690\u85cf\u7684 sleep \u64cd\u4f5c\n\n**\u8bb0\u4f4f**\uff1a\u4fdd\u6301\u4ee3\u7801\u7684\u610f\u56fe\u6e05\u6670\u3001\u7c7b\u578b\u5b89\u5168\u4e14\u5177\u6709\u53ef\u89c2\u6d4b\u6027\u3002\u9664\u975e\u8bc1\u660e\u6709\u5fc5\u8981\uff0c\u5426\u5219\u5e94\u4f18\u5148\u8003\u8651\u53ef\u7ef4\u62a4\u6027\u800c\u975e\u5fae\u4f18\u5316\u3002\n" + }, + "/Users/Library/Applications/xx/code/github/everything-claude-code-zh/skills/django-tdd/SKILL.md": { + "md5": "134e3d2ad53f049515358305758fd308", + "content": "---\nname: django-tdd\ndescription: \u4f7f\u7528 pytest-django\u3001\u6d4b\u8bd5\u9a71\u52a8\u5f00\u53d1\uff08TDD\uff09\u65b9\u6cd5\u8bba\u3001factory_boy\u3001Mock \u6a21\u62df\u3001\u8986\u76d6\u7387\u7edf\u8ba1\u4ee5\u53ca Django REST Framework API \u6d4b\u8bd5\u7684 Django \u6d4b\u8bd5\u7b56\u7565\u3002\n---\n\n# Django TDD \u6d4b\u8bd5\u6307\u5357\n\n\u4f7f\u7528 pytest\u3001factory_boy \u548c Django REST Framework \u5bf9 Django \u5e94\u7528\u8fdb\u884c\u6d4b\u8bd5\u9a71\u52a8\u5f00\u53d1\uff08TDD\uff09\u3002\n\n## \u4f55\u65f6\u6fc0\u6d3b\n\n- \u7f16\u5199\u65b0\u7684 Django \u5e94\u7528\u7a0b\u5e8f\u65f6\n- \u5b9e\u73b0 Django REST Framework API \u65f6\n- \u6d4b\u8bd5 Django \u6a21\u578b\uff08Models\uff09\u3001\u89c6\u56fe\uff08Views\uff09\u548c\u5e8f\u5217\u5316\u5668\uff08Serializers\uff09\u65f6\n- \u4e3a Django \u9879\u76ee\u642d\u5efa\u6d4b\u8bd5\u57fa\u7840\u8bbe\u65bd\u65f6\n\n## Django \u7684 TDD \u5de5\u4f5c\u6d41\n\n### \u7ea2-\u7eff-\u91cd\u6784\u5faa\u73af\uff08Red-Green-Refactor Cycle\uff09\n\n```python\n# \u6b65\u9aa4 1\uff1a\u7ea2\u8272\uff08RED\uff09 - \u7f16\u5199\u5931\u8d25\u7684\u6d4b\u8bd5\ndef test_user_creation():\n user = User.objects.create_user(email='test@example.com', password='testpass123')\n assert user.email == 'test@example.com'\n assert user.check_password('testpass123')\n assert not user.is_staff\n\n# \u6b65\u9aa4 2\uff1a\u7eff\u8272\uff08GREEN\uff09 - \u7f16\u5199\u4ee3\u7801\u4f7f\u6d4b\u8bd5\u901a\u8fc7\n# \u521b\u5efa User \u6a21\u578b\u6216\u5de5\u5382\uff08Factory\uff09\n\n# \u6b65\u9aa4 3\uff1a\u91cd\u6784\uff08REFACTOR\uff09 - \u5728\u4fdd\u6301\u6d4b\u8bd5\u901a\u8fc7\u7684\u524d\u63d0\u4e0b\u4f18\u5316\u4ee3\u7801\n```\n\n## \u73af\u5883\u642d\u5efa\n\n### pytest \u914d\u7f6e\n\n```ini\n# pytest.ini\n[pytest]\nDJANGO_SETTINGS_MODULE = config.settings.test\ntestpaths = tests\npython_files = test_*.py\npython_classes = Test*\npython_functions = test_*\naddopts =\n --reuse-db\n --nomigrations\n --cov=apps\n --cov-report=html\n --cov-report=term-missing\n --strict-markers\nmarkers =\n slow: \u6807\u8bb0\u4e3a\u8017\u65f6\u8f83\u957f\u7684\u6d4b\u8bd5\n integration: \u6807\u8bb0\u4e3a\u96c6\u6210\u6d4b\u8bd5\n```\n\n### \u6d4b\u8bd5\u8bbe\u7f6e\uff08Settings\uff09\n\n```python\n# config/settings/test.py\nfrom .base import *\n\nDEBUG = True\nDATABASES = {\n 'default': {\n 'ENGINE': 'django.db.backends.sqlite3',\n 'NAME': ':memory:',\n }\n}\n\n# \u7981\u7528\u8fc1\u79fb\u4ee5\u63d0\u5347\u901f\u5ea6\nclass DisableMigrations:\n def __contains__(self, item):\n return True\n\n def __getitem__(self, item):\n return None\n\nMIGRATION_MODULES = DisableMigrations()\n\n# \u4f7f\u7528\u66f4\u5feb\u7684\u5bc6\u7801\u54c8\u5e0c\u7b97\u6cd5\nPASSWORD_HASHERS = [\n 'django.contrib.auth.hashers.MD5PasswordHasher',\n]\n\n# \u90ae\u4ef6\u540e\u7aef\u914d\u7f6e\nEMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'\n\n# Celery \u59cb\u7ec8\u540c\u6b65\u6267\u884c\nCELERY_TASK_ALWAYS_EAGER = True\nCELERY_TASK_EAGER_PROPAGATES = True\n```\n\n### conftest.py\n\n```python\n# tests/conftest.py\nimport pytest\nfrom django.utils import timezone\nfrom django.contrib.auth import get_user_model\n\nUser = get_user_model()\n\n@pytest.fixture(autouse=True)\ndef timezone_settings(settings):\n \"\"\"\u786e\u4fdd\u65f6\u533a\u4e00\u81f4\u3002\"\"\"\n settings.TIME_ZONE = 'UTC'\n\n@pytest.fixture\ndef user(db):\n \"\"\"\u521b\u5efa\u6d4b\u8bd5\u7528\u6237\u3002\"\"\"\n return User.objects.create_user(\n email='test@example.com',\n password='testpass123',\n username='testuser'\n )\n\n@pytest.fixture\ndef admin_user(db):\n \"\"\"\u521b\u5efa\u7ba1\u7406\u5458\u7528\u6237\u3002\"\"\"\n return User.objects.create_superuser(\n email='admin@example.com',\n password='adminpass123',\n username='admin'\n )\n\n@pytest.fixture\ndef authenticated_client(client, user):\n \"\"\"\u8fd4\u56de\u5df2\u8ba4\u8bc1\u7684\u5ba2\u6237\u7aef\u3002\"\"\"\n client.force_login(user)\n return client\n\n@pytest.fixture\ndef api_client():\n \"\"\"\u8fd4\u56de DRF API \u5ba2\u6237\u7aef\u3002\"\"\"\n from rest_framework.test import APIClient\n return APIClient()\n\n@pytest.fixture\ndef authenticated_api_client(api_client, user):\n \"\"\"\u8fd4\u56de\u5df2\u8ba4\u8bc1\u7684 API \u5ba2\u6237\u7aef\u3002\"\"\"\n api_client.force_authenticate(user=user)\n return api_client\n```\n\n## Factory Boy\n\n### \u5de5\u5382\u914d\u7f6e\n\n```python\n# tests/factories.py\nimport factory\nfrom factory import fuzzy\nfrom datetime import datetime, timedelta\nfrom django.contrib.auth import get_user_model\nfrom apps.products.models import Product, Category\n\nUser = get_user_model()\n\nclass UserFactory(factory.django.DjangoModelFactory):\n \"\"\"User \u6a21\u578b\u7684\u5de5\u5382\u7c7b\u3002\"\"\"\n\n class Meta:\n model = User\n\n email = factory.Sequence(lambda n: f\"user{n}@example.com\")\n username = factory.Sequence(lambda n: f\"user{n}\")\n password = factory.PostGenerationMethodCall('set_password', 'testpass123')\n first_name = factory.Faker('first_name')\n last_name = factory.Faker('last_name')\n is_active = True\n\nclass CategoryFactory(factory.django.DjangoModelFactory):\n \"\"\"Category \u6a21\u578b\u7684\u5de5\u5382\u7c7b\u3002\"\"\"\n\n class Meta:\n model = Category\n\n name = factory.Faker('word')\n slug = factory.LazyAttribute(lambda obj: obj.name.lower())\n description = factory.Faker('text')\n\nclass ProductFactory(factory.django.DjangoModelFactory):\n \"\"\"Product \u6a21\u578b\u7684\u5de5\u5382\u7c7b\u3002\"\"\"\n\n class Meta:\n model = Product\n\n name = factory.Faker('sentence', nb_words=3)\n slug = factory.LazyAttribute(lambda obj: obj.name.lower().replace(' ', '-'))\n description = factory.Faker('text')\n price = fuzzy.FuzzyDecimal(10.00, 1000.00, 2)\n stock = fuzzy.FuzzyInteger(0, 100)\n is_active = True\n category = factory.SubFactory(CategoryFactory)\n created_by = factory.SubFactory(UserFactory)\n\n @factory.post_generation\n def tags(self, create, extracted, **kwargs):\n \"\"\"\u4e3a\u4ea7\u54c1\u6dfb\u52a0\u6807\u7b7e\u3002\"\"\"\n if not create:\n return\n if extracted:\n for tag in extracted:\n self.tags.add(tag)\n```\n\n### \u4f7f\u7528\u5de5\u5382\n\n```python\n# tests/test_models.py\nimport pytest\nfrom tests.factories import ProductFactory, UserFactory\n\ndef test_product_creation():\n \"\"\"\u6d4b\u8bd5\u4f7f\u7528\u5de5\u5382\u521b\u5efa\u4ea7\u54c1\u3002\"\"\"\n product = ProductFactory(price=100.00, stock=50)\n assert product.price == 100.00\n assert product.stock == 50\n assert product.is_active is True\n\ndef test_product_with_tags():\n \"\"\"\u6d4b\u8bd5\u5e26\u6709\u6807\u7b7e\u7684\u4ea7\u54c1\u3002\"\"\"\n tags = [TagFactory(name='electronics'), TagFactory(name='new')]\n product = ProductFactory(tags=tags)\n assert product.tags.count() == 2\n\ndef test_multiple_products():\n \"\"\"\u6d4b\u8bd5\u521b\u5efa\u591a\u4e2a\u4ea7\u54c1\u3002\"\"\"\n products = ProductFactory.create_batch(10)\n assert len(products) == 10\n```\n\n## \u6a21\u578b\u6d4b\u8bd5\uff08Model Testing\uff09\n\n### \u6a21\u578b\u6d4b\u8bd5\u7528\u4f8b\n\n```python\n# tests/test_models.py\nimport pytest\nfrom django.core.exceptions import ValidationError\nfrom tests.factories import UserFactory, ProductFactory\n\nclass TestUserModel:\n \"\"\"\u6d4b\u8bd5 User \u6a21\u578b\u3002\"\"\"\n\n def test_create_user(self, db):\n \"\"\"\u6d4b\u8bd5\u521b\u5efa\u666e\u901a\u7528\u6237\u3002\"\"\"\n user = UserFactory(email='test@example.com')\n assert user.email == 'test@example.com'\n assert user.check_password('testpass123')\n assert not user.is_staff\n assert not user.is_superuser\n\n def test_create_superuser(self, db):\n \"\"\"\u6d4b\u8bd5\u521b\u5efa\u8d85\u7ea7\u7528\u6237\u3002\"\"\"\n user = UserFactory(\n email='admin@example.com',\n is_staff=True,\n is_superuser=True\n )\n assert user.is_staff\n assert user.is_superuser\n\n def test_user_str(self, db):\n \"\"\"\u6d4b\u8bd5\u7528\u6237\u5b57\u7b26\u4e32\u8868\u793a\u5f62\u5f0f\u3002\"\"\"\n user = UserFactory(email='test@example.com')\n assert str(user) == 'test@example.com'\n\nclass TestProductModel:\n \"\"\"\u6d4b\u8bd5 Product \u6a21\u578b\u3002\"\"\"\n\n def test_product_creation(self, db):\n \"\"\"\u6d4b\u8bd5\u521b\u5efa\u4ea7\u54c1\u3002\"\"\"\n product = ProductFactory()\n assert product.id is not None\n assert product.is_active is True\n assert product.created_at is not None\n\n def test_product_slug_generation(self, db):\n \"\"\"\u6d4b\u8bd5 Slug \u81ea\u52a8\u751f\u6210\u3002\"\"\"\n product = ProductFactory(name='Test Product')\n assert product.slug == 'test-product'\n\n def test_product_price_validation(self, db):\n \"\"\"\u6d4b\u8bd5\u4ef7\u683c\u4e0d\u80fd\u4e3a\u8d1f\u6570\u3002\"\"\"\n product = ProductFactory(price=-10)\n with pytest.raises(ValidationError):\n product.full_clean()\n\n def test_product_manager_active(self, db):\n \"\"\"\u6d4b\u8bd5 active \u7ba1\u7406\u5668\u65b9\u6cd5\u3002\"\"\"\n ProductFactory.create_batch(5, is_active=True)\n ProductFactory.create_batch(3, is_active=False)\n\n active_count = Product.objects.active().count()\n assert active_count == 5\n\n def test_product_stock_management(self, db):\n \"\"\"\u6d4b\u8bd5\u5e93\u5b58\u7ba1\u7406\u3002\"\"\"\n product = ProductFactory(stock=10)\n product.reduce_stock(5)\n product.refresh_from_db()\n assert product.stock == 5\n\n with pytest.raises(ValueError):\n product.reduce_stock(10) # \u5e93\u5b58\u4e0d\u8db3\n```\n\n## \u89c6\u56fe\u6d4b\u8bd5\uff08View Testing\uff09\n\n### Django \u89c6\u56fe\u6d4b\u8bd5\n\n```python\n# tests/test_views.py\nimport pytest\nfrom django.urls import reverse\nfrom tests.factories import ProductFactory, UserFactory\n\nclass TestProductViews:\n \"\"\"\u6d4b\u8bd5\u4ea7\u54c1\u89c6\u56fe\u3002\"\"\"\n\n def test_product_list(self, client, db):\n \"\"\"\u6d4b\u8bd5\u4ea7\u54c1\u5217\u8868\u89c6\u56fe\u3002\"\"\"\n ProductFactory.create_batch(10)\n\n response = client.get(reverse('products:list'))\n\n assert response.status_code == 200\n assert len(response.context['products']) == 10\n\n def test_product_detail(self, client, db):\n \"\"\"\u6d4b\u8bd5\u4ea7\u54c1\u8be6\u60c5\u89c6\u56fe\u3002\"\"\"\n product = ProductFactory()\n\n response = client.get(reverse('products:detail', kwargs={'slug': product.slug}))\n\n assert response.status_code == 200\n assert response.context['product'] == product\n\n def test_product_create_requires_login(self, client, db):\n \"\"\"\u6d4b\u8bd5\u521b\u5efa\u4ea7\u54c1\u9700\u8981\u767b\u5f55\u8ba4\u8bc1\u3002\"\"\"\n response = client.get(reverse('products:create'))\n\n assert response.status_code == 302\n assert response.url.startswith('/accounts/login/')\n\n def test_product_create_authenticated(self, authenticated_client, db):\n \"\"\"\u6d4b\u8bd5\u4ee5\u5df2\u8ba4\u8bc1\u7528\u6237\u8eab\u4efd\u521b\u5efa\u4ea7\u54c1\u3002\"\"\"\n response = authenticated_client.get(reverse('products:create'))\n\n assert response.status_code == 200\n\n def test_product_create_post(self, authenticated_client, db, category):\n \"\"\"\u6d4b\u8bd5\u901a\u8fc7 POST \u8bf7\u6c42\u521b\u5efa\u4ea7\u54c1\u3002\"\"\"\n data = {\n 'name': 'Test Product',\n 'description': 'A test product',\n 'price': '99.99',\n 'stock': 10,\n 'category': category.id,\n }\n\n response = authenticated_client.post(reverse('products:create'), data)\n\n assert response.status_code == 302\n assert Product.objects.filter(name='Test Product').exists()\n```\n\n## DRF API \u6d4b\u8bd5\n\n### \u5e8f\u5217\u5316\u5668\u6d4b\u8bd5\n\n```python\n# tests/test_serializers.py\nimport pytest\nfrom rest_framework.exceptions import ValidationError\nfrom apps.products.serializers import ProductSerializer\nfrom tests.factories import ProductFactory\n\nclass TestProductSerializer:\n \"\"\"\u6d4b\u8bd5 ProductSerializer\u3002\"\"\"\n\n def test_serialize_product(self, db):\n \"\"\"\u6d4b\u8bd5\u5e8f\u5217\u5316\u4ea7\u54c1\u5bf9\u8c61\u3002\"\"\"\n product = ProductFactory()\n serializer = ProductSerializer(product)\n\n data = serializer.data\n\n assert data['id'] == product.id\n assert data['name'] == product.name\n assert data['price'] == str(product.price)\n\n def test_deserialize_product(self, db):\n \"\"\"\u6d4b\u8bd5\u53cd\u5e8f\u5217\u5316\u4ea7\u54c1\u6570\u636e\u3002\"\"\"\n data = {\n 'name': 'Test Product',\n 'description': 'Test description',\n 'price': '99.99',\n 'stock': 10,\n 'category': 1,\n }\n\n serializer = ProductSerializer(data=data)\n\n assert serializer.is_valid()\n product = serializer.save()\n\n assert product.name == 'Test Product'\n assert float(product.price) == 99.99\n\n def test_price_validation(self, db):\n \"\"\"\u6d4b\u8bd5\u4ef7\u683c\u9a8c\u8bc1\u3002\"\"\"\n data = {\n 'name': 'Test Product',\n 'price': '-10.00',\n 'stock': 10,\n }\n\n serializer = ProductSerializer(data=data)\n\n assert not serializer.is_valid()\n assert 'price' in serializer.errors\n\n def test_stock_validation(self, db):\n \"\"\"\u6d4b\u8bd5\u5e93\u5b58\u4e0d\u80fd\u4e3a\u8d1f\u6570\u3002\"\"\"\n data = {\n 'name': 'Test Product',\n 'price': '99.99',\n 'stock': -5,\n }\n\n serializer = ProductSerializer(data=data)\n\n assert not serializer.is_valid()\n assert 'stock' in serializer.errors\n```\n\n### API ViewSet \u6d4b\u8bd5\n\n```python\n# tests/test_api.py\nimport pytest\nfrom rest_framework.test import APIClient\nfrom rest_framework import status\nfrom django.urls import reverse\nfrom tests.factories import ProductFactory, UserFactory\n\nclass TestProductAPI:\n \"\"\"\u6d4b\u8bd5\u4ea7\u54c1 API \u63a5\u53e3\u3002\"\"\"\n\n @pytest.fixture\n def api_client(self):\n \"\"\"\u8fd4\u56de API \u5ba2\u6237\u7aef\u3002\"\"\"\n return APIClient()\n\n def test_list_products(self, api_client, db):\n \"\"\"\u6d4b\u8bd5\u5217\u51fa\u4ea7\u54c1\u3002\"\"\"\n ProductFactory.create_batch(10)\n\n url = reverse('api:product-list')\n response = api_client.get(url)\n\n assert response.status_code == status.HTTP_200_OK\n assert response.data['count'] == 10\n\n def test_retrieve_product(self, api_client, db):\n \"\"\"\u6d4b\u8bd5\u83b7\u53d6\u5355\u4e2a\u4ea7\u54c1\u8be6\u60c5\u3002\"\"\"\n product = ProductFactory()\n\n url = reverse('api:product-detail', kwargs={'pk': product.id})\n response = api_client.get(url)\n\n assert response.status_code == status.HTTP_200_OK\n assert response.data['id'] == product.id\n\n def test_create_product_unauthorized(self, api_client, db):\n \"\"\"\u6d4b\u8bd5\u672a\u6388\u6743\u65f6\u521b\u5efa\u4ea7\u54c1\u3002\"\"\"\n url = reverse('api:product-list')\n data = {'name': 'Test Product', 'price': '99.99'}\n\n response = api_client.post(url, data)\n\n assert response.status_code == status.HTTP_401_UNAUTHORIZED\n\n def test_create_product_authorized(self, authenticated_api_client, db):\n \"\"\"\u6d4b\u8bd5\u4ee5\u5df2\u8ba4\u8bc1\u7528\u6237\u8eab\u4efd\u521b\u5efa\u4ea7\u54c1\u3002\"\"\"\n url = reverse('api:product-list')\n data = {\n 'name': 'Test Product',\n 'description': 'Test',\n 'price': '99.99',\n 'stock': 10,\n }\n\n response = authenticated_api_client.post(url, data)\n\n assert response.status_code == status.HTTP_201_CREATED\n assert response.data['name'] == 'Test Product'\n\n def test_update_product(self, authenticated_api_client, db):\n \"\"\"\u6d4b\u8bd5\u66f4\u65b0\u4ea7\u54c1\u3002\"\"\"\n product = ProductFactory(created_by=authenticated_api_client.user)\n\n url = reverse('api:product-detail', kwargs={'pk': product.id})\n data = {'name': 'Updated Product'}\n\n response = authenticated_api_client.patch(url, data)\n\n assert response.status_code == status.HTTP_200_OK\n assert response.data['name'] == 'Updated Product'\n\n def test_delete_product(self, authenticated_api_client, db):\n \"\"\"\u6d4b\u8bd5\u5220\u9664\u4ea7\u54c1\u3002\"\"\"\n product = ProductFactory(created_by=authenticated_api_client.user)\n\n url = reverse('api:product-detail', kwargs={'pk': product.id})\n response = authenticated_api_client.delete(url)\n\n assert response.status_code == status.HTTP_204_NO_CONTENT\n\n def test_filter_products_by_price(self, api_client, db):\n \"\"\"\u6d4b\u8bd5\u6309\u4ef7\u683c\u8fc7\u6ee4\u4ea7\u54c1\u3002\"\"\"\n ProductFactory(price=50)\n ProductFactory(price=150)\n\n url = reverse('api:product-list')\n response = api_client.get(url, {'price_min': 100})\n\n assert response.status_code == status.HTTP_200_OK\n assert response.data['count'] == 1\n\n def test_search_products(self, api_client, db):\n \"\"\"\u6d4b\u8bd5\u641c\u7d22\u4ea7\u54c1\u3002\"\"\"\n ProductFactory(name='Apple iPhone')\n ProductFactory(name='Samsung Galaxy')\n\n url = reverse('api:product-list')\n response = api_client.get(url, {'search': 'Apple'})\n\n assert response.status_code == status.HTTP_200_OK\n assert response.data['count'] == 1\n```\n\n## Mock \u6a21\u62df\u4e0e\u8865\u4e01\uff08Mocking and Patching\uff09\n\n### \u6a21\u62df\u5916\u90e8\u670d\u52a1\n\n```python\n# tests/test_views.py\nfrom unittest.mock import patch, Mock\nimport pytest\n\nclass TestPaymentView:\n \"\"\"\u4f7f\u7528\u6a21\u62df\u652f\u4ed8\u7f51\u5173\u6d4b\u8bd5\u652f\u4ed8\u89c6\u56fe\u3002\"\"\"\n\n @patch('apps.payments.services.stripe')\n def test_successful_payment(self, mock_stripe, client, user, product):\n \"\"\"\u4f7f\u7528\u6a21\u62df\u7684 Stripe \u6d4b\u8bd5\u6210\u529f\u652f\u4ed8\u3002\"\"\"\n # \u914d\u7f6e\u6a21\u62df\u5bf9\u8c61\n mock_stripe.Charge.create.return_value = {\n 'id': 'ch_123',\n 'status': 'succeeded',\n 'amount': 9999,\n }\n\n client.force_login(user)\n response = client.post(reverse('payments:process'), {\n 'product_id': product.id,\n 'token': 'tok_visa',\n })\n\n assert response.status_code == 302\n mock_stripe.Charge.create.assert_called_once()\n\n @patch('apps.payments.services.stripe')\n def test_failed_payment(self, mock_stripe, client, user, product):\n \"\"\"\u6d4b\u8bd5\u652f\u4ed8\u5931\u8d25\u3002\"\"\"\n mock_stripe.Charge.create.side_effect = Exception('Card declined')\n\n client.force_login(user)\n response = client.post(reverse('payments:process'), {\n 'product_id': product.id,\n 'token': 'tok_visa',\n })\n\n assert response.status_code == 302\n assert 'error' in response.url\n```\n\n### \u6a21\u62df\u90ae\u4ef6\u53d1\u9001\n\n```python\n# tests/test_email.py\nfrom django.core import mail\nfrom django.test import override_settings\n\n@override_settings(EMAIL_BACKEND='django.core.mail.backends.locmem.EmailBackend')\ndef test_order_confirmation_email(db, order):\n \"\"\"\u6d4b\u8bd5\u8ba2\u5355\u786e\u8ba4\u90ae\u4ef6\u3002\"\"\"\n order.send_confirmation_email()\n\n assert len(mail.outbox) == 1\n assert order.user.email in mail.outbox[0].to\n assert 'Order Confirmation' in mail.outbox[0].subject\n```\n\n## \u96c6\u6210\u6d4b\u8bd5\uff08Integration Testing\uff09\n\n### \u5168\u6d41\u7a0b\u6d4b\u8bd5\n\n```python\n# tests/test_integration.py\nimport pytest\nfrom django.urls import reverse\nfrom tests.factories import UserFactory, ProductFactory\n\nclass TestCheckoutFlow:\n \"\"\"\u6d4b\u8bd5\u5b8c\u6574\u7684\u7ed3\u8d26\u6d41\u7a0b\u3002\"\"\"\n\n def test_guest_to_purchase_flow(self, client, db):\n \"\"\"\u6d4b\u8bd5\u4ece\u6e38\u5ba2\u5230\u8d2d\u4e70\u5b8c\u6210\u7684\u5b8c\u6574\u6d41\u7a0b\u3002\"\"\"\n # \u6b65\u9aa4 1\uff1a\u6ce8\u518c\n response = client.post(reverse('users:register'), {\n 'email': 'test@example.com',\n 'password': 'testpass123',\n 'password_confirm': 'testpass123',\n })\n assert response.status_code == 302\n\n # \u6b65\u9aa4 2\uff1a\u767b\u5f55\n response = client.post(reverse('users:login'), {\n 'email': 'test@example.com',\n 'password': 'testpass123',\n })\n assert response.status_code == 302\n\n # \u6b65\u9aa4 3\uff1a\u6d4f\u89c8\u4ea7\u54c1\n product = ProductFactory(price=100)\n response = client.get(reverse('products:detail', kwargs={'slug': product.slug}))\n assert response.status_code == 200\n\n # \u6b65\u9aa4 4\uff1a\u6dfb\u52a0\u5230\u8d2d\u7269\u8f66\n response = client.post(reverse('cart:add'), {\n 'product_id': product.id,\n 'quantity': 1,\n })\n assert response.status_code == 302\n\n # \u6b65\u9aa4 5\uff1a\u7ed3\u8d26\n response = client.get(reverse('checkout:review'))\n assert response.status_code == 200\n assert product.name in response.content.decode()\n\n # \u6b65\u9aa4 6\uff1a\u5b8c\u6210\u8d2d\u4e70\n with patch('apps.checkout.services.process_payment') as mock_payment:\n mock_payment.return_value = True\n response = client.post(reverse('checkout:complete'))\n\n assert response.status_code == 302\n assert Order.objects.filter(user__email='test@example.com').exists()\n```\n\n## \u6d4b\u8bd5\u6700\u4f73\u5b9e\u8df5\n\n### \u63a8\u8350\u505a\u6cd5\uff08DO\uff09\n\n- **\u4f7f\u7528\u5de5\u5382\uff08Factories\uff09**\uff1a\u907f\u514d\u624b\u52a8\u521b\u5efa\u5bf9\u8c61\u3002\n- **\u6bcf\u4e2a\u6d4b\u8bd5\u4e00\u4e2a\u65ad\u8a00**\uff1a\u4fdd\u6301\u6d4b\u8bd5\u4e13\u6ce8\u3002\n- **\u63cf\u8ff0\u6027\u6d4b\u8bd5\u540d\u79f0**\uff1a\u5982 `test_user_cannot_delete_others_post`\u3002\n- **\u6d4b\u8bd5\u8fb9\u754c\u60c5\u51b5**\uff1a\u7a7a\u8f93\u5165\u3001None \u503c\u3001\u8fb9\u754c\u6761\u4ef6\u3002\n- **\u6a21\u62df\uff08Mock\uff09\u5916\u90e8\u670d\u52a1**\uff1a\u4e0d\u8981\u4f9d\u8d56\u5916\u90e8 API\u3002\n- **\u4f7f\u7528 Fixtures**\uff1a\u6d88\u9664\u91cd\u590d\u4ee3\u7801\u3002\n- **\u6d4b\u8bd5\u6743\u9650\u63a7\u5236**\uff1a\u786e\u4fdd\u6388\u6743\u903b\u8f91\u6b63\u5e38\u5de5\u4f5c\u3002\n- **\u4fdd\u6301\u6d4b\u8bd5\u8fd0\u884c\u8fc5\u901f**\uff1a\u4f7f\u7528 `--reuse-db` \u548c `--nomigrations`\u3002\n\n### \u907f\u514d\u505a\u6cd5\uff08DON'T\uff09\n\n- **\u4e0d\u8981\u6d4b\u8bd5 Django \u5185\u90e8\u673a\u5236**\uff1a\u76f8\u4fe1 Django \u81ea\u8eab\u5df2\u901a\u8fc7\u6d4b\u8bd5\u3002\n- **\u4e0d\u8981\u6d4b\u8bd5\u7b2c\u4e09\u65b9\u5e93\u4ee3\u7801**\uff1a\u76f8\u4fe1\u5e93\u4f5c\u8005\u7684\u6d4b\u8bd5\u3002\n- **\u4e0d\u8981\u5ffd\u7565\u5931\u8d25\u7684\u6d4b\u8bd5**\uff1a\u6240\u6709\u6d4b\u8bd5\u90fd\u5fc5\u987b\u901a\u8fc7\u3002\n- **\u4e0d\u8981\u8ba9\u6d4b\u8bd5\u4ea7\u751f\u4f9d\u8d56**\uff1a\u6d4b\u8bd5\u5e94\u8be5\u53ef\u4ee5\u4ee5\u4efb\u4f55\u987a\u5e8f\u8fd0\u884c\u3002\n- **\u4e0d\u8981\u8fc7\u5ea6\u6a21\u62df**\uff1a\u4ec5\u5bf9\u5916\u90e8\u4f9d\u8d56\u9879\u8fdb\u884c Mock\u3002\n- **\u4e0d\u8981\u6d4b\u8bd5\u79c1\u6709\u65b9\u6cd5**\uff1a\u6d4b\u8bd5\u516c\u5171\u63a5\u53e3\u3002\n- **\u4e0d\u8981\u4f7f\u7528\u751f\u4ea7\u6570\u636e\u5e93**\uff1a\u59cb\u7ec8\u4f7f\u7528\u4e13\u7528\u6d4b\u8bd5\u6570\u636e\u5e93\u3002\n\n## \u8986\u76d6\u7387\uff08Coverage\uff09\n\n### \u8986\u76d6\u7387\u914d\u7f6e\n\n```bash\n# \u8fd0\u884c\u5e26\u6709\u8986\u76d6\u7387\u7edf\u8ba1\u7684\u6d4b\u8bd5\npytest --cov=apps --cov-report=html --cov-report=term-missing\n\n# \u751f\u6210\u5e76\u67e5\u770b HTML \u62a5\u544a\nopen htmlcov/index.html\n```\n\n### \u8986\u76d6\u7387\u76ee\u6807\n\n| \u7ec4\u4ef6 | \u76ee\u6807\u8986\u76d6\u7387 |\n|-----------|-----------------|\n| \u6a21\u578b (Models) | 90%+ |\n| \u5e8f\u5217\u5316\u5668 (Serializers) | 85%+ |\n| \u89c6\u56fe (Views) | 80%+ |\n| \u670d\u52a1\u5c42 (Services) | 90%+ |\n| \u5de5\u5177\u7c7b (Utilities) | 80%+ |\n| \u603b\u4f53 (Overall) | 80%+ |\n\n## \u5feb\u901f\u53c2\u8003\n\n| \u6a21\u5f0f | \u7528\u6cd5 |\n|---------|-------|\n| `@pytest.mark.django_db` | \u542f\u7528\u6570\u636e\u5e93\u8bbf\u95ee |\n| `client` | Django \u6d4b\u8bd5\u5ba2\u6237\u7aef |\n| `api_client` | DRF API \u5ba2\u6237\u7aef |\n| `factory.create_batch(n)` | \u521b\u5efa\u591a\u4e2a\u5bf9\u8c61 |\n| `patch('module.function')` | \u6a21\u62df\u5916\u90e8\u4f9d\u8d56 |\n| `override_settings` | \u4e34\u65f6\u66f4\u6539\u8bbe\u7f6e |\n| `force_authenticate()` | \u5728\u6d4b\u8bd5\u4e2d\u7ed5\u8fc7\u8ba4\u8bc1 |\n| `assertRedirects` | \u68c0\u67e5\u91cd\u5b9a\u5411 |\n| `assertTemplateUsed` | \u9a8c\u8bc1\u6a21\u677f\u4f7f\u7528\u60c5\u51b5 |\n| `mail.outbox` | \u68c0\u67e5\u5df2\u53d1\u9001\u7684\u90ae\u4ef6 |\n\n\u8bf7\u8bb0\u4f4f\uff1a\u6d4b\u8bd5\u5373\u6587\u6863\u3002\u826f\u597d\u7684\u6d4b\u8bd5\u80fd\u591f\u89e3\u91ca\u4ee3\u7801\u7684\u9884\u671f\u5de5\u4f5c\u65b9\u5f0f\u3002\u4fdd\u6301\u6d4b\u8bd5\u7b80\u5355\u3001\u6613\u8bfb\u4e14\u6613\u4e8e\u7ef4\u62a4\u3002\n" + }, + "/Users/Library/Applications/xx/code/github/everything-claude-code-zh/skills/springboot-tdd/SKILL.md": { + "md5": "b99c9f82ac8f8da340998fed7b06e4a1", + "content": "---\nname: springboot-tdd\ndescription: \u4f7f\u7528 JUnit 5\u3001Mockito\u3001MockMvc\u3001Testcontainers \u548c JaCoCo \u8fdb\u884c Spring Boot \u7684\u6d4b\u8bd5\u9a71\u52a8\u5f00\u53d1\uff08TDD\uff09\u3002\u5728\u6dfb\u52a0\u529f\u80fd\u3001\u4fee\u590d Bug \u6216\u8fdb\u884c\u91cd\u6784\u65f6\u4f7f\u7528\u3002\n---\n\n# Spring Boot \u6d4b\u8bd5\u9a71\u52a8\u5f00\u53d1\uff08TDD\uff09\u5de5\u4f5c\u6d41\n\n\u9488\u5bf9 Spring Boot \u670d\u52a1\u7684 TDD \u6307\u5357\uff0c\u8981\u6c42 80% \u4ee5\u4e0a\u7684\u8986\u76d6\u7387\uff08\u5355\u5143\u6d4b\u8bd5 + \u96c6\u6210\u6d4b\u8bd5\uff09\u3002\n\n## \u9002\u7528\u573a\u666f\n\n- \u5f00\u53d1\u65b0\u529f\u80fd\u6216\u7aef\u70b9\uff08Endpoints\uff09\n- \u4fee\u590d Bug \u6216\u8fdb\u884c\u4ee3\u7801\u91cd\u6784\n- \u6dfb\u52a0\u6570\u636e\u8bbf\u95ee\u903b\u8f91\u6216\u5b89\u5168\u89c4\u5219\n\n## \u5de5\u4f5c\u6d41\n\n1) \u5148\u5199\u6d4b\u8bd5\uff08\u6d4b\u8bd5\u5e94\u5f53\u5931\u8d25\uff09\n2) \u5b9e\u73b0\u6700\u5c11\u91cf\u7684\u4ee3\u7801\u4ee5\u4f7f\u6d4b\u8bd5\u901a\u8fc7\n3) \u5728\u6d4b\u8bd5\u901a\u8fc7\uff08Green\uff09\u7684\u524d\u63d0\u4e0b\u8fdb\u884c\u91cd\u6784\n4) \u5f3a\u5236\u6267\u884c\u8986\u76d6\u7387\u68c0\u67e5\uff08JaCoCo\uff09\n\n## \u5355\u5143\u6d4b\u8bd5\uff08JUnit 5 + Mockito\uff09\n\n```java\n@ExtendWith(MockitoExtension.class)\nclass MarketServiceTest {\n @Mock MarketRepository repo;\n @InjectMocks MarketService service;\n\n @Test\n void createsMarket() {\n CreateMarketRequest req = new CreateMarketRequest(\"name\", \"desc\", Instant.now(), List.of(\"cat\"));\n when(repo.save(any())).thenAnswer(inv -> inv.getArgument(0));\n\n Market result = service.create(req);\n\n assertThat(result.name()).isEqualTo(\"name\");\n verify(repo).save(any());\n }\n}\n```\n\n\u6a21\u5f0f\uff1a\n- Arrange-Act-Assert\uff08\u51c6\u5907-\u6267\u884c-\u65ad\u8a00\uff09\n- \u907f\u514d\u90e8\u5206\u6253\u6869\uff08Partial Mocks\uff09\uff1b\u4f18\u5148\u4f7f\u7528\u663e\u5f0f\u6869\u51fd\u6570\uff08Stubbing\uff09\n- \u4f7f\u7528 `@ParameterizedTest` \u5904\u7406\u591a\u79cd\u53d8\u4f53\u573a\u666f\n\n## Web \u5c42\u6d4b\u8bd5\uff08MockMvc\uff09\n\n```java\n@WebMvcTest(MarketController.class)\nclass MarketControllerTest {\n @Autowired MockMvc mockMvc;\n @MockBean MarketService marketService;\n\n @Test\n void returnsMarkets() throws Exception {\n when(marketService.list(any())).thenReturn(Page.empty());\n\n mockMvc.perform(get(\"/api/markets\"))\n .andExpect(status().isOk())\n .andExpect(jsonPath(\"$.content\").isArray());\n }\n}\n```\n\n## \u96c6\u6210\u6d4b\u8bd5\uff08SpringBootTest\uff09\n\n```java\n@SpringBootTest\n@AutoConfigureMockMvc\n@ActiveProfiles(\"test\")\nclass MarketIntegrationTest {\n @Autowired MockMvc mockMvc;\n\n @Test\n void createsMarket() throws Exception {\n mockMvc.perform(post(\"/api/markets\")\n .contentType(MediaType.APPLICATION_JSON)\n .content(\"\"\"\n {\"name\":\"Test\",\"description\":\"Desc\",\"endDate\":\"2030-01-01T00:00:00Z\",\"categories\":[\"general\"]}\n \"\"\"))\n .andExpect(status().isCreated());\n }\n}\n```\n\n## \u6301\u4e45\u5c42\u6d4b\u8bd5\uff08DataJpaTest\uff09\n\n```java\n@DataJpaTest\n@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)\n@Import(TestContainersConfig.class)\nclass MarketRepositoryTest {\n @Autowired MarketRepository repo;\n\n @Test\n void savesAndFinds() {\n MarketEntity entity = new MarketEntity();\n entity.setName(\"Test\");\n repo.save(entity);\n\n Optional found = repo.findByName(\"Test\");\n assertThat(found).isPresent();\n }\n}\n```\n\n## Testcontainers\n\n- \u4f7f\u7528\u53ef\u91cd\u7528\u7684\u5bb9\u5668\uff08\u5982 Postgres/Redis\uff09\u6765\u6a21\u62df\u751f\u4ea7\u73af\u5883\n- \u901a\u8fc7 `@DynamicPropertySource` \u8fdb\u884c\u8fde\u63a5\uff0c\u5c06 JDBC URL \u6ce8\u5165\u5230 Spring \u4e0a\u4e0b\u6587\u4e2d\n\n## \u8986\u76d6\u7387\uff08JaCoCo\uff09\n\nMaven \u914d\u7f6e\u7247\u6bb5\uff1a\n```xml\n\n org.jacoco\n jacoco-maven-plugin\n 0.8.14\n \n \n prepare-agent\n \n \n report\n verify\n report\n \n \n\n```\n\n## \u65ad\u8a00\uff08Assertions\uff09\n\n- \u4e3a\u4e86\u63d0\u9ad8\u53ef\u8bfb\u6027\uff0c\u4f18\u5148\u9009\u62e9 AssertJ (`assertThat`)\n- \u5bf9\u4e8e JSON \u54cd\u5e94\uff0c\u4f7f\u7528 `jsonPath`\n- \u5bf9\u4e8e\u5f02\u5e38\u6d4b\u8bd5\uff1a`assertThatThrownBy(...)`\n\n## \u6d4b\u8bd5\u6570\u636e\u6784\u5efa\u5668\uff08Test Data Builders\uff09\n\n```java\nclass MarketBuilder {\n private String name = \"Test\";\n MarketBuilder withName(String name) { this.name = name; return this; }\n Market build() { return new Market(null, name, MarketStatus.ACTIVE); }\n}\n```\n\n## CI \u547d\u4ee4\n\n- Maven\uff1a`mvn -T 4 test` \u6216 `mvn verify`\n- Gradle\uff1a`./gradlew test jacocoTestReport`\n\n**\u8bb0\u4f4f**\uff1a\u4fdd\u6301\u6d4b\u8bd5\u5feb\u901f\u3001\u9694\u79bb\u4e14\u5177\u6709\u786e\u5b9a\u6027\u3002\u6d4b\u8bd5\u7684\u662f\u884c\u4e3a\uff0c\u800c\u975e\u5b9e\u73b0\u7ec6\u8282\u3002\n" + }, + "/Users/Library/Applications/xx/code/github/everything-claude-code-zh/skills/python-patterns/SKILL.md": { + "md5": "7bb68dce2d4ae7a20d7720761d382bae", + "content": "---\nname: python-patterns\ndescription: Pythonic idioms, PEP 8 standards, type hints, and best practices for building robust, efficient, and maintainable Python applications.\n---\n\n# Python \u5f00\u53d1\u6a21\u5f0f (Python Development Patterns)\n\n\u6784\u5efa\u5065\u58ee\u3001\u9ad8\u6548\u4e14\u53ef\u7ef4\u62a4\u7684\u5e94\u7528\u7a0b\u5e8f\u7684 Pythonic \u60ef\u7528\u6a21\u5f0f\u548c\u6700\u4f73\u5b9e\u8df5\u3002\n\n## \u6fc0\u6d3b\u65f6\u673a (When to Activate)\n\n- \u7f16\u5199\u65b0\u7684 Python \u4ee3\u7801\u65f6\n- \u8fdb\u884c Python \u4ee3\u7801\u5ba1\u67e5\uff08Review\uff09\u65f6\n- \u91cd\u6784\u73b0\u6709\u7684 Python \u4ee3\u7801\u65f6\n- \u8bbe\u8ba1 Python \u5305\uff08Package\uff09\u6216\u6a21\u5757\uff08Module\uff09\u65f6\n\n## \u6838\u5fc3\u539f\u5219\n\n### 1. \u53ef\u8bfb\u6027\u81f3\u4e0a (Readability Counts)\n\nPython \u4f18\u5148\u8003\u8651\u53ef\u8bfb\u6027\u3002\u4ee3\u7801\u5e94\u5f53\u76f4\u89c2\u4e14\u6613\u4e8e\u7406\u89e3\u3002\n\n```python\n# Good: \u6e05\u6670\u4e14\u6613\u8bfb\ndef get_active_users(users: list[User]) -> list[User]:\n \"\"\"\u4ec5\u4ece\u63d0\u4f9b\u7684\u5217\u8868\u4e2d\u8fd4\u56de\u6d3b\u8dc3\u7528\u6237\u3002\"\"\"\n return [user for user in users if user.is_active]\n\n\n# Bad: \u5de7\u5999\u4f46\u4ee4\u4eba\u56f0\u60d1\ndef get_active_users(u):\n return [x for x in u if x.a]\n```\n\n### 2. \u663e\u5f0f\u4f18\u4e8e\u9690\u5f0f (Explicit is Better Than Implicit)\n\n\u907f\u514d\u201c\u9b54\u6cd5\u201d\u884c\u4e3a\uff1b\u6e05\u6670\u5730\u8868\u8fbe\u4ee3\u7801\u7684\u529f\u80fd\u3002\n\n```python\n# Good: \u663e\u5f0f\u914d\u7f6e\nimport logging\n\nlogging.basicConfig(\n level=logging.INFO,\n format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'\n)\n\n# Bad: \u9690\u85cf\u7684\u526f\u4f5c\u7528\nimport some_module\nsome_module.setup() # \u8fd9\u5230\u5e95\u505a\u4e86\u4ec0\u4e48\uff1f\n```\n\n### 3. EAFP \u6a21\u5f0f - \u8bf7\u6c42\u5bbd\u6055\u6bd4\u8bf7\u6c42\u8bb8\u53ef\u66f4\u5bb9\u6613 (Easier to Ask Forgiveness Than Permission)\n\nPython \u503e\u5411\u4e8e\u4f7f\u7528\u5f02\u5e38\u5904\u7406\u800c\u975e\u9884\u5148\u68c0\u67e5\u6761\u4ef6\u3002\n\n```python\n# Good: EAFP \u98ce\u683c\ndef get_value(dictionary: dict, key: str) -> Any:\n try:\n return dictionary[key]\n except KeyError:\n return default_value\n\n# Bad: LBYL (Look Before You Leap\uff0c\u4e09\u601d\u800c\u540e\u884c) \u98ce\u683c\ndef get_value(dictionary: dict, key: str) -> Any:\n if key in dictionary:\n return dictionary[key]\n else:\n return default_value\n```\n\n## \u7c7b\u578b\u63d0\u793a (Type Hints)\n\n### \u57fa\u7840\u7c7b\u578b\u6ce8\u89e3\n\n```python\nfrom typing import Optional, List, Dict, Any\n\ndef process_user(\n user_id: str,\n data: Dict[str, Any],\n active: bool = True\n) -> Optional[User]:\n \"\"\"\u5904\u7406\u7528\u6237\u5e76\u8fd4\u56de\u66f4\u65b0\u540e\u7684 User \u6216 None\u3002\"\"\"\n if not active:\n return None\n return User(user_id, data)\n```\n\n### \u73b0\u4ee3\u7c7b\u578b\u63d0\u793a (Python 3.9+)\n\n```python\n# Python 3.9+ - \u4f7f\u7528\u5185\u7f6e\u7c7b\u578b\ndef process_items(items: list[str]) -> dict[str, int]:\n return {item: len(item) for item in items}\n\n# Python 3.8 \u53ca\u66f4\u65e9\u7248\u672c - \u4f7f\u7528 typing \u6a21\u5757\nfrom typing import List, Dict\n\ndef process_items(items: List[str]) -> Dict[str, int]:\n return {item: len(item) for item in items}\n```\n\n### \u7c7b\u578b\u522b\u540d (Type Aliases) \u548c TypeVar\n\n```python\nfrom typing import TypeVar, Union\n\n# \u590d\u6742\u7c7b\u578b\u7684\u7c7b\u578b\u522b\u540d\nJSON = Union[dict[str, Any], list[Any], str, int, float, bool, None]\n\ndef parse_json(data: str) -> JSON:\n return json.loads(data)\n\n# \u6cdb\u578b\nT = TypeVar('T')\n\ndef first(items: list[T]) -> T | None: \n \"\"\"\u8fd4\u56de\u7b2c\u4e00\u9879\uff0c\u5982\u679c\u5217\u8868\u4e3a\u7a7a\u5219\u8fd4\u56de None\u3002\"\"\"\n return items[0] if items else None\n```\n\n### \u57fa\u4e8e\u534f\u8bae (Protocol) \u7684\u9e2d\u5b50\u7c7b\u578b (Duck Typing)\n\n```python\nfrom typing import Protocol\n\nclass Renderable(Protocol):\n def render(self) -> str:\n \"\"\"\u5c06\u5bf9\u8c61\u6e32\u67d3\u4e3a\u5b57\u7b26\u4e32\u3002\"\"\"\n\ndef render_all(items: list[Renderable]) -> str:\n \"\"\"\u6e32\u67d3\u6240\u6709\u5b9e\u73b0\u4e86 Renderable \u534f\u8bae\u7684\u9879\u76ee\u3002\"\"\"\n return \"\\n\".join(item.render() for item in items)\n```\n\n## \u5f02\u5e38\u5904\u7406\u6a21\u5f0f (Error Handling Patterns)\n\n### \u7279\u5b9a\u5f02\u5e38\u5904\u7406\n\n```python\n# Good: \u6355\u83b7\u7279\u5b9a\u7684\u5f02\u5e38\ndef load_config(path: str) -> Config:\n try:\n with open(path) as f:\n return Config.from_json(f.read())\n except FileNotFoundError as e:\n raise ConfigError(f\"Config file not found: {path}\") from e\n except json.JSONDecodeError as e:\n raise ConfigError(f\"Invalid JSON in config: {path}\") from e\n\n# Bad: \u7a7a\u5f02\u5e38\u6355\u83b7\ndef load_config(path: str) -> Config:\n try:\n with open(path) as f:\n return Config.from_json(f.read())\n except:\n return None # \u9759\u9ed8\u5931\u8d25\uff01\n```\n\n### \u5f02\u5e38\u94fe (Exception Chaining)\n\n```python\ndef process_data(data: str) -> Result:\n try:\n parsed = json.loads(data)\n except json.JSONDecodeError as e:\n # \u4f7f\u7528\u5f02\u5e38\u94fe\u4ee5\u4fdd\u7559\u5806\u6808\u8ddf\u8e2a (traceback)\n raise ValueError(f\"Failed to parse data: {data}\") from e\n```\n\n### \u81ea\u5b9a\u4e49\u5f02\u5e38\u5c42\u6b21\u7ed3\u6784\n\n```python\nclass AppError(Exception):\n \"\"\"\u6240\u6709\u5e94\u7528\u7a0b\u5e8f\u9519\u8bef\u7684\u57fa\u7c7b\u3002\"\"\"\n pass\n\nclass ValidationError(AppError):\n \"\"\"\u5f53\u8f93\u5165\u9a8c\u8bc1\u5931\u8d25\u65f6\u5f15\u53d1\u3002\"\"\"\n pass\n\nclass NotFoundError(AppError):\n \"\"\"\u5f53\u8bf7\u6c42\u7684\u8d44\u6e90\u672a\u627e\u5230\u65f6\u5f15\u53d1\u3002\"\"\"\n pass\n\n# \u4f7f\u7528\u793a\u4f8b\ndef get_user(user_id: str) -> User:\n user = db.find_user(user_id)\n if not user:\n raise NotFoundError(f\"User not found: {user_id}\")\n return user\n```\n\n## \u4e0a\u4e0b\u6587\u7ba1\u7406\u5668 (Context Managers)\n\n### \u8d44\u6e90\u7ba1\u7406\n\n```python\n# Good: \u4f7f\u7528\u4e0a\u4e0b\u6587\u7ba1\u7406\u5668\ndef process_file(path: str) -> str:\n with open(path, 'r') as f:\n return f.read()\n\n# Bad: \u624b\u52a8\u8d44\u6e90\u7ba1\u7406\ndef process_file(path: str) -> str:\n f = open(path, 'r')\n try:\n return f.read()\n finally:\n f.close()\n```\n\n### \u81ea\u5b9a\u4e49\u4e0a\u4e0b\u6587\u7ba1\u7406\u5668\n\n```python\nfrom contextlib import contextmanager\n\n@contextmanager\ndef timer(name: str):\n \"\"\"\u7528\u4e8e\u5bf9\u4ee3\u7801\u5757\u8ba1\u65f6\u7684\u4e0a\u4e0b\u6587\u7ba1\u7406\u5668\u3002\"\"\"\n start = time.perf_counter()\n yield\n elapsed = time.perf_counter() - start\n print(f\"{name} took {elapsed:.4f} seconds\")\n\n# \u4f7f\u7528\u793a\u4f8b\nwith timer(\"data processing\"):\n process_large_dataset()\n```\n\n### \u4e0a\u4e0b\u6587\u7ba1\u7406\u5668\u7c7b\n\n```python\nclass DatabaseTransaction:\n def __init__(self, connection):\n self.connection = connection\n\n def __enter__(self):\n self.connection.begin_transaction()\n return self\n\n def __exit__(self, exc_type, exc_val, exc_tb):\n if exc_type is None:\n self.connection.commit()\n else:\n self.connection.rollback()\n return False # \u4e0d\u8981\u6291\u5236\u5f02\u5e38\n\n# \u4f7f\u7528\u793a\u4f8b\nwith DatabaseTransaction(conn):\n user = conn.create_user(user_data)\n conn.create_profile(user.id, profile_data)\n```\n\n## \u63a8\u5bfc\u5f0f (Comprehensions) \u4e0e\u751f\u6210\u5668 (Generators)\n\n### \u5217\u8868\u63a8\u5bfc\u5f0f (List Comprehensions)\n\n```python\n# Good: \u7528\u4e8e\u7b80\u5355\u8f6c\u6362\u7684\u5217\u8868\u63a8\u5bfc\u5f0f\nnames = [user.name for user in users if user.is_active]\n\n# Bad: \u624b\u52a8\u5faa\u73af\nnames = []\nfor user in users:\n if user.is_active:\n names.append(user.name)\n\n# \u590d\u6742\u7684\u63a8\u5bfc\u5f0f\u5e94\u5f53\u5c55\u5f00\n# Bad: \u592a\u8fc7\u590d\u6742\nresult = [x * 2 for x in items if x > 0 if x % 2 == 0]\n\n# Good: \u4f7f\u7528\u751f\u6210\u5668\u51fd\u6570\ndef filter_and_transform(items: Iterable[int]) -> list[int]:\n result = []\n for x in items:\n if x > 0 and x % 2 == 0:\n result.append(x * 2)\n return result\n```\n\n### \u751f\u6210\u5668\u8868\u8fbe\u5f0f (Generator Expressions)\n\n```python\n# Good: \u7528\u4e8e\u5ef6\u8fdf\u6c42\u503c\u7684\u751f\u6210\u5668\ntotal = sum(x * x for x in range(1_000_000))\n\n# Bad: \u521b\u5efa\u4e86\u5de8\u5927\u7684\u4e2d\u95f4\u5217\u8868\ntotal = sum([x * x for x in range(1_000_000)])\n```\n\n### \u751f\u6210\u5668\u51fd\u6570 (Generator Functions)\n\n```python\ndef read_large_file(path: str) -> Iterator[str]:\n \"\"\"\u9010\u884c\u8bfb\u53d6\u5927\u6587\u4ef6\u3002\"\"\"\n with open(path) as f:\n for line in f:\n yield line.strip()\n\n# \u4f7f\u7528\u793a\u4f8b\nfor line in read_large_file(\"huge.txt\"):\n process(line)\n```\n\n## \u6570\u636e\u7c7b (Data Classes) \u4e0e\u547d\u540d\u5143\u7ec4 (Named Tuples)\n\n### \u6570\u636e\u7c7b (Data Classes)\n\n```python\nfrom dataclasses import dataclass, field\nfrom datetime import datetime\n\n@dataclass\nclass User:\n \"\"\"\u5177\u6709\u81ea\u52a8\u751f\u6210 __init__\u3001__repr__ \u548c __eq__ \u7684\u7528\u6237\u5b9e\u4f53\u3002\"\"\"\n id: str\n name: str\n email: str\n created_at: datetime = field(default_factory=datetime.now)\n is_active: bool = True\n\n# \u4f7f\u7528\u793a\u4f8b\nuser = User(\n id=\"123\",\n name=\"Alice\",\n email=\"alice@example.com\"\n)\n```\n\n### \u5e26\u9a8c\u8bc1\u7684\u6570\u636e\u7c7b\n\n```python\n@dataclass\nclass User:\n email: str\n age: int\n\n def __post_init__(self):\n # \u9a8c\u8bc1\u7535\u5b50\u90ae\u4ef6\u683c\u5f0f\n if \"@\" not in self.email:\n raise ValueError(f\"Invalid email: {self.email}\")\n # \u9a8c\u8bc1\u5e74\u9f84\u8303\u56f4\n if self.age < 0 or self.age > 150:\n raise ValueError(f\"Invalid age: {self.age}\")\n```\n\n### \u547d\u540d\u5143\u7ec4 (Named Tuples)\n\n```python\nfrom typing import NamedTuple\n\nclass Point(NamedTuple):\n \"\"\"\u4e0d\u53ef\u53d8\u7684\u4e8c\u7ef4\u70b9\u3002\"\"\"\n x: float\n y: float\n\n def distance(self, other: 'Point') -> float:\n return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5\n\n# \u4f7f\u7528\u793a\u4f8b\np1 = Point(0, 0)\np2 = Point(3, 4)\nprint(p1.distance(p2)) # 5.0\n```\n\n## \u88c5\u9970\u5668 (Decorators)\n\n### \u51fd\u6570\u88c5\u9970\u5668\n\n```python\nimport functools\nimport time\n\ndef timer(func: Callable) -> Callable:\n \"\"\"\u7528\u4e8e\u5bf9\u51fd\u6570\u6267\u884c\u8fdb\u884c\u8ba1\u65f6\u7684\u88c5\u9970\u5668\u3002\"\"\"\n @functools.wraps(func)\n def wrapper(*args, **kwargs):\n start = time.perf_counter()\n result = func(*args, **kwargs)\n elapsed = time.perf_counter() - start\n print(f\"{func.__name__} took {elapsed:.4f}s\")\n return result\n return wrapper\n\n@timer\ndef slow_function():\n time.sleep(1)\n\n# slow_function() \u8f93\u51fa: slow_function took 1.0012s\n```\n\n### \u53c2\u6570\u5316\u88c5\u9970\u5668\n\n```python\ndef repeat(times: int):\n \"\"\"\u7528\u4e8e\u591a\u6b21\u91cd\u590d\u6267\u884c\u51fd\u6570\u7684\u88c5\u9970\u5668\u3002\"\"\"\n def decorator(func: Callable) -> Callable:\n @functools.wraps(func)\n def wrapper(*args, **kwargs):\n results = []\n for _ in range(times):\n results.append(func(*args, **kwargs))\n return results\n return wrapper\n return decorator\n\n@repeat(times=3)\ndef greet(name: str) -> str:\n return f\"Hello, {name}!\"\n\n# greet(\"Alice\") \u8fd4\u56de [\"Hello, Alice!\", \"Hello, Alice!\", \"Hello, Alice!\"]\n```\n\n### \u57fa\u4e8e\u7c7b\u7684\u88c5\u9970\u5668\n\n```python\nclass CountCalls:\n \"\"\"\u7edf\u8ba1\u51fd\u6570\u88ab\u8c03\u7528\u6b21\u6570\u7684\u88c5\u9970\u5668\u3002\"\"\"\n def __init__(self, func: Callable):\n functools.update_wrapper(self, func)\n self.func = func\n self.count = 0\n\n def __call__(self, *args, **kwargs):\n self.count += 1\n print(f\"{self.func.__name__} has been called {self.count} times\")\n return self.func(*args, **kwargs)\n\n@CountCalls\ndef process():\n pass\n\n# \u6bcf\u6b21\u8c03\u7528 process() \u90fd\u4f1a\u6253\u5370\u8c03\u7528\u8ba1\u6570\n```\n\n## \u5e76\u53d1\u6a21\u5f0f (Concurrency Patterns)\n\n### \u7ebf\u7a0b (Threading) \u5904\u7406 I/O \u5bc6\u96c6\u578b\u4efb\u52a1\n\n```python\nimport concurrent.futures\nimport threading\n\ndef fetch_url(url: str) -> str:\n \"\"\"\u83b7\u53d6 URL (I/O \u5bc6\u96c6\u578b\u64cd\u4f5c)\u3002\"\"\"\n import urllib.request\n with urllib.request.urlopen(url) as response:\n return response.read().decode()\n\ndef fetch_all_urls(urls: list[str]) -> dict[str, str]:\n \"\"\"\u4f7f\u7528\u7ebf\u7a0b\u5e76\u53d1\u5730\u83b7\u53d6\u591a\u4e2a URL\u3002\"\"\"\n with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:\n future_to_url = {executor.submit(fetch_url, url): url for url in urls}\n results = {}\n for future in concurrent.futures.as_completed(future_to_url):\n url = future_to_url[future]\n try:\n results[url] = future.result()\n except Exception as e:\n results[url] = f\"Error: {e}\"\n return results\n```\n\n### \u591a\u8fdb\u7a0b (Multiprocessing) \u5904\u7406 CPU \u5bc6\u96c6\u578b\u4efb\u52a1\n\n```python\ndef process_data(data: list[int]) -> int:\n \"\"\"CPU \u5bc6\u96c6\u578b\u8ba1\u7b97\u3002\"\"\"\n return sum(x ** 2 for x in data)\n\ndef process_all(datasets: list[list[int]]) -> list[int]:\n \"\"\"\u4f7f\u7528\u591a\u4e2a\u8fdb\u7a0b\u5904\u7406\u591a\u4e2a\u6570\u636e\u96c6\u3002\"\"\"\n with concurrent.futures.ProcessPoolExecutor() as executor:\n results = list(executor.map(process_data, datasets))\n return results\n```\n\n### Async/Await \u5904\u7406\u5e76\u53d1 I/O\n\n```python\nimport asyncio\n\nasync def fetch_async(url: str) -> str:\n \"\"\"\u5f02\u6b65\u83b7\u53d6 URL\u3002\"\"\"\n import aiohttp\n async with aiohttp.ClientSession() as session:\n async with session.get(url) as response:\n return await response.text()\n\nasync def fetch_all(urls: list[str]) -> dict[str, str]:\n \"\"\"\u5e76\u53d1\u5730\u83b7\u53d6\u591a\u4e2a URL\u3002\"\"\"\n tasks = [fetch_async(url) for url in urls]\n results = await asyncio.gather(*tasks, return_exceptions=True)\n return dict(zip(urls, results))\n```\n\n## \u5305\u7ec4\u7ec7 (Package Organization)\n\n### \u6807\u51c6\u9879\u76ee\u5e03\u5c40\n\n```\nmyproject/\n\u251c\u2500\u2500 src/\n\u2502 \u2514\u2500\u2500 mypackage/\n\u2502 \u251c\u2500\u2500 __init__.py\n\u2502 \u251c\u2500\u2500 main.py\n\u2502 \u251c\u2500\u2500 api/\n\u2502 \u2502 \u251c\u2500\u2500 __init__.py\n\u2502 \u2502 \u2514\u2500\u2500 routes.py\n\u2502 \u251c\u2500\u2500 models/\n\u2502 \u2502 \u251c\u2500\u2500 __init__.py\n\u2502 \u2502 \u2514\u2500\u2500 user.py\n\u2502 \u2514\u2500\u2500 utils/\n\u2502 \u251c\u2500\u2500 __init__.py\n\u2502 \u2514\u2500\u2500 helpers.py\n\u251c\u2500\u2500 tests/\n\u2502 \u251c\u2500\u2500 __init__.py\n\u2502 \u251c\u2500\u2500 conftest.py\n\u2502 \u251c\u2500\u2500 test_api.py\n\u2502 \u2514\u2500\u2500 test_models.py\n\u251c\u2500\u2500 pyproject.toml\n\u251c\u2500\u2500 README.md\n\u2514\u2500\u2500 .gitignore\n```\n\n### \u5bfc\u5165\u89c4\u8303\n\n```python\n# Good: \u5bfc\u5165\u987a\u5e8f - \u6807\u51c6\u5e93\u3001\u7b2c\u4e09\u65b9\u5e93\u3001\u672c\u5730\u5e93\nimport os\nimport sys\nfrom pathlib import Path\n\nimport requests\nfrom fastapi import FastAPI\n\nfrom mypackage.models import User\nfrom mypackage.utils import format_name\n\n# Good: \u4f7f\u7528 isort \u81ea\u52a8\u8fdb\u884c\u5bfc\u5165\u6392\u5e8f\n# pip install isort\n```\n\n### \u7528\u4e8e\u5305\u5bfc\u51fa\u7684 __init__.py\n\n```python\n# mypackage/__init__.py\n\"\"\"mypackage - \u4e00\u4e2a Python \u5305\u793a\u4f8b\u3002\"\"\"\n\n__version__ = \"1.0.0\"\n\n# \u5728\u5305\u7ea7\u522b\u5bfc\u51fa\u4e3b\u8981\u7684\u7c7b/\u51fd\u6570\nfrom mypackage.models import User, Post\nfrom mypackage.utils import format_name\n\n__all__ = [\"User\", \"Post\", \"format_name\"]\n```\n\n## \u5185\u5b58\u4e0e\u6027\u80fd\n\n### \u4f7f\u7528 __slots__ \u63d0\u9ad8\u5185\u5b58\u6548\u7387\n\n```python\n# Bad: \u5e38\u89c4\u7c7b\u4f7f\u7528 __dict__ (\u5360\u7528\u66f4\u591a\u5185\u5b58)\nclass Point:\n def __init__(self, x: float, y: float):\n self.x = x\n self.y = y\n\n# Good: __slots__ \u51cf\u5c11\u5185\u5b58\u4f7f\u7528\nclass Point:\n __slots__ = ['x', 'y']\n\n def __init__(self, x: float, y: float):\n self.x = x\n self.y = y\n```\n\n### \u7528\u4e8e\u5927\u6570\u636e\u7684\u751f\u6210\u5668\n\n```python\n# Bad: \u5728\u5185\u5b58\u4e2d\u8fd4\u56de\u5b8c\u6574\u5217\u8868\ndef read_lines(path: str) -> list[str]:\n with open(path) as f:\n return [line.strip() for line in f]\n\n# Good: \u4e00\u6b21\u4ea7\u51fa\u4e00\u884c\ndef read_lines(path: str) -> Iterator[str]:\n with open(path) as f:\n for line in f:\n yield line.strip()\n```\n\n### \u907f\u514d\u5728\u5faa\u73af\u4e2d\u8fdb\u884c\u5b57\u7b26\u4e32\u62fc\u63a5\n\n```python\n# Bad: \u7531\u4e8e\u5b57\u7b26\u4e32\u4e0d\u53ef\u53d8\u6027\uff0c\u590d\u6742\u5ea6\u4e3a O(n\u00b2)\nresult = \"\"\nfor item in items:\n result += str(item)\n\n# Good: \u4f7f\u7528 join\uff0c\u590d\u6742\u5ea6\u4e3a O(n)\nresult = \"\".join(str(item) for item in items)\n\n# Good: \u4f7f\u7528 StringIO \u8fdb\u884c\u6784\u5efa\nfrom io import StringIO\n\nbuffer = StringIO()\nfor item in items:\n buffer.write(str(item))\nresult = buffer.getvalue()\n```\n\n## Python \u5de5\u5177\u94fe\u96c6\u6210\n\n### \u5e38\u7528\u547d\u4ee4\n\n```bash\n# \u4ee3\u7801\u683c\u5f0f\u5316\nblack .\nisort .\n\n# \u9759\u6001\u68c0\u67e5 (Linting)\nruff check .\npylint mypackage/\n\n# \u7c7b\u578b\u68c0\u67e5\nmypy .\n\n# \u6d4b\u8bd5\npytest --cov=mypackage --cov-report=html\n\n# \u5b89\u5168\u626b\u63cf\nbandit -r .\n\n# \u4f9d\u8d56\u7ba1\u7406\npip-audit\nsafety check\n```\n\n### pyproject.toml \u914d\u7f6e\n\n```toml\n[project]\nname = \"mypackage\"\nversion = \"1.0.0\"\nrequires-python = \">=3.9\"\ndependencies = [\n \"requests>=2.31.0\",\n \"pydantic>=2.0.0\",\n]\n\n[project.optional-dependencies]\ndev = [\n \"pytest>=7.4.0\",\n \"pytest-cov>=4.1.0\",\n \"black>=23.0.0\",\n \"ruff>=0.1.0\",\n \"mypy>=1.5.0\",\n]\n\n[tool.black]\nline-length = 88\ntarget-version = ['py39']\n\n[tool.ruff]\nline-length = 88\nselect = [\"E\", \"F\", \"I\", \"N\", \"W\"]\n\n[tool.mypy]\npython_version = \"3.9\"\nwarn_return_any = true\nwarn_unused_configs = true\ndisallow_untyped_defs = true\n\n[tool.pytest.ini_options]\ntestpaths = [\"tests\"]\naddopts = \"--cov=mypackage --cov-report=term-missing\"\n```\n\n## \u5feb\u901f\u53c2\u8003\uff1aPython \u60ef\u7528\u6cd5 (Python Idioms)\n\n| \u60ef\u7528\u6cd5 | \u63cf\u8ff0 |\n|-------|-------------|\n| EAFP | \u8bf7\u6c42\u5bbd\u6055\u6bd4\u8bf7\u6c42\u8bb8\u53ef\u66f4\u5bb9\u6613 (Easier to Ask Forgiveness than Permission) |\n| \u4e0a\u4e0b\u6587\u7ba1\u7406\u5668 (Context managers) | \u4f7f\u7528 `with` \u8fdb\u884c\u8d44\u6e90\u7ba1\u7406 |\n| \u5217\u8868\u63a8\u5bfc\u5f0f (List comprehensions) | \u7528\u4e8e\u7b80\u5355\u8f6c\u6362 |\n| \u751f\u6210\u5668 (Generators) | \u7528\u4e8e\u5ef6\u8fdf\u6c42\u503c\u548c\u5927\u578b\u6570\u636e\u96c6 |\n| \u7c7b\u578b\u63d0\u793a (Type hints) | \u4e3a\u51fd\u6570\u7b7e\u540d\u6dfb\u52a0\u6ce8\u89e3 |\n| \u6570\u636e\u7c7b (Dataclasses) | \u7528\u4e8e\u5e26\u6709\u81ea\u52a8\u751f\u6210\u65b9\u6cd5\u7684\u5404\u79cd\u6570\u636e\u5bb9\u5668 |\n| `__slots__` | \u7528\u4e8e\u5185\u5b58\u4f18\u5316 |\n| f-strings | \u7528\u4e8e\u5b57\u7b26\u4e32\u683c\u5f0f\u5316 (Python 3.6+) |\n| `pathlib.Path` | \u7528\u4e8e\u8def\u5f84\u64cd\u4f5c (Python 3.4+) |\n| `enumerate` | \u5728\u5faa\u73af\u4e2d\u83b7\u53d6\u7d22\u5f15-\u5143\u7d20\u5bf9 |\n\n## \u5e94\u907f\u514d\u7684\u53cd\u6a21\u5f0f (Anti-Patterns)\n\n```python\n# Bad: \u53ef\u53d8\u9ed8\u8ba4\u53c2\u6570\ndef append_to(item, items=[]):\n items.append(item)\n return items\n\n# Good: \u4f7f\u7528 None \u5e76\u521b\u5efa\u65b0\u5217\u8868\ndef append_to(item, items=None):\n if items is None:\n items = []\n items.append(item)\n return items\n\n# Bad: \u4f7f\u7528 type() \u68c0\u67e5\u7c7b\u578b\nif type(obj) == list:\n process(obj)\n\n# Good: \u4f7f\u7528 isinstance\nif isinstance(obj, list):\n process(obj)\n\n# Bad: \u4f7f\u7528 == \u4e0e None \u6bd4\u8f83\nif value == None:\n process()\n\n# Good: \u4f7f\u7528 is\nif value is None:\n process()\n\n# Bad: from module import *\nfrom os.path import *\n\n# Good: \u663e\u5f0f\u5bfc\u5165\nfrom os.path import join, exists\n\n# Bad: \u7a7a\u5f02\u5e38\u6355\u83b7\ntry:\n risky_operation()\nexcept:\n pass\n\n# Good: \u7279\u5b9a\u5f02\u5e38\ntry:\n risky_operation()\nexcept SpecificError as e:\n logger.error(f\"Operation failed: {e}\")\n```\n\n__\u8bb0\u4f4f__\uff1aPython \u4ee3\u7801\u5e94\u5f53\u662f\u6613\u8bfb\u7684\u3001\u663e\u5f0f\u7684\uff0c\u5e76\u9075\u5faa\u6700\u5c0f\u60ca\u8bb6\u539f\u5219\u3002\u5982\u6709\u7591\u95ee\uff0c\u8bf7\u4f18\u5148\u8003\u8651\u6e05\u6670\u5ea6\u800c\u975e\u5de7\u5999\u6027\u3002\n\n```" } } \ No newline at end of file diff --git a/translation_workdir/scripts/batch_processor.py b/translation_workdir/scripts/batch_processor.py index 6e8eaa7..7570728 100644 --- a/translation_workdir/scripts/batch_processor.py +++ b/translation_workdir/scripts/batch_processor.py @@ -31,13 +31,15 @@ def main(): parser.add_argument("--model", default="gemini-3-flash-preview", help="模型名称") args = parser.parse_args() - # 1. 扫描项目中所有的 .md 文件 (排除工作目录本身) + # 1. 扫描项目中所有的 .md 文件 (排除工作目录本身及上游中文目录) all_md_files = [] for root, dirs, files in os.walk("."): - if any(x in root for x in [WORKDIR, "bak", "node_modules", ".git", "_zh"]): + # 排除系统目录、翻译工作区、输出目录以及上游的中文目录 (zh-TW/zh-CN) + if any(x in root for x in [WORKDIR, "bak", "node_modules", ".git", "_zh", "zh-TW", "zh-CN"]): continue for f in files: - if f.endswith(".md") and not f.endswith("_zh.md"): + # 排除已翻译文件(_zh.md)及上游中文文件(.zh-CN.md等) + if f.endswith(".md") and not f.endswith("_zh.md") and "zh-CN" not in f and "zh-TW" not in f: all_md_files.append(os.path.abspath(os.path.join(root, f))) if not os.path.exists(args.prompt): diff --git a/translation_workdir/scripts/sync_upstream.sh b/translation_workdir/scripts/sync_upstream.sh old mode 100644 new mode 100755