feat: update readme
This commit is contained in:
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
需要 Python 3、git,以及 Cursor / Claude Code / Codex 或其它兼容 Agent Skills 的 Agent。人类用法见根 [README.md](README.md) 和各 skill 的 `README.md`。用户要代装时按 README 的 [让 Agent 代装](README.md#让-agent-代装) 执行,不要只用 `pouch select`。
|
需要 Python 3、git,以及 Cursor / Claude Code / Codex 或其它兼容 Agent Skills 的 Agent。人类用法见根 [README.md](README.md) 和各 skill 的 `README.md`。用户要代装时:pouch skill 已安装则走其「安装 pouch」模式;否则读 [skills/pouch/references/install.md](skills/pouch/references/install.md) 并执行。不要只用 `pouch select`。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://git.yumee.top/laily/pouch.git ~/.pouch
|
git clone https://git.yumee.top/laily/pouch.git ~/.pouch
|
||||||
@@ -55,8 +55,10 @@ AGENTS.md # 本文档
|
|||||||
| [discussion-notes](skills/discussion-notes/README.md) | 边讨论边维护 Markdown 笔记 |
|
| [discussion-notes](skills/discussion-notes/README.md) | 边讨论边维护 Markdown 笔记 |
|
||||||
| [draw-prototype-flow](skills/draw-prototype-flow/README.md) | 把产品想法画成可编辑的 Draw.io 低保真原型和用户动线 |
|
| [draw-prototype-flow](skills/draw-prototype-flow/README.md) | 把产品想法画成可编辑的 Draw.io 低保真原型和用户动线 |
|
||||||
| [eli5](skills/eli5/README.md) | 用大图少字的单页 HTML 把话题讲给完全没背景的人听 |
|
| [eli5](skills/eli5/README.md) | 用大图少字的单页 HTML 把话题讲给完全没背景的人听 |
|
||||||
|
| [host-sec-check](skills/host-sec-check/README.md) | 对 Linux/VPS 做入侵取向的安全检查,默认只出报告 |
|
||||||
| [manage-release](skills/manage-release/README.md) | 给已准备好的 commit 发版本:更新说明、tag、可选 Forge Release |
|
| [manage-release](skills/manage-release/README.md) | 给已准备好的 commit 发版本:更新说明、tag、可选 Forge Release |
|
||||||
| [memory-distill](skills/memory-distill/README.md) | 任务结束后把可复用结论写入通用记忆和项目记忆 |
|
| [memory-distill](skills/memory-distill/README.md) | 任务结束后把可复用结论写入通用记忆和项目记忆 |
|
||||||
|
| [note-style](skills/note-style/README.md) | 把材料整理成可长期查阅的知识笔记 |
|
||||||
| [orc](skills/orc/README.md) | 把开发、发版、DEB / Docker 意图编排成阶段,按档位分发给对应 skill |
|
| [orc](skills/orc/README.md) | 把开发、发版、DEB / Docker 意图编排成阶段,按档位分发给对应 skill |
|
||||||
| [pouch](skills/pouch/README.md) | 创建、校验、安装和维护 builtin skill |
|
| [pouch](skills/pouch/README.md) | 创建、校验、安装和维护 builtin skill |
|
||||||
|
|
||||||
@@ -331,6 +333,8 @@ Claude Code 对 symlink 支持不稳定:可能无法发现 skill,或写入
|
|||||||
| 我要… | 命令 | 在哪 |
|
| 我要… | 命令 | 在哪 |
|
||||||
| ---------- | --------------------------------- | ---- |
|
| ---------- | --------------------------------- | ---- |
|
||||||
| 首次安装 | `git clone <repo> ~/.pouch && ~/.pouch/install.sh` | 任意 |
|
| 首次安装 | `git clone <repo> ~/.pouch && ~/.pouch/install.sh` | 任意 |
|
||||||
|
| 让 Agent 代装 | 说「帮我安装 pouch」或丢仓库 URL | 任意 |
|
||||||
|
| 介绍 / 接入 skill | 说「有哪些 skill」或「把这个项目该用的 skill 接上」 | 项目目录 |
|
||||||
| 新建 skill | `pouch create` → Agent 完善 → `check/finalize` | 任意项目 |
|
| 新建 skill | `pouch create` → Agent 完善 → `check/finalize` | 任意项目 |
|
||||||
| 全局启用 | `pouch add <name> -g` | 任意 |
|
| 全局启用 | `pouch add <name> -g` | 任意 |
|
||||||
| 项目启用 | `pouch add <name>` 或 `pouch enable <name>` | 项目目录 |
|
| 项目启用 | `pouch add <name>` 或 `pouch enable <name>` | 项目目录 |
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
自研 [Agent Skills](https://agentskills.io) 的单一事实来源(SSOT)。仓库、家目录 `~/.pouch` 和 CLI 都叫 **pouch**。Skill 内容与 CLI 在本仓库一并维护。
|
自研 [Agent Skills](https://agentskills.io) 的单一事实来源(SSOT)。仓库、家目录 `~/.pouch` 和 CLI 都叫 **pouch**。Skill 内容与 CLI 在本仓库一并维护。
|
||||||
|
|
||||||
人类用法看各 skill 的 `README.md`;`SKILL.md` 是给 Agent 的指令。用户要你代装时,按 [让 Agent 代装](#让-agent-代装) 自己执行,不要只把命令贴回去。
|
pouch 本义是小袋子,这里取做锦囊妙计的锦囊:skill 就是里面的妙计。
|
||||||
|
|
||||||
|
人类用法看各 skill 的 `README.md`;`SKILL.md` 是给 Agent 的指令。用户要你代装时,读 [skills/pouch/references/install.md](skills/pouch/references/install.md) 并执行,不要只把命令贴回去,也不要用 `pouch select`。
|
||||||
|
|
||||||
## 前置
|
## 前置
|
||||||
|
|
||||||
@@ -37,56 +39,12 @@ mv ~/.skills ~/.pouch
|
|||||||
|
|
||||||
## 让 Agent 代装
|
## 让 Agent 代装
|
||||||
|
|
||||||
用户说「帮我安装 pouch」「把 skill 装上」时按本节做。CLI 只认 `~/.pouch`。不要用 `pouch select`(必须交互式 TTY)。不要在未确认时覆盖已有的 `~/.pouch`。
|
对 Agent 说「帮我安装 pouch」,或把仓库地址丢给它。步骤只维护在
|
||||||
|
[skills/pouch/references/install.md](skills/pouch/references/install.md):
|
||||||
|
确认 python3/git → 处理 `~/.pouch` → `install.sh` → 用短清单介绍 skill → 问要装哪些。
|
||||||
|
不要用 `pouch select`。不要在未确认时覆盖已有的 `~/.pouch`。
|
||||||
|
|
||||||
### 1. 确认本机
|
自己装仍看上面的「快速开始」。
|
||||||
|
|
||||||
需要 `python3` 和 `git`。缺了先说明怎么装,不要继续。
|
|
||||||
|
|
||||||
### 2. 让 `~/.pouch` 指向本仓库
|
|
||||||
|
|
||||||
按现状只走一条:
|
|
||||||
|
|
||||||
| 现状 | 动作 |
|
|
||||||
| --- | --- |
|
|
||||||
| 当前目录就是这份仓库(有 `install.sh` 和 `skills/`),且没有 `~/.pouch` | `ln -sfn "$(pwd)" "$HOME/.pouch"` |
|
|
||||||
| `~/.pouch` 已在且含 `skills/`、`install.sh` | 沿用 |
|
|
||||||
| 只有 `~/.skills` | `mv ~/.skills ~/.pouch` |
|
|
||||||
| 都没有 | `git clone https://git.yumee.top/laily/pouch.git ~/.pouch`。失败则问用户本地路径或可用 remote |
|
|
||||||
| `~/.pouch` 在,但不是这份仓库 | 停下问用户,不要删、不要覆盖 |
|
|
||||||
|
|
||||||
### 3. 装 CLI 和 `pouch` skill
|
|
||||||
|
|
||||||
```bash
|
|
||||||
"$HOME/.pouch/install.sh"
|
|
||||||
export PATH="$HOME/.local/bin:$PATH"
|
|
||||||
command -v pouch
|
|
||||||
pouch status
|
|
||||||
```
|
|
||||||
|
|
||||||
`install.sh` 会改 rc,当前会话仍要 `export PATH`。不要让用户重开终端才继续。
|
|
||||||
|
|
||||||
### 4. 安装用户要的其它 skill
|
|
||||||
|
|
||||||
先 `pouch add --list`。用户没点名就问一次:装哪些、全局还是当前项目。能判断就不要追问。
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pouch add <name> -g -y # 全局,所有项目都能用
|
|
||||||
pouch add <name> -y # 仅当前项目
|
|
||||||
```
|
|
||||||
|
|
||||||
默认 `pouch add` 装到当前工作目录。cwd 是 pouch 仓库本身时用 `-g`,除非用户就在维护 pouch。用户是在自己的应用仓库里请你代装,才做项目级安装。
|
|
||||||
|
|
||||||
`install.sh` 已经全局装好 `pouch` skill。其它 skill 不要默认全装。
|
|
||||||
|
|
||||||
### 5. 验收并告诉用户下一步
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pouch status
|
|
||||||
pouch doctor
|
|
||||||
```
|
|
||||||
|
|
||||||
说明:打开 Cursor / Claude Code / Codex 后,按各 skill README 里的示例说话即可。软链异常则 `pouch doctor --fix`。已经装好时只汇报状态,问还要不要加 skill,不要重新 clone。
|
|
||||||
|
|
||||||
## 装完之后怎么用
|
## 装完之后怎么用
|
||||||
|
|
||||||
@@ -112,17 +70,20 @@ catalog.yaml # pouch 预置 Skill 来源目录
|
|||||||
AGENTS.md # 详细规范与架构说明
|
AGENTS.md # 详细规范与架构说明
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
| 路径 | 说明 |
|
| 路径 | 说明 |
|
||||||
|------|------|
|
| ---------------------------- | ----------------------------------------------------- |
|
||||||
| [skills/](skills/) | 自研 skill,每个子目录含给人看的 `README.md` 和给 Agent 的 `SKILL.md` |
|
| [skills/](skills/) | 自研 skill,每个子目录含给人看的 `README.md` 和给 Agent 的 `SKILL.md` |
|
||||||
| [pouch/](pouch/README.md) | 安装、软链、健康检查 CLI |
|
| [pouch/](pouch/README.md) | 安装、软链、健康检查 CLI |
|
||||||
| [catalog.yaml](catalog.yaml) | pouch 预置 Skill 来源目录 |
|
| [catalog.yaml](catalog.yaml) | pouch 预置 Skill 来源目录 |
|
||||||
| [AGENTS.md](AGENTS.md) | 设计原则、编写规范、架构详解 |
|
| [AGENTS.md](AGENTS.md) | 设计原则、编写规范、架构详解 |
|
||||||
|
|
||||||
|
|
||||||
## 自研 Skill
|
## 自研 Skill
|
||||||
|
|
||||||
|
|
||||||
| Skill | 说明 |
|
| Skill | 说明 |
|
||||||
| --- | --- |
|
| ----------------------------------------------------------------------- | ------------------------------------------- |
|
||||||
| [ack](skills/ack/README.md) | 显式初始化、检查并运行三角色闭环:需求 / bug、测试环境、发版与回归 |
|
| [ack](skills/ack/README.md) | 显式初始化、检查并运行三角色闭环:需求 / bug、测试环境、发版与回归 |
|
||||||
| [builder](skills/builder/README.md) | 按统一契约构建和发布 DEB / Docker 镜像 |
|
| [builder](skills/builder/README.md) | 按统一契约构建和发布 DEB / Docker 镜像 |
|
||||||
| [declarative-openspec-loop](skills/declarative-openspec-loop/README.md) | 你给校验方式,Agent 自动 propose / apply / 校验直到通过 |
|
| [declarative-openspec-loop](skills/declarative-openspec-loop/README.md) | 你给校验方式,Agent 自动 propose / apply / 校验直到通过 |
|
||||||
@@ -130,11 +91,14 @@ AGENTS.md # 详细规范与架构说明
|
|||||||
| [discussion-notes](skills/discussion-notes/README.md) | 边讨论边维护 Markdown 笔记 |
|
| [discussion-notes](skills/discussion-notes/README.md) | 边讨论边维护 Markdown 笔记 |
|
||||||
| [draw-prototype-flow](skills/draw-prototype-flow/README.md) | 把产品想法画成可编辑的 Draw.io 低保真原型和用户动线 |
|
| [draw-prototype-flow](skills/draw-prototype-flow/README.md) | 把产品想法画成可编辑的 Draw.io 低保真原型和用户动线 |
|
||||||
| [eli5](skills/eli5/README.md) | 用大图少字的单页 HTML 把话题讲给完全没背景的人听 |
|
| [eli5](skills/eli5/README.md) | 用大图少字的单页 HTML 把话题讲给完全没背景的人听 |
|
||||||
|
| [host-sec-check](skills/host-sec-check/README.md) | 对 Linux/VPS 做入侵取向的安全检查,默认只出报告 |
|
||||||
| [manage-release](skills/manage-release/README.md) | 给已准备好的 commit 发版本:更新说明、tag、可选 Forge Release |
|
| [manage-release](skills/manage-release/README.md) | 给已准备好的 commit 发版本:更新说明、tag、可选 Forge Release |
|
||||||
| [memory-distill](skills/memory-distill/README.md) | 任务结束后把可复用结论写入通用记忆和项目记忆 |
|
| [memory-distill](skills/memory-distill/README.md) | 任务结束后把可复用结论写入通用记忆和项目记忆 |
|
||||||
|
| [note-style](skills/note-style/README.md) | 把材料整理成可长期查阅的知识笔记 |
|
||||||
| [orc](skills/orc/README.md) | 把开发、发版、DEB / Docker 意图编排成阶段,按档位分发给对应 skill |
|
| [orc](skills/orc/README.md) | 把开发、发版、DEB / Docker 意图编排成阶段,按档位分发给对应 skill |
|
||||||
| [pouch](skills/pouch/README.md) | 创建、校验、安装和维护 builtin skill |
|
| [pouch](skills/pouch/README.md) | 创建、校验、安装和维护 builtin skill |
|
||||||
|
|
||||||
|
|
||||||
ACK 是包含规范、模板与校验脚本的完整 Skill。安装后可初始化当前项目状态:
|
ACK 是包含规范、模板与校验脚本的完整 Skill。安装后可初始化当前项目状态:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -253,3 +217,4 @@ npx skills find typescript
|
|||||||
- [Vercel skills CLI](https://github.com/vercel-labs/skills)
|
- [Vercel skills CLI](https://github.com/vercel-labs/skills)
|
||||||
- [skills.sh](https://skills.sh)
|
- [skills.sh](https://skills.sh)
|
||||||
- [Cursor Skills 文档](https://cursor.com/docs/context/skills)
|
- [Cursor Skills 文档](https://cursor.com/docs/context/skills)
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,12 @@ pouch add manage-release -g # 全局
|
|||||||
- 请求里写清目标版本、基线分支,以及允许执行到哪一步。没有明确授权的远端写操作
|
- 请求里写清目标版本、基线分支,以及允许执行到哪一步。没有明确授权的远端写操作
|
||||||
不会执行。
|
不会执行。
|
||||||
|
|
||||||
|
## 使用示例
|
||||||
|
|
||||||
|
```text
|
||||||
|
使用 manage-release,为已经合并到 main 的 1.6.0 发布 v1.6.0 tag。不要创建 Forge Release。
|
||||||
|
```
|
||||||
|
|
||||||
## 常见用法
|
## 常见用法
|
||||||
|
|
||||||
### 确定下一个版本号
|
### 确定下一个版本号
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# note-style
|
||||||
|
|
||||||
|
把已有材料整理成可长期查阅的知识笔记:压缩空话、统一术语,保留事实、公式、条件和参考。
|
||||||
|
|
||||||
|
## 什么时候使用
|
||||||
|
|
||||||
|
- 要把聊天稿、草稿或 AI 生成的长文改成能放进知识库的笔记。
|
||||||
|
- 要整理 Obsidian 笔记,去掉重复和口语。
|
||||||
|
- 要把技术原理或操作方案写成教科书 / 手册口吻。
|
||||||
|
|
||||||
|
不适合:还在讨论、还没有可依据的材料,或需要 Agent 发明新结论。
|
||||||
|
|
||||||
|
## 使用前准备
|
||||||
|
|
||||||
|
- 给出要整理的材料(文件路径、粘贴正文,或当前对话里的那一段)。
|
||||||
|
- 可选:目标读者、篇幅要求、笔记路径。不说则在原材料风格上压缩约三成。
|
||||||
|
|
||||||
|
## 使用示例
|
||||||
|
|
||||||
|
```text
|
||||||
|
用 note-style 把这篇对话整理成可放进 Obsidian 的技术笔记,保留公式和适用条件。
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
把 docs/draft-auth.md 改写成手册口吻,删掉空话,有疑点列待核实项。
|
||||||
|
```
|
||||||
|
|
||||||
|
## Agent 会做什么
|
||||||
|
|
||||||
|
1. 抽出事实、定义、步骤、条件和参考,删掉铺垫和重复。
|
||||||
|
2. 按材料类型选结构(原理或操作),用中性书面语重写全文。
|
||||||
|
3. 先给你完整笔记;只有事实或技术疑点时才追加「待核实项」。
|
||||||
|
|
||||||
|
不会补没有依据的结论,也不会为缩短篇幅删掉必要条件。
|
||||||
|
|
||||||
|
## 如何判断完成
|
||||||
|
|
||||||
|
- 得到一份完整笔记,而不是改写说明。
|
||||||
|
- 原文里的公式、条件、安全警告和参考还在。
|
||||||
|
- 没有聊天式过渡或段末复述。
|
||||||
+14
-5
@@ -7,6 +7,7 @@
|
|||||||
- 想把项目里的重复工作沉淀成一个 skill。
|
- 想把项目里的重复工作沉淀成一个 skill。
|
||||||
- 想完善、校验、转正或发布已有 skill。
|
- 想完善、校验、转正或发布已有 skill。
|
||||||
- 想把自研 skill 安装到当前项目或全局 Agent。
|
- 想把自研 skill 安装到当前项目或全局 Agent。
|
||||||
|
- 想让 Agent 代装 pouch,或介绍有哪些 skill、在当前项目接入。
|
||||||
- 想检查并修复 skill 软链接。
|
- 想检查并修复 skill 软链接。
|
||||||
- 想优化某个已有 skill 的结构,减少点中后灌进上下文的内容。
|
- 想优化某个已有 skill 的结构,减少点中后灌进上下文的内容。
|
||||||
|
|
||||||
@@ -22,6 +23,10 @@ CLI 命令细节见仓库里的 [pouch/README.md](../../pouch/README.md)。
|
|||||||
## 使用示例
|
## 使用示例
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
帮我安装 pouch。
|
||||||
|
帮我装 https://git.yumee.top/laily/pouch
|
||||||
|
有哪些自研 skill,各用一句话介绍。
|
||||||
|
把这个项目该用的 skill 接上。
|
||||||
帮我把「评审 PR 时按这份清单走」做成一个自研 skill。
|
帮我把「评审 PR 时按这份清单走」做成一个自研 skill。
|
||||||
pouch 帮我检查 discussion-notes 这个 skill。
|
pouch 帮我检查 discussion-notes 这个 skill。
|
||||||
pouch 帮忙优化 ack 这个 skill 结构,减少 token 浪费。
|
pouch 帮忙优化 ack 这个 skill 结构,减少 token 浪费。
|
||||||
@@ -107,15 +112,19 @@ pouch doctor --fix
|
|||||||
|
|
||||||
## Agent 会做什么
|
## Agent 会做什么
|
||||||
|
|
||||||
1. 创建草稿到 `~/.pouch/.drafts/`,按你的想法完善 `SKILL.md` 和给人看的 `README.md`。
|
1. 本机还没有 `~/.pouch` 时,按安装剧本 clone 或链接,跑 `install.sh`,再介绍 builtin skill。
|
||||||
2. 跑 `pouch check`;你确认后再 `finalize` 到 `skills/`。
|
2. 在业务仓库里按文件信号建议 skill,`pouch add` 之后问一句要不要初始化 ack / builder / deployer。
|
||||||
3. 安装只建指向 SSOT 的软链,不复制文件。默认装当前项目;只有你明确要求时才加 `-g`。
|
3. 创建草稿到 `~/.pouch/.drafts/`,按你的想法完善 `SKILL.md` 和给人看的 `README.md`。
|
||||||
4. 优化结构时先测量再改 SSOT,不删发版、部署类安全限制。
|
4. 跑 `pouch check`;你确认后再 `finalize` 到 `skills/`。
|
||||||
5. 未要求时不 commit、不 push、不全局安装。
|
5. 安装只建指向 SSOT 的软链,不复制文件。默认装当前项目;只有你明确要求时才加 `-g`。
|
||||||
|
6. 优化结构时先测量再改 SSOT,不删发版、部署类安全限制。
|
||||||
|
7. 未要求时不 commit、不 push、不全局安装。
|
||||||
|
|
||||||
## 如何判断完成
|
## 如何判断完成
|
||||||
|
|
||||||
- `pouch check <name>` 输出校验通过。
|
- `pouch check <name>` 输出校验通过。
|
||||||
- 正式 skill 同时包含 `SKILL.md` 和 `README.md`。
|
- 正式 skill 同时包含 `SKILL.md` 和 `README.md`。
|
||||||
- `pouch status` 或 `pouch doctor` 显示目标软链接正常。
|
- `pouch status` 或 `pouch doctor` 显示目标软链接正常。
|
||||||
|
- 代装完成后 CLI 可用,且介绍了 builtin skill 的一句话和示例请求。
|
||||||
|
- 项目接入后,建议的 skill 已 `pouch add`;ack / builder / deployer 要么已初始化,要么明确问过。
|
||||||
- 优化结构时:审计报告有前后体积,安全限制仍在 `SKILL.md`,没有为缩字删掉模式。
|
- 优化结构时:审计报告有前后体积,安全限制仍在 `SKILL.md`,没有为缩字删掉模式。
|
||||||
|
|||||||
+25
-11
@@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
name: pouch
|
name: pouch
|
||||||
description: >-
|
description: >-
|
||||||
创建、校验、转正、安装或发布 ~/.pouch 自研 skill,初始化 skill 项目状态,
|
创建、校验、转正、安装或发布 ~/.pouch 自研 skill;代装 pouch、介绍 builtin
|
||||||
或优化 skill 分层加载结构以减少 token 浪费。
|
skill、在项目里接入并引导初始化;或优化 skill 分层结构以减少 token 浪费。
|
||||||
触发词:pouch、skiff、自研 skill、创建 skill、publish skill、安装自研 skill、
|
触发词:pouch、skiff、帮我装 pouch、有哪些 skill、skill 怎么用、接入当前项目、
|
||||||
初始化 skill、优化 skill 结构、减少 token 浪费、skill 太长、分层加载。
|
初始化 skill、创建 skill、publish skill、优化 skill 结构、分层加载。
|
||||||
---
|
---
|
||||||
|
|
||||||
# pouch 自研 Skill 工作流
|
# pouch 自研 Skill 工作流
|
||||||
@@ -15,24 +15,38 @@ SSOT 固定在 `~/.pouch/skills/<name>/`。内容通过 **symlink** 分发到各
|
|||||||
|
|
||||||
## 选择模式
|
## 选择模式
|
||||||
|
|
||||||
|
- 安装 pouch、帮我装 pouch、代装、丢仓库 URL、`~/.pouch` 还不存在:读
|
||||||
|
[install.md](references/install.md) 并执行。
|
||||||
|
- 有哪些 skill、skill 怎么用、介绍 skill:执行「介绍 builtin skill」。
|
||||||
|
- 在这个项目接入、把 skill 接上、引导初始化:读
|
||||||
|
[onboard.md](references/onboard.md) 并执行。
|
||||||
- 创建、完善、转正草稿:执行「创建新的 skill」。
|
- 创建、完善、转正草稿:执行「创建新的 skill」。
|
||||||
- 行为不对、触发不准、校验失败、可复用优化回流:执行「问题或优化回流」。
|
- 行为不对、触发不准、校验失败、可复用优化回流:执行「问题或优化回流」。
|
||||||
- 优化结构、减少 token 浪费、skill 太长、分层加载:执行「优化 skill 结构」。先读
|
- 优化结构、减少 token 浪费、skill 太长、分层加载:执行「优化 skill 结构」。
|
||||||
[token-structure.md](references/token-structure.md)。
|
|
||||||
- 安装、卸载、浏览、init、status:执行「安装与维护」。
|
- 安装、卸载、浏览、init、status:执行「安装与维护」。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 在项目中使用 skill
|
## 介绍 builtin skill
|
||||||
|
|
||||||
先浏览可用的 builtin skill,再安装到当前项目:
|
不要通读各 README,不要把 `pouch add --list` 的 description 念给用户。跑:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 <pouch-skill-dir>/scripts/catalog.py
|
||||||
|
```
|
||||||
|
|
||||||
|
把表格里的「做什么」和「可以这么说」原样告诉用户。用户点名再 `pouch add`。
|
||||||
|
|
||||||
|
## 在项目中使用 skill
|
||||||
|
|
||||||
|
已点名则直接安装;没点名或要按仓库判断:读 [onboard.md](references/onboard.md)。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pouch add --list
|
|
||||||
pouch add <name> -a cursor -a claude -a codex -a agents -y
|
pouch add <name> -a cursor -a claude -a codex -a agents -y
|
||||||
```
|
```
|
||||||
|
|
||||||
`pouch add <name>` 默认安装到当前项目;只有用户明确需要所有项目使用时才加 `-g`。安装结果是指向 `~/.pouch/skills/<name>/` 的软链,不要在 Agent 目录创建副本。
|
默认当前项目;只有用户明确需要所有项目使用时才加 `-g`。只建指向
|
||||||
|
`~/.pouch/skills/<name>/` 的软链,不要在 Agent 目录创建副本。
|
||||||
|
|
||||||
## 创建新的 skill
|
## 创建新的 skill
|
||||||
|
|
||||||
@@ -159,7 +173,7 @@ pouch remove discussion-notes -g -a cursor -y # 全局
|
|||||||
pouch rm discussion-notes -g -y # rm 别名
|
pouch rm discussion-notes -g -y # rm 别名
|
||||||
```
|
```
|
||||||
|
|
||||||
浏览可用自研 skill:
|
浏览可用自研 skill(给 Agent 匹配用,不要念给用户):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pouch add --list
|
pouch add --list
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# 安装 pouch
|
||||||
|
|
||||||
|
用户说「帮我安装 pouch」「把 skill 装上」,或丢来仓库 URL / 本地路径时执行。
|
||||||
|
本文件是代装步骤的唯一详细来源。不要用 `pouch select`(必须交互式 TTY)。
|
||||||
|
不要在未确认时覆盖已有的 `~/.pouch`。CLI 只认 `~/.pouch`。
|
||||||
|
|
||||||
|
默认 remote:`https://git.yumee.top/laily/pouch.git`。用户给了 URL 或本地路径则用用户的。
|
||||||
|
|
||||||
|
## 1. 确认本机
|
||||||
|
|
||||||
|
需要 `python3` 和 `git`。缺了先说明怎么装,不要继续。
|
||||||
|
|
||||||
|
## 2. 让 `~/.pouch` 指向本仓库
|
||||||
|
|
||||||
|
按现状只走一条:
|
||||||
|
|
||||||
|
| 现状 | 动作 |
|
||||||
|
| --- | --- |
|
||||||
|
| 当前目录就是这份仓库(有 `install.sh` 和 `skills/`),且没有 `~/.pouch` | `ln -sfn "$(pwd)" "$HOME/.pouch"` |
|
||||||
|
| `~/.pouch` 已在且含 `skills/`、`install.sh` | 沿用 |
|
||||||
|
| 只有 `~/.skills` | `mv ~/.skills ~/.pouch` |
|
||||||
|
| 都没有 | `git clone <URL> ~/.pouch`。失败则问用户本地路径或可用 remote |
|
||||||
|
| `~/.pouch` 在,但不是这份仓库 | 停下问用户,不要删、不要覆盖 |
|
||||||
|
|
||||||
|
clone 私有 Git 失败时停下来问路径或认证,不要改 remote、不要改用交互式 `pouch select`。
|
||||||
|
|
||||||
|
## 3. 装 CLI 和 `pouch` skill
|
||||||
|
|
||||||
|
```bash
|
||||||
|
"$HOME/.pouch/install.sh"
|
||||||
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
|
command -v pouch
|
||||||
|
pouch status
|
||||||
|
```
|
||||||
|
|
||||||
|
`install.sh` 会改 rc,当前会话仍要 `export PATH`。不要让用户重开终端才继续。
|
||||||
|
它只全局安装 **pouch** 这一个 skill。其它 skill 不要默认全装。
|
||||||
|
|
||||||
|
## 4. 介绍 builtin skill,再问要装哪些
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 "$HOME/.pouch/skills/pouch/scripts/catalog.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
把表格里的「做什么」和「可以这么说」告诉用户。不要念 frontmatter `description`,
|
||||||
|
不要通读各 README。用户没点名就问一次:装哪些、全局还是当前项目。能判断就不要追问。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pouch add <name> -g -y # 全局,所有项目都能用
|
||||||
|
pouch add <name> -y # 仅当前项目
|
||||||
|
```
|
||||||
|
|
||||||
|
默认 `pouch add` 装到当前工作目录。cwd 是 pouch 仓库本身时用 `-g`,除非用户就在维护 pouch。
|
||||||
|
用户是在自己的应用仓库里请你代装,才做项目级安装。
|
||||||
|
|
||||||
|
## 5. 验收
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pouch status
|
||||||
|
pouch doctor
|
||||||
|
```
|
||||||
|
|
||||||
|
软链异常则 `pouch doctor --fix`。已经装好时只汇报状态,问还要不要加 skill,不要重新 clone。
|
||||||
|
|
||||||
|
当前目录是业务仓库(不是 pouch 仓库)时,回到 SKILL.md,走「接入当前项目」。
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# 在当前项目接入 skill
|
||||||
|
|
||||||
|
用户说「把 skill 接上」「在这个项目初始化 skill」「这个仓库该用哪些 pouch skill」时执行。
|
||||||
|
安装和初始化是两步,不要只 `pouch add` 就当接入完成。
|
||||||
|
|
||||||
|
当前目录是 pouch 仓库本身(有 `install.sh`、`skills/pouch/SKILL.md`、`bin/pouch`)时停:
|
||||||
|
这是维护 SSOT 的仓库,不要按业务项目接入。改 skill 走「创建」或「问题或优化回流」。
|
||||||
|
|
||||||
|
## 1. 看仓库信号
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 <pouch-skill-dir>/scripts/suggest_skills.py
|
||||||
|
```
|
||||||
|
|
||||||
|
只根据脚本输出建议,不要为了「可能有用」推荐 eli5、note-style、host-sec-check 这类无仓库信号的 skill。
|
||||||
|
用户点名的除外。
|
||||||
|
|
||||||
|
## 2. 安装
|
||||||
|
|
||||||
|
把 `suggested` 和 `already` 都列为应安装的 skill(`already` 表示项目里已有状态,
|
||||||
|
软链仍要 `pouch add`,除非已经装上)。用户说「接上该用的」时只装这两类,不要全装 builtin。
|
||||||
|
只对 `init` 列问要不要初始化。
|
||||||
|
|
||||||
|
默认当前项目:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pouch add <name> -a cursor -a claude -a codex -a agents -y
|
||||||
|
```
|
||||||
|
|
||||||
|
只有用户明确要所有项目都能用时才加 `-g`。cwd 是 pouch 仓库时不要走到这一步。
|
||||||
|
|
||||||
|
## 3. 问一句要不要初始化
|
||||||
|
|
||||||
|
对已经装上、或本来就在的 ack / builder / deployer,看脚本的 `init` 列:
|
||||||
|
|
||||||
|
| Skill | 尚未初始化时对用户说 | 不要 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| ack | `/ack 初始化` 或 `pouch init ack` | 静默 `pouch init` |
|
||||||
|
| builder | 「用 builder 初始化」 | `pouch init builder` |
|
||||||
|
| deployer | 「用 deployer 初始化」 | `pouch init deployer` |
|
||||||
|
|
||||||
|
一次问清要初始化哪些。用户没点名不要自己跑初始化。ACK 初始化**不会**自动初始化
|
||||||
|
deployer / builder,需要的话要另说。
|
||||||
|
|
||||||
|
orc、manage-release 和其余 builtin 装上就能用,不必项目初始化。
|
||||||
|
|
||||||
|
## 4. 介绍怎么喊
|
||||||
|
|
||||||
|
装完后跑 `catalog.py`,只念本次装上的那几行「可以这么说」。
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Print a human-facing builtin skill list: one-liner + copy-paste example.
|
||||||
|
|
||||||
|
Reads each skill's README.md. Do not substitute SKILL.md descriptions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
_HEADING_RE = re.compile(r"^#{1,6}\s+")
|
||||||
|
_FENCE_RE = re.compile(r"^```(\w+)?\s*$")
|
||||||
|
|
||||||
|
|
||||||
|
def skills_dir() -> Path:
|
||||||
|
here = Path(__file__).resolve()
|
||||||
|
repo_skills = here.parents[2]
|
||||||
|
if (repo_skills / "pouch" / "SKILL.md").is_file():
|
||||||
|
return repo_skills
|
||||||
|
return Path.home() / ".pouch" / "skills"
|
||||||
|
|
||||||
|
|
||||||
|
def _strip_md(text: str) -> str:
|
||||||
|
text = re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", text)
|
||||||
|
text = re.sub(r"`([^`]+)`", r"\1", text)
|
||||||
|
return " ".join(text.split())
|
||||||
|
|
||||||
|
|
||||||
|
def _first_sentence(text: str) -> str:
|
||||||
|
text = _strip_md(text).strip()
|
||||||
|
for sep in ("。", "!", "?"):
|
||||||
|
idx = text.find(sep)
|
||||||
|
if idx != -1:
|
||||||
|
return text[: idx + 1]
|
||||||
|
return text.rstrip("::") + ("。" if text else "")
|
||||||
|
|
||||||
|
|
||||||
|
def _lede(readme: str) -> str:
|
||||||
|
lines = readme.splitlines()
|
||||||
|
i = 0
|
||||||
|
while i < len(lines) and (
|
||||||
|
not lines[i].strip() or _HEADING_RE.match(lines[i])
|
||||||
|
):
|
||||||
|
i += 1
|
||||||
|
chunk: list[str] = []
|
||||||
|
while i < len(lines):
|
||||||
|
raw = lines[i]
|
||||||
|
stripped = raw.strip()
|
||||||
|
if not stripped:
|
||||||
|
break
|
||||||
|
if _HEADING_RE.match(raw) or stripped.startswith(("- ", "* ", "> ")):
|
||||||
|
break
|
||||||
|
chunk.append(stripped)
|
||||||
|
i += 1
|
||||||
|
return _first_sentence(" ".join(chunk))
|
||||||
|
|
||||||
|
|
||||||
|
def _section_body(readme: str, titles: tuple[str, ...]) -> str | None:
|
||||||
|
lines = readme.splitlines()
|
||||||
|
start = None
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if line.startswith("## ") and line[3:].strip() in titles:
|
||||||
|
start = i + 1
|
||||||
|
break
|
||||||
|
if start is None:
|
||||||
|
return None
|
||||||
|
end = len(lines)
|
||||||
|
for j in range(start, len(lines)):
|
||||||
|
if lines[j].startswith("## "):
|
||||||
|
end = j
|
||||||
|
break
|
||||||
|
return "\n".join(lines[start:end])
|
||||||
|
|
||||||
|
|
||||||
|
def _first_example(readme: str) -> str:
|
||||||
|
body = _section_body(readme, ("使用示例", "常见用法"))
|
||||||
|
search = body if body is not None else readme
|
||||||
|
lines = search.splitlines()
|
||||||
|
i = 0
|
||||||
|
while i < len(lines):
|
||||||
|
fence = _FENCE_RE.match(lines[i].strip())
|
||||||
|
if not fence:
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
i += 1
|
||||||
|
block: list[str] = []
|
||||||
|
while i < len(lines) and not lines[i].strip().startswith("```"):
|
||||||
|
block.append(lines[i])
|
||||||
|
i += 1
|
||||||
|
for raw in block:
|
||||||
|
stripped = raw.strip()
|
||||||
|
if not stripped or stripped.startswith("#"):
|
||||||
|
continue
|
||||||
|
return stripped
|
||||||
|
i += 1
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def iter_skills(root: Path) -> list[Path]:
|
||||||
|
skills: list[Path] = []
|
||||||
|
if not root.is_dir():
|
||||||
|
return skills
|
||||||
|
for entry in sorted(root.iterdir()):
|
||||||
|
if not entry.is_dir() or entry.name.startswith("_"):
|
||||||
|
continue
|
||||||
|
if (entry / "SKILL.md").is_file():
|
||||||
|
skills.append(entry)
|
||||||
|
return skills
|
||||||
|
|
||||||
|
|
||||||
|
def catalog_rows(root: Path) -> list[tuple[str, str, str]]:
|
||||||
|
rows: list[tuple[str, str, str]] = []
|
||||||
|
errors: list[str] = []
|
||||||
|
for skill in iter_skills(root):
|
||||||
|
readme_path = skill / "README.md"
|
||||||
|
if not readme_path.is_file():
|
||||||
|
errors.append(f"{skill.name}: 缺少 README.md")
|
||||||
|
continue
|
||||||
|
text = readme_path.read_text(encoding="utf-8")
|
||||||
|
lede = _lede(text)
|
||||||
|
example = _first_example(text)
|
||||||
|
if not lede:
|
||||||
|
errors.append(f"{skill.name}: README 缺少一句话说明")
|
||||||
|
if not example:
|
||||||
|
errors.append(f"{skill.name}: README 缺少可复制的使用示例")
|
||||||
|
if lede and example:
|
||||||
|
rows.append((skill.name, lede, example))
|
||||||
|
if errors:
|
||||||
|
raise SystemExit("catalog 失败:\n " + "\n ".join(errors))
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def format_table(rows: list[tuple[str, str, str]]) -> str:
|
||||||
|
lines = [
|
||||||
|
"| Skill | 做什么 | 可以这么说 |",
|
||||||
|
"| --- | --- | --- |",
|
||||||
|
]
|
||||||
|
for name, lede, example in rows:
|
||||||
|
example = example.replace("|", "\\|")
|
||||||
|
lede = lede.replace("|", "\\|")
|
||||||
|
lines.append(f"| {name} | {lede} | `{example}` |")
|
||||||
|
return "\n".join(lines) + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="列出 builtin skill 的一句话说明和可复制示例"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--skills-dir",
|
||||||
|
type=Path,
|
||||||
|
default=None,
|
||||||
|
help="skill 根目录(默认本仓库 skills/ 或 ~/.pouch/skills)",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
root = args.skills_dir.resolve() if args.skills_dir else skills_dir()
|
||||||
|
if not root.is_dir():
|
||||||
|
print(f"找不到 skills 目录: {root}", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
sys.stdout.write(format_table(catalog_rows(root)))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Suggest pouch skills from well-known files in a project root."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
INIT = {
|
||||||
|
"ack": "说「/ack 初始化」或 `pouch init ack`;不要静默初始化",
|
||||||
|
"builder": "说「用 builder 初始化」;不要 `pouch init builder`",
|
||||||
|
"deployer": "说「用 deployer 初始化」;不要 `pouch init deployer`",
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE_SIGNALS: dict[str, tuple[str, ...]] = {
|
||||||
|
"ack": (".pouch/ack",),
|
||||||
|
"builder": (
|
||||||
|
"makefile.builder",
|
||||||
|
"debian",
|
||||||
|
"Dockerfile",
|
||||||
|
"Containerfile",
|
||||||
|
".env.builder",
|
||||||
|
),
|
||||||
|
"deployer": (
|
||||||
|
".pouch/deployer",
|
||||||
|
"compose.yaml",
|
||||||
|
"compose.yml",
|
||||||
|
"docker-compose.yaml",
|
||||||
|
"docker-compose.yml",
|
||||||
|
"argocd.yaml",
|
||||||
|
"k8s",
|
||||||
|
"helm",
|
||||||
|
),
|
||||||
|
"manage-release": ("CHANGELOG.md", "CHANGELOG", "VERSION"),
|
||||||
|
"declarative-openspec-loop": ("openspec", ".openspec"),
|
||||||
|
"discussion-notes": ("000inbox",),
|
||||||
|
}
|
||||||
|
|
||||||
|
INITED_MARKERS = {
|
||||||
|
"ack": (".pouch/ack",),
|
||||||
|
"builder": ("makefile.builder",),
|
||||||
|
"deployer": (".pouch/deployer",),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def project_root(start: Path) -> Path:
|
||||||
|
cur = start.resolve()
|
||||||
|
if cur.is_file():
|
||||||
|
cur = cur.parent
|
||||||
|
for candidate in (cur, *cur.parents):
|
||||||
|
if (candidate / ".git").exists():
|
||||||
|
return candidate
|
||||||
|
return cur
|
||||||
|
|
||||||
|
|
||||||
|
def is_pouch_repo(root: Path) -> bool:
|
||||||
|
return (
|
||||||
|
(root / "install.sh").is_file()
|
||||||
|
and (root / "bin" / "pouch").is_file()
|
||||||
|
and (root / "skills" / "pouch" / "SKILL.md").is_file()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def existing(root: Path, rel: str) -> bool:
|
||||||
|
return (root / rel).exists()
|
||||||
|
|
||||||
|
|
||||||
|
def hits(root: Path, names: tuple[str, ...]) -> list[str]:
|
||||||
|
return [name for name in names if existing(root, name)]
|
||||||
|
|
||||||
|
|
||||||
|
def suggest(root: Path) -> dict[str, object]:
|
||||||
|
suggested: list[tuple[str, list[str]]] = []
|
||||||
|
already: list[tuple[str, list[str]]] = []
|
||||||
|
init: list[tuple[str, str]] = []
|
||||||
|
for name, names in FILE_SIGNALS.items():
|
||||||
|
found = hits(root, names)
|
||||||
|
if not found:
|
||||||
|
continue
|
||||||
|
inited = hits(root, INITED_MARKERS.get(name, ()))
|
||||||
|
if inited:
|
||||||
|
already.append((name, inited))
|
||||||
|
else:
|
||||||
|
suggested.append((name, found))
|
||||||
|
if name in INIT and not inited:
|
||||||
|
init.append((name, INIT[name]))
|
||||||
|
return {
|
||||||
|
"root": str(root),
|
||||||
|
"this_is_pouch_repo": is_pouch_repo(root),
|
||||||
|
"suggested": suggested,
|
||||||
|
"already": already,
|
||||||
|
"init": init,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def format_report(data: dict[str, object]) -> str:
|
||||||
|
lines = [f"project: {data['root']}"]
|
||||||
|
if data["this_is_pouch_repo"]:
|
||||||
|
lines.append("this_is_pouch_repo: true")
|
||||||
|
lines.append("不要按业务项目接入。维护 skill 走 pouch 的创建或回流模式。")
|
||||||
|
return "\n".join(lines) + "\n"
|
||||||
|
|
||||||
|
lines.append("this_is_pouch_repo: false")
|
||||||
|
suggested = data["suggested"]
|
||||||
|
already = data["already"]
|
||||||
|
init = data["init"]
|
||||||
|
if suggested:
|
||||||
|
lines.append("suggested:")
|
||||||
|
for name, why in suggested:
|
||||||
|
lines.append(f" {name}\twhy: {', '.join(why)}")
|
||||||
|
else:
|
||||||
|
lines.append("suggested: (none)")
|
||||||
|
if already:
|
||||||
|
lines.append("already:")
|
||||||
|
for name, why in already:
|
||||||
|
lines.append(f" {name}\t{', '.join(why)}")
|
||||||
|
else:
|
||||||
|
lines.append("already: (none)")
|
||||||
|
if init:
|
||||||
|
lines.append("init:")
|
||||||
|
for name, how in init:
|
||||||
|
lines.append(f" {name}\t{how}")
|
||||||
|
else:
|
||||||
|
lines.append("init: (none)")
|
||||||
|
return "\n".join(lines) + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description="根据仓库信号建议 pouch skill")
|
||||||
|
parser.add_argument(
|
||||||
|
"path",
|
||||||
|
nargs="?",
|
||||||
|
default=".",
|
||||||
|
help="项目目录(默认 cwd;向上找 .git)",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
root = project_root(Path(args.path))
|
||||||
|
sys.stdout.write(format_report(suggest(root)))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
CATALOG = REPO_ROOT / "skills" / "pouch" / "scripts" / "catalog.py"
|
||||||
|
SUGGEST = REPO_ROOT / "skills" / "pouch" / "scripts" / "suggest_skills.py"
|
||||||
|
|
||||||
|
|
||||||
|
def _run(script: Path, *args: str) -> subprocess.CompletedProcess[str]:
|
||||||
|
return subprocess.run(
|
||||||
|
[sys.executable, str(script), *args],
|
||||||
|
check=False,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CatalogTests(unittest.TestCase):
|
||||||
|
def test_repo_skills_have_lede_and_example(self) -> None:
|
||||||
|
result = _run(CATALOG, "--skills-dir", str(REPO_ROOT / "skills"))
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
for name in (
|
||||||
|
"ack",
|
||||||
|
"builder",
|
||||||
|
"deployer",
|
||||||
|
"host-sec-check",
|
||||||
|
"manage-release",
|
||||||
|
"note-style",
|
||||||
|
"pouch",
|
||||||
|
):
|
||||||
|
self.assertIn(f"| {name} |", result.stdout)
|
||||||
|
self.assertIn("/ack 初始化", result.stdout)
|
||||||
|
self.assertIn("用 builder 初始化这个项目", result.stdout)
|
||||||
|
self.assertNotIn("触发词", result.stdout)
|
||||||
|
|
||||||
|
def test_missing_readme_fails(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
skill = Path(tmp) / "demo"
|
||||||
|
skill.mkdir()
|
||||||
|
(skill / "SKILL.md").write_text(
|
||||||
|
"---\nname: demo\ndescription: d\n---\n# demo\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
result = _run(CATALOG, "--skills-dir", tmp)
|
||||||
|
self.assertEqual(result.returncode, 1)
|
||||||
|
self.assertIn("缺少 README.md", result.stderr)
|
||||||
|
|
||||||
|
def test_extracts_first_sentence_and_skips_hash_comments(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
skill = Path(tmp) / "demo"
|
||||||
|
skill.mkdir()
|
||||||
|
(skill / "SKILL.md").write_text(
|
||||||
|
"---\nname: demo\ndescription: d\n---\n# demo\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
(skill / "README.md").write_text(
|
||||||
|
"# demo\n\n"
|
||||||
|
"这是一句话说明。后面还有一句不该进短清单。\n\n"
|
||||||
|
"## 使用示例\n\n"
|
||||||
|
"```text\n"
|
||||||
|
"# 注释\n"
|
||||||
|
"用 demo 做这件事。\n"
|
||||||
|
"```\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
result = _run(CATALOG, "--skills-dir", tmp)
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
self.assertIn("这是一句话说明。", result.stdout)
|
||||||
|
self.assertNotIn("不该进短清单", result.stdout)
|
||||||
|
self.assertIn("用 demo 做这件事。", result.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
class SuggestSkillsTests(unittest.TestCase):
|
||||||
|
def test_pouch_repo_is_not_an_app(self) -> None:
|
||||||
|
result = _run(SUGGEST, str(REPO_ROOT))
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
self.assertIn("this_is_pouch_repo: true", result.stdout)
|
||||||
|
self.assertNotIn("suggested:", result.stdout)
|
||||||
|
|
||||||
|
def test_dockerfile_suggests_builder(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
root = Path(tmp)
|
||||||
|
(root / "Dockerfile").write_text("FROM scratch\n", encoding="utf-8")
|
||||||
|
result = _run(SUGGEST, str(root))
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
self.assertIn("this_is_pouch_repo: false", result.stdout)
|
||||||
|
self.assertIn("builder", result.stdout)
|
||||||
|
self.assertIn("Dockerfile", result.stdout)
|
||||||
|
self.assertIn("用 builder 初始化", result.stdout)
|
||||||
|
self.assertIn("不要 `pouch init builder`", result.stdout)
|
||||||
|
|
||||||
|
def test_existing_makefile_builder_is_already(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
root = Path(tmp)
|
||||||
|
(root / "makefile.builder").write_text("build:\n", encoding="utf-8")
|
||||||
|
result = _run(SUGGEST, str(root))
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
self.assertIn("already:", result.stdout)
|
||||||
|
self.assertIn("builder", result.stdout)
|
||||||
|
self.assertIn("init: (none)", result.stdout)
|
||||||
|
|
||||||
|
def test_compose_and_ack_dir(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
root = Path(tmp)
|
||||||
|
(root / "compose.yaml").write_text("services: {}\n", encoding="utf-8")
|
||||||
|
(root / ".pouch" / "ack").mkdir(parents=True)
|
||||||
|
result = _run(SUGGEST, str(root))
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
self.assertIn("deployer", result.stdout)
|
||||||
|
self.assertIn("compose.yaml", result.stdout)
|
||||||
|
self.assertIn("ack", result.stdout)
|
||||||
|
self.assertIn(".pouch/ack", result.stdout)
|
||||||
|
self.assertIn("用 deployer 初始化", result.stdout)
|
||||||
|
self.assertNotIn("/ack 初始化", result.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -21,6 +21,9 @@ class ProjectSkillContentTests(unittest.TestCase):
|
|||||||
"第三方 skill",
|
"第三方 skill",
|
||||||
"pouch init ack",
|
"pouch init ack",
|
||||||
"显式调用全局 `/ack` skill",
|
"显式调用全局 `/ack` skill",
|
||||||
|
"references/install.md",
|
||||||
|
"scripts/catalog.py",
|
||||||
|
"references/onboard.md",
|
||||||
):
|
):
|
||||||
self.assertIn(expected, content)
|
self.assertIn(expected, content)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user