feat(deployer): skip sync on localhost and complete skill READMEs

Redeploy locally with docker compose when the node is 127.0.0.1 and
the image already exists. Make list/sync/remote/deb importable under
python3 -I -S. Add human README.md for every builtin skill, slim ack's
user-facing docs, and drop unused OpenSpec config.
This commit is contained in:
2026-09-04 00:07:15 +08:00
parent ef22c6829e
commit c2d6a5a0d7
22 changed files with 517 additions and 372 deletions
+1
View File
@@ -31,3 +31,4 @@ Thumbs.db
# 本地环境配置(勿提交密钥) # 本地环境配置(勿提交密钥)
.env .env
.env.* .env.*
.pouch
+27 -37
View File
@@ -12,16 +12,14 @@
## 快速开始 ## 快速开始
需要 Python 3、git,以及 Cursor / Claude Code / Codex 或其它兼容 Agent Skills 的 Agent。人类用法见根 [README.md](README.md) 和各 skill 的 `README.md`。用户要代装时按 README 的 [让 Agent 代装](README.md#让-agent-代装) 执行,不要只用 `pouch select`。
```bash ```bash
# 1. 克隆并关联
git clone https://git.yumee.top/laily/pouch.git ~/.pouch git clone https://git.yumee.top/laily/pouch.git ~/.pouch
cd ~/.pouch && ./install.sh cd ~/.pouch && ./install.sh
# install.sh 会自动把 pouch 项目 skill 安装到所有 Agent # install.sh 只把 pouch skill 装到所有 Agent;其它 skill 用 select / add
# 2. 安装其他 skill pouch select -g
pouch add declarative-openspec-loop -g
# 3. 查看状态
pouch list pouch list
pouch status pouch status
``` ```
@@ -31,19 +29,11 @@ pouch status
## 仓库结构 ## 仓库结构
``` ```
skills/ skills/ # 自研 skill(SSOT):每个子目录含 SKILL.md(Agent)和 README.md(人类)
├── _template/ # 新建 skill 的脚手架 pouch/ # CLI 源码(Python 3)
├── ack/ # 完整 ACK skill,含 references/templates/scripts bin/pouch # CLI 入口
├── declarative-openspec-loop/ # 自研 skill,必须含 SKILL.md catalog.yaml # pouch 预置 Skill 来源目录
│ ├── SKILL.md AGENTS.md # 本文档
│ └── reference.md
├── discussion-notes/ # 讨论沉淀笔记
│ ├── SKILL.md
│ └── reference.md
pouch/ # CLI 源码(Python 3)
bin/pouch # CLI 入口
catalog.yaml # pouch 预置 Skill 来源目录
AGENTS.md # 本文档
``` ```
**本仓库包含**:`skills/`、`pouch/`、`bin/pouch`、`catalog.yaml`、`AGENTS.md` **本仓库包含**:`skills/`、`pouch/`、`bin/pouch`、`catalog.yaml`、`AGENTS.md`
@@ -56,16 +46,21 @@ AGENTS.md # 本文档
### 内置(Builtin) ### 内置(Builtin)
| Skill | 说明 | | Skill | 说明 |
| ---------------------------------------------------------------------- | ------------------------------------------------- | | --- | --- |
| [orc](skills/orc/SKILL.md) | ORC 入口:显式编排开发、版本发布与产物任务,支持 Agent 分档 | | [ack](skills/ack/README.md) | 显式初始化、检查并运行三角色闭环:需求 / bug、测试环境、发版与回归 |
| [ack](skills/ack/SKILL.md) | ACK 入口:显式初始化、检查并运行三角色闭环、测试环境、发版与回归 | | [builder](skills/builder/README.md) | 按统一契约构建和发布 DEB / Docker 镜像 |
| [pouch](skills/pouch/SKILL.md) | 本项目工作流:创建、使用、反馈与更新 builtin skill | | [declarative-openspec-loop](skills/declarative-openspec-loop/README.md) | 你给校验方式,Agent 自动 propose / apply / 校验直到通过 |
| [declarative-openspec-loop](skills/declarative-openspec-loop/SKILL.md) | 声明式编程循环:用户提供校验方式,Agent 自动 propose/apply/校验并迭代直到通过 | | [deployer](skills/deployer/README.md) | 用 Git 里的 Compose 或 Argo CD 清单部署服务 |
| [discussion-notes](skills/discussion-notes/SKILL.md) | 讨论沉淀:边讨论边维护 Markdown 笔记,无 .raw.md | | [discussion-notes](skills/discussion-notes/README.md) | 边讨论边维护 Markdown 笔记 |
| [draw-prototype-flow](skills/draw-prototype-flow/README.md) | 把产品想法画成可编辑的 Draw.io 低保真原型和用户动线 |
| [manage-release](skills/manage-release/README.md) | 给已准备好的 commit 发版本:更新说明、tag、可选 Forge Release |
| [memory-distill](skills/memory-distill/README.md) | 任务结束后把可复用结论写入通用记忆和项目记忆 |
| [orc](skills/orc/README.md) | 把开发、发版、DEB / Docker 意图编排成阶段,按档位分发给对应 skill |
| [pouch](skills/pouch/README.md) | 创建、校验、安装和维护 builtin skill |
新建 skill:复制 `skills/_template/` → `skills/<name>/`,编辑 `SKILL.md`,在本仓库 commit。 新建 skill:优先 `pouch create`;或复制 `skills/_template/` → `skills/<name>/`,编辑 `SKILL.md` 和 `README.md`,在本仓库 commit。
Skill 需要的稳定规范、模板、示例和脚本直接放在自己的目录中,例如 Skill 需要的稳定规范、模板、示例和脚本直接放在自己的目录中,例如
`skills/ack/references/`、`templates/`、`examples/` 和 `scripts/`。项目初始化只生成 `skills/ack/references/`、`templates/`、`examples/` 和 `scripts/`。项目初始化只生成
@@ -164,12 +159,6 @@ catalog.yaml ←── pouch add / fetch
| **Custom Source** | `~/.local/share/pouch/sources/` 或本地路径 | `pouch source add/fetch` | | **Custom Source** | `~/.local/share/pouch/sources/` 或本地路径 | `pouch source add/fetch` |
| **External NPM** | `node_modules/` | `npx skills add` / `skills-npm` | | **External NPM** | `node_modules/` | `npx skills add` / `skills-npm` |
### 非 Skill 资料分类
| 类型 | 位置 | 维护方式 |
| --- | --- | --- |
| **OpenSpec 配置** | `openspec/` | 本仓库 commit;服务于本仓库自身的规格流程 |
### 多 Agent 路径 ### 多 Agent 路径
@@ -260,7 +249,8 @@ pouch sync
``` ```
skill-name/ skill-name/
├── SKILL.md # 必需 ├── SKILL.md # 必需,给 Agent
├── README.md # 必需,给人类
├── reference.md # 可选 ├── reference.md # 可选
├── examples.md # 可选 ├── examples.md # 可选
└── scripts/ # 可选 └── scripts/ # 可选
@@ -285,7 +275,7 @@ description: >-
### 新建流程 ### 新建流程
1. `pouch create my-skill --idea "..." --from-project .` 1. `pouch create my-skill --idea "..." --from-project .`
2. Agent 编辑 `~/.pouch/.drafts/my-skill/SKILL.md` 2. Agent 编辑 `~/.pouch/.drafts/my-skill/SKILL.md` 和 `README.md`
3. `pouch check my-skill` 3. `pouch check my-skill`
4. 用户确认后执行 `pouch finalize my-skill` 4. 用户确认后执行 `pouch finalize my-skill`
5. `pouch add my-skill -a cursor -g -y` 验证 5. `pouch add my-skill -a cursor -g -y` 验证
@@ -341,8 +331,8 @@ Claude Code 对 symlink 支持不稳定:可能无法发现 skill,或写入
| ---------- | --------------------------------- | ---- | | ---------- | --------------------------------- | ---- |
| 首次安装 | `git clone <repo> ~/.pouch && ~/.pouch/install.sh` | 任意 | | 首次安装 | `git clone <repo> ~/.pouch && ~/.pouch/install.sh` | 任意 |
| 新建 skill | `pouch create` → Agent 完善 → `check/finalize` | 任意项目 | | 新建 skill | `pouch create` → Agent 完善 → `check/finalize` | 任意项目 |
| 全局启用 | `pouch install <name>` | 任意 | | 全局启用 | `pouch add <name> -g` | 任意 |
| 项目启用 | `pouch enable <name>` | 项目目录 | | 项目启用 | `pouch add <name>` 或 `pouch enable <name>` | 项目目录 |
| 看状态 | `pouch status` | 任意 | | 看状态 | `pouch status` | 任意 |
| 装社区 skill | `npx skills add owner/repo -g -y` | 任意 | | 装社区 skill | `npx skills add owner/repo -g -y` | 任意 |
| 更新外部 skill | `pouch fetch <name>` | 任意 | | 更新外部 skill | `pouch fetch <name>` | 任意 |
+103 -17
View File
@@ -2,15 +2,26 @@
自研 [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-代装) 自己执行,不要只把命令贴回去。
## 前置
- Python 3、git
- Cursor、Claude Code、Codex,或其它兼容 Agent Skills 的 Agent
- `~/.local/bin` 需要在 `PATH` 里(`install.sh` 会写入 bash / zsh / fish 的 rc,当前 shell 要重开或 `source` 一次才生效)
## 快速开始 ## 快速开始
```bash ```bash
git clone https://git.yumee.top/laily/pouch.git ~/.pouch git clone https://git.yumee.top/laily/pouch.git ~/.pouch
cd ~/.pouch cd ~/.pouch
./install.sh # 安装 CLI,并将 pouch 项目 skill 安装到所有 Agent ./install.sh
pouch add declarative-openspec-loop -g # install.sh 会安装 pouch 命令,并把 pouch 这一个 skill 装到所有 Agent
pouch select # 交互式选择并批量安装 # 若当前 shell 找不到 pouch:重开终端,或 source 对应 rc
pouch select # 交互选择要装的 skill(默认当前项目)
pouch select -g # 或装到全局,所有项目都能用
pouch list pouch list
pouch status pouch status
``` ```
@@ -24,10 +35,77 @@ mv ~/.skills ~/.pouch
`install.sh` 会安装 `pouch` 命令,并保留 `pouch` 作为旧命令别名。项目里已有的 `.skills.yaml` 仍可读取;新写入使用 `.pouch.yaml`。 `install.sh` 会安装 `pouch` 命令,并保留 `pouch` 作为旧命令别名。项目里已有的 `.skills.yaml` 仍可读取;新写入使用 `.pouch.yaml`。
## 让 Agent 代装
用户说「帮我安装 pouch」「把 skill 装上」时按本节做。CLI 只认 `~/.pouch`。不要用 `pouch select`(必须交互式 TTY)。不要在未确认时覆盖已有的 `~/.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。
## 装完之后怎么用
1. 打开已安装目标的 Agent(Cursor / Claude Code / Codex 等)。
2. 项目级安装:打开那个项目再说话。全局安装(`-g`):任意项目都可以。
3. 按对应 skill 的 README,把示例请求发给 Agent。
`./install.sh` 只全局安装 `pouch`。其它 skill 都要 `pouch add` 或 `pouch select`。
```bash
cd ~/code/my-app
pouch add ack # 当前项目
pouch add discussion-notes -g # 全局
```
## 仓库结构 ## 仓库结构
``` ```
skills/ # 自研 skill(SSOT):每个子目录必须有 SKILL.md skills/ # 自研 skill(SSOT):每个子目录含 SKILL.md 和 README.md
pouch/ # CLI 源码(Python 3) pouch/ # CLI 源码(Python 3)
bin/pouch # CLI 入口 bin/pouch # CLI 入口
catalog.yaml # pouch 预置 Skill 来源目录 catalog.yaml # pouch 预置 Skill 来源目录
@@ -36,7 +114,7 @@ AGENTS.md # 详细规范与架构说明
| 路径 | 说明 | | 路径 | 说明 |
|------|------| |------|------|
| [skills/](skills/) | 自研 skill,每个子目录含 `SKILL.md`,可附带 references、templates 和 scripts | | [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) | 设计原则、编写规范、架构详解 |
@@ -44,14 +122,19 @@ AGENTS.md # 详细规范与架构说明
## 自研 Skill ## 自研 Skill
| Skill | 说明 | | Skill | 说明 |
|-------|------| | --- | --- |
| [orc](skills/orc/SKILL.md) | 显式编排开发、版本发布和产物任务,支持 low/mid/high Agent 档位 | | [ack](skills/ack/README.md) | 显式初始化、检查并运行三角色闭环:需求 / bug、测试环境、发版与回归 |
| [ack](skills/ack/SKILL.md) | 显式初始化、检查并运行 ACK 三角色协作及可选交付闭环 | | [builder](skills/builder/README.md) | 按统一契约构建和发布 DEB / Docker 镜像 |
| [pouch](skills/pouch/SKILL.md) | 在项目中创建、安装、反馈和维护 builtin skill | | [declarative-openspec-loop](skills/declarative-openspec-loop/README.md) | 你给校验方式,Agent 自动 propose / apply / 校验直到通过 |
| [declarative-openspec-loop](skills/declarative-openspec-loop/SKILL.md) | 声明式编程循环:用户提供校验方式,Agent 自动迭代直到通过 | | [deployer](skills/deployer/README.md) | 用 Git 里的 Compose 或 Argo CD 清单部署服务 |
| [discussion-notes](skills/discussion-notes/SKILL.md) | 讨论沉淀:边讨论边维护 Markdown 笔记 | | [discussion-notes](skills/discussion-notes/README.md) | 边讨论边维护 Markdown 笔记 |
| [draw-prototype-flow](skills/draw-prototype-flow/README.md) | 把产品想法画成可编辑的 Draw.io 低保真原型和用户动线 |
| [manage-release](skills/manage-release/README.md) | 给已准备好的 commit 发版本:更新说明、tag、可选 Forge Release |
| [memory-distill](skills/memory-distill/README.md) | 任务结束后把可复用结论写入通用记忆和项目记忆 |
| [orc](skills/orc/README.md) | 把开发、发版、DEB / Docker 意图编排成阶段,按档位分发给对应 skill |
| [pouch](skills/pouch/README.md) | 创建、校验、安装和维护 builtin skill |
ACK 是包含规范、模板与校验脚本的完整 Skill。安装 Skill 后可初始化当前项目状态: ACK 是包含规范、模板与校验脚本的完整 Skill。安装后可初始化当前项目状态:
```bash ```bash
pouch init ack pouch init ack
@@ -62,11 +145,13 @@ pouch init ack --project ~/app
项目可用自然语言让 `/ack` 把测试环境绑到 deployer、维护发版 profile,并在任务 项目可用自然语言让 `/ack` 把测试环境绑到 deployer、维护发版 profile,并在任务
验证通过后收获回归用例。 验证通过后收获回归用例。
builder 与 deployer 的项目接入走各自 skill 的「初始化」模式,不要 `pouch init builder` 或 `pouch init deployer`。
新建 skill: 新建 skill:
```bash ```bash
pouch create my-skill --idea "描述要解决的重复问题" --from-project . pouch create my-skill --idea "描述要解决的重复问题" --from-project .
# 由 Agent 完善 ~/.pouch/.drafts/my-skill/SKILL.md # 由 Agent 完善 ~/.pouch/.drafts/my-skill/SKILL.md 和 README.md
pouch check my-skill pouch check my-skill
pouch finalize my-skill pouch finalize my-skill
pouch add my-skill -g # 全局安装验证 pouch add my-skill -g # 全局安装验证
@@ -89,7 +174,7 @@ pouch add <name> -g -a cursor
```yaml ```yaml
skills: skills:
- name: declarative-openspec-loop - name: ack
source: builtin source: builtin
- name: think - name: think
source: catalog:waza source: catalog:waza
@@ -101,9 +186,9 @@ targets: # 可选,默认 all
``` ```
```bash ```bash
pouch add declarative-openspec-loop pouch add ack
pouch sync pouch sync
pouch remove declarative-openspec-loop pouch remove ack
``` ```
## Catalog 与 Custom Source ## Catalog 与 Custom Source
@@ -133,7 +218,7 @@ npx skills find typescript
``` ```
本仓库 本仓库
├── skills/<name>/ ←── pouch install / enable ├── skills/<name>/ ←── pouch add / select
├── catalog.yaml ←── 预置来源发现与 fetch ├── catalog.yaml ←── 预置来源发现与 fetch
└── pouch/ ←── python3 -m pouch └── pouch/ ←── python3 -m pouch
↑ ↑
@@ -159,6 +244,7 @@ npx skills find typescript
- [AGENTS.md](AGENTS.md) — 完整规范、多 Agent 路径、编写约定 - [AGENTS.md](AGENTS.md) — 完整规范、多 Agent 路径、编写约定
- [pouch/README.md](pouch/README.md) — CLI 命令参考与开发说明 - [pouch/README.md](pouch/README.md) — CLI 命令参考与开发说明
- 各 skill 的人类说明见上表 `README.md`
## 参考 ## 参考
-20
View File
@@ -1,20 +0,0 @@
schema: spec-driven
# Project context (optional)
# This is shown to AI when creating artifacts.
# Add your tech stack, conventions, style guides, domain knowledge, etc.
# Example:
# context: |
# Tech stack: TypeScript, React, Node.js
# We use conventional commits
# Domain: e-commerce platform
# Per-artifact rules (optional)
# Add custom rules for specific artifacts.
# Example:
# rules:
# proposal:
# - Keep proposals under 500 words
# - Always include a "Non-goals" section
# tasks:
# - Break tasks into chunks of max 2 hours
+2 -1
View File
@@ -201,7 +201,7 @@ pouch doctor -a cursor
```bash ```bash
cd ~/code/my-app cd ~/code/my-app
pouch add declarative-openspec-loop -a cursor -y pouch add discussion-notes -a cursor -y
``` ```
### 添加 Catalog Source ### 添加 Catalog Source
@@ -251,3 +251,4 @@ pouch/
- [项目 README](../README.md) - [项目 README](../README.md)
- [AGENTS.md](../AGENTS.md) - [AGENTS.md](../AGENTS.md)
- 各 skill 的人类说明:`skills/<name>/README.md`
+54 -265
View File
@@ -1,28 +1,25 @@
# ACK # ACK
ACK 是一个显式调用的 Agent Skill,用三种独立角色运行工程协作闭环: ACK 是显式调用的 Agent Skill,用三种独立角色跑工程协作闭环:
- Coordinator 拆解需求、派发任务并终检。 - Coordinator 拆解需求、派发任务并终检
- Developer 实现并执行白盒验证。 - Developer 实现并做白盒验证
- Test 独立执行黑盒复测。 - Test 独立做黑盒复测
关键约束是验证者不等于实现者。每个任务最多修复三轮,仍未通过时记录为 验证者不能等于实现者。每个任务最多修三轮,仍不过就记为 `leftover`,然后继续别的任务。
`leftover`,然后继续处理其它任务。
测试环境部署由 ACK 触发、**内部调用 deployer** 执行。发版仍写在同一份 测试环境由 ACK 触发、内部调用 [deployer](../deployer/README.md)。发版写在同一份 `.pouch/ack/delivery.yaml`。功能或 bug 验证通过后,黑盒用例收进 `.pouch/ack/regression.yaml`,之后可以单独跑回归。
`.pouch/ack/delivery.yaml`。功能或 bug 验证通过后,把黑盒用例收进
`.pouch/ack/regression.yaml`;之后可以单独跑回归。
ACK 只在用户显式调用 `/ack` 或 `$ack` 时运行。 只在你显式说 `/ack` 或 `$ack` 时运行。
## 使用场景 ## 什么时候使用
| 场景 | 怎么说 | 结果 | | 场景 | 怎么说 | 结果 |
| --- | --- | --- | | --- | --- | --- |
| 初始化 | `/ack 初始化` | 生成并补全 `.pouch/ack/`;缺项按统一格式列出 | | 初始化 | `/ack 初始化` | 生成并补全 `.pouch/ack/`;缺项按统一格式列出 |
| 检查 | `/ack 检查配置` | 只读校验,默认不改文件;测试环境缺 deployer 时转交 | | 检查 | `/ack 检查配置` | 只读校验,默认不改文件;测试环境缺 deployer 时转交 |
| 做需求 | `/ack 处理这个需求:…` | 产品文档 + 拆任务 → 确认 → 三角色闭环 | | 做需求 | `/ack 处理这个需求:…` | 产品文档 + 拆任务 → 你确认 → 三角色闭环 |
| 修 bug | `/ack 修这个 bug:…` 或处理飞书收件 | 短描述 + 验收 → 确认(飞书须你点「已确认」)→ 同一闭环 | | 修 bug | `/ack 修这个 bug:…` 或处理飞书收件 | 短描述 + 验收 → 你确认(飞书须你点「已确认」)→ 同一闭环 |
| 交付配置 | 说明怎么布测试环境 / 怎么发版 | 写入同一份 `delivery.yaml`;测试环境绑定 deployer | | 交付配置 | 说明怎么布测试环境 / 怎么发版 | 写入同一份 `delivery.yaml`;测试环境绑定 deployer |
| 运行测试环境 | `/ack 重新布测试环境` | 内部加载 deployer,布 `.pouch/deployer/<env>` | | 运行测试环境 | `/ack 重新布测试环境` | 内部加载 deployer,布 `.pouch/deployer/<env>` |
| 运行版本发布 | `/ack 发布一个版本` | 按 `intents.release`;stable/生产仍要单独批准 | | 运行版本发布 | `/ack 发布一个版本` | 按 `intents.release`;stable/生产仍要单独批准 |
@@ -32,17 +29,18 @@ ACK 只在用户显式调用 `/ack` 或 `$ack` 时运行。
## 安装 ## 安装
全局安装:
```bash ```bash
pouch add ack -g pouch add ack # 当前项目
pouch add ack -g # 全局
``` ```
或只安装到当前项目: ## 使用前准备
```bash - 项目是 Git 仓库。
pouch add ack - 自动拉起 Developer / Test worker 时需要 Orca,以及项目里配置好的 Codex、Cursor、Grok 或 OMP。
``` - 布测试环境需要同时安装 [deployer](../deployer/README.md),并准备好 SSH 和目标机器的 Docker Compose。
- 飞书收 bug 是可选的,要在 `project.md` 里配置 `bugIntake`。
- 不要 `pouch init builder` 或 `pouch init deployer`。测试环境和发版直接对 `/ack` 说;缺 deployer 时 ACK 会转去它的初始化。
## 初始化项目 ## 初始化项目
@@ -51,7 +49,7 @@ pouch init ack
pouch init ack --project ~/code/my-app pouch init ack --project ~/code/my-app
``` ```
初始化后,项目只保存自己的 ACK 状态: 也可以直接说 `/ack 初始化`。初始化后项目只保存自己的状态:
```text ```text
.pouch/ack/ .pouch/ack/
@@ -62,271 +60,62 @@ pouch init ack --project ~/code/my-app
└── regression.yaml # 默认 cases: [] └── regression.yaml # 默认 cases: []
``` ```
不会在项目中复制或链接 ACK Skill。通用规范、模板和脚本始终从已安装的 Skill 不会把 ACK Skill 复制进项目。规范、模板和脚本始终从已安装的 Skill 目录读取。
目录读取。
ACK 从当前命令指定的 `--project-root/.pouch/ack/` 定位项目状态,不要求在 旧项目如果只有 `project.md` 和 `tasks.yaml`,不要重跑初始化。说 `/ack 检查配置`,授权后再补空的 `knowledge.yaml` 等缺件。
`tasks.yaml` 中持久化 `repoPath` 或 `devWorktree`。自动 worker 的实际工作目录由
`--worktree` 指定;默认在 `--project-root` 工作,v0.19 起不再配置 `allowedWorktrees` 白名单。
## Skill 结构 ## 使用示例
```text
skills/ack/
├── SKILL.md
├── README.md
├── VERSION
├── references/ # 三角色规范、闭环流程和初始化说明
├── templates/ # project.md、tasks.yaml、knowledge.yaml、delivery.yaml、regression.yaml 模板和 schema
├── examples/ # 完整示例
└── scripts/ # 状态校验、任务/知识/回归选择、安全验证执行与结构化 worker launcher
```
`SKILL.md` 是 Agent 的工作流入口。`references/` 是按需读取的稳定规范;
`.pouch/ack/project.md` 只保存当前项目的命令、路径和权限差异;
`.pouch/ack/tasks.yaml` 保存当前任务状态;`.pouch/ack/knowledge.yaml` 保存跨任务复用、
已经独立验证的项目知识护栏。
`.pouch/ack/delivery.yaml` 是测试环境绑定和版本发布的唯一契约;测试环境由 ACK
内部调用 deployer,运行证据记在 `tasks.yaml.deliveryRuns`。
`.pouch/ack/regression.yaml` 是黑盒回归用例目录,运行证据记在
`tasks.yaml.regressionRuns`。
## 检查项目状态
Agent 会从当前 ACK Skill 目录解析校验脚本:
```bash
python3 <ack-skill-dir>/scripts/validate_tasks.py .pouch/ack/tasks.yaml
python3 <ack-skill-dir>/scripts/validate_knowledge.py .pouch/ack/knowledge.yaml \
--tasks .pouch/ack/tasks.yaml
python3 <ack-skill-dir>/scripts/validate_delivery.py .pouch/ack/delivery.yaml \
--tasks .pouch/ack/tasks.yaml --project-root <project-root>
python3 <ack-skill-dir>/scripts/validate_regression.py .pouch/ack/regression.yaml \
--tasks .pouch/ack/tasks.yaml
```
Coordinator 可以按当前任务上下文做确定性推荐:
```bash
python3 <ack-skill-dir>/scripts/select_tasks.py .pouch/ack/tasks.yaml
python3 <ack-skill-dir>/scripts/select_tasks.py .pouch/ack/tasks.yaml \
--task-id BUG-001
python3 <ack-skill-dir>/scripts/select_knowledge.py .pouch/ack/knowledge.yaml \
--component web --path web/app.py --tag long-running-service --limit 10
python3 <ack-skill-dir>/scripts/select_regression.py .pouch/ack/regression.yaml
python3 <ack-skill-dir>/scripts/select_regression.py .pouch/ack/regression.yaml \
--suite full --case-id REG-login-001
```
任务选择器会解析并执行完整任务板的内置语义校验,但只输出 `project`、`summary`、
默认可工作状态或显式 `--task-id` 命中的任务,以及这些任务引用的 receipt 和 delivery
run。默认最多 20 条,超过预算时显式失败;Agent 不应回退为把完整 `tasks.yaml` 注入
上下文。
默认 JSON 输出会同时给出固定知识引用和已解析的 `verificationTarget.path/args`;
选择器只输出数据,不执行检查。`scope.all=true` 的全项目 active 规则优先占用
`--limit`;如果全项目规则本身超过预算,选择器会显式失败,不会静默漏派。
需要执行知识项引用的检查时,只传 registry ID 给 ACK 的安全执行入口:
```bash
python3 <ack-skill-dir>/scripts/run_verification.py \
.pouch/ack/knowledge.yaml check-api-contract --project-root <project-root>
```
该入口会在执行前重新校验知识库,只打开一次项目根目录 fd,再从同一个 fd 逐段以
`O_NOFOLLOW` 打开知识库和检查文件;检查内容复制到匿名、尽可能 sealed 的稳定
快照,再以结构化 argv 和 `shell=False` 启动。它不接受临时命令或额外参数。
选择器输出的 path/args 只用于审阅,不应由 Agent 自行拼接执行。Runner 只读取
项目内无 symlink 的权威
`.pouch/ack/knowledge.yaml`,不接受替代知识文件或放宽后的项目根。检查进程的 cwd
和 `ACK_PROJECT_ROOT` 都固定到该根 fd;后者是只在检查进程存活期间有效的
`/proc/self/fd/...` 或 `/dev/fd/...` 路径。原始可读路径另放在
`ACK_PROJECT_ROOT_DISPLAY`,只能用于日志,不能用于资源访问。Runner 还提供
`ACK_VERIFICATION_REF` 和 `ACK_VERIFICATION_PATH`;检查脚本必须据此定位资源,
不能依赖 `$0` 或 `__file__` 所在目录,因为实际执行的是匿名快照。
知识先由 Developer 或 Test 作为 `candidate` 提名,经独立验证和 Coordinator gate
后才能成为 `active`。Coordinator 按路径、组件、依赖、版本和标签推荐相关知识,
确认后将固定 revision 的 `knowledgeRefs` 写入任务上下文;每轮只派发命中的少量
条目,不全量注入知识库。
旧项目只有 `project.md` 和 `tasks.yaml` 时,不要重跑初始化。由 `/ack` 检查现有
状态,获得用户授权后补一个空的 `knowledge.yaml`;如果任务板尚未声明知识库,
同时只补 `project.knowledgeFile: .pouch/ack/knowledge.yaml`,再运行跨文件校验。
只有 Coordinator 写 `tasks.yaml`、`knowledge.yaml` 和 `regression.yaml`。知识正文
不能作为自由 shell 执行;关键约束应继续下沉到测试、lint、CI 或正式规范。ACK
不自动修改项目的 `AGENTS.md`、`CLAUDE.md` 或其它 Agent 指令文件。
## 配置与运行交付
测试环境走 deployer 的项目内布局(通常是 `.pouch/deployer/test`),发版仍用
delivery profile。可以直接说:
```text
/ack 测试环境用项目里的 .pouch/deployer/test;发版方式以后再告诉你。
```
ACK 把测试环境写成 `intents.testEnvironment.via: deployer`,把发版写成
`intents.release` 指向的 profile。`.pouch/deployer/<env>` 还没就绪时,ACK
会转去 deployer 的初始化,而不是自己编 compose。首次配置保持关闭,确认后才启用。
之后可以说:
```text ```text
/ack 初始化
/ack 检查配置
/ack 处理这个需求:给后台加导出 CSV
/ack 修这个 bug:保存后列表不刷新。复现:… 期望:…
/ack 处理飞书里待整理的 bug
/ack 测试环境用项目里的 .pouch/deployer/test;发版方式以后再告诉你
/ack 重新布一下测试环境,我要测试 /ack 重新布一下测试环境,我要测试
/ack 发布一个版本 /ack 发布一个版本
```
对应 intent 未配置时先问清楚并写回同一文件,不猜测。运行测试环境时 ACK 加载
deployer skill,不复制 compose 命令。本地进程启动写在 `project.md`,不算这个
模式。intent 运行不要求当前有 `verified` 任务;`deliveryRuns.intent` 记录是
测试环境还是发版。
交付配置只允许声明式工具 target 或仓库内可执行脚本,不接受自由 shell,也不保存
凭据值。任务进入 `verified` 后的常规交付仍按确认过的 profile 执行。默认
profile 只能停在 `validation_ready` 或 `review_ready`;稳定发布或生产部署必须经过
对应 approval 步骤。具体契约见 `references/delivery.md`。
## 启动 Worker
worker 的机器配置位于 `tasks.yaml.project.orchestration`:项目显式维护模型
allowlist、结构化 profile、默认 profile 和允许的 worktree。不得在 `project.md`
或任务里保存完整启动命令、额外 argv 或环境变量。
先审阅计划,不产生终端:
```bash
python3 <ack-skill-dir>/scripts/launch_worker.py plan \
--project-root <project-root> \
--task-id <task-id> \
--attempt-id <task-id>-A<round> \
--role developer \
--profile-id codex-dev-standard \
--worktree <allowed-worktree>
```
确认后执行 `launch`,并显式绑定刚审阅的 fingerprint:
```bash
python3 <ack-skill-dir>/scripts/launch_worker.py launch \
--project-root <project-root> \
--task-id <task-id> \
--attempt-id <task-id>-A<round> \
--role developer \
--profile-id codex-dev-standard \
--worktree <allowed-worktree> \
--expected-launch-fingerprint <plan 中的 sha256:...>
```
launcher 是自动创建 worker 的唯一入口:它从权威任务板重读 profile,构造固定
argv,忽略调用者 PATH、使用环境 allowlist,并验证真实 Git worktree 注册;然后
通过仓库外的单次启动记录、terminal-bound nonce/proof 和受限 bootstrap 调用 Orca。
返回的 receipt 含 `runtimeId`、handle、incarnation、profile hash、slot 和 worktree
identity。Coordinator 将 receipt 追加到顶层 `workerReceipts`,再把 receipt ID
写入任务的 `dispatch.developer` 或 `dispatch.test`,并同步写入本轮 `attemptId`。
校验器要求 receipt 与当前 ACK task、角色、profile 和 attempt 完全一致;历史 receipt
不能跨任务或跨轮次改挂。
v0.10 自动 launcher 仅支持 `read-only` 与 `workspace-write`。full-access、
Codex bypass、Cursor YOLO/force、Grok `--yolo` / bypassPermissions 和关闭
sandbox 都会 fail closed;在有可信平台审批或独立签发通道之前,不用项目文件
伪装成用户授权。Grok worker 由 launcher 固定带 `--always-approve`,避免工具调用
停在确认框,sandbox 仍必须启用。v0.17 起 `cli: grok` 是一等 worker CLI;`cli: omp` 使用 OMP 的结构化模型、thinking 和 approval-mode 参数,workspace-write 默认 yolo。旧任务板没有结构化
`project.orchestration` 时仍可读取和手动协作,但不得自动创建 worker。
持久化 `receiptHash` 是无密钥 checksum,不是 launcher 身份证明。ACK 只复用同一轮
内明确空闲、角色/profile/worktree 匹配,并且可以可信清理历史消息、取得新会话身份的
worker;正在执行、等待回报、状态不明或关联未完成任务的 worker 不复用。由于当前
Orca 没有可验证的历史消息清理接口,Orca 派发仍重新 `plan` 并用 expected fingerprint
启动 fresh worker。
fingerprint 只校验完整计划没有漂移,不是一次性令牌;成功后不得用同一 fingerprint
重复启动,结果不确定时必须先 reconcile。
若创建或关闭回执不完整,或外部 launch record 状态无法可靠持久化,launcher 会返回
`indeterminate/reconcile-required`;必须先核对 record 与 Orca live state,不能
直接重试。
Coordinator 最后标记整轮任务完成后,会关闭所有只关联 `verified` 任务的 worker
终端。仍关联 `blocked`、`failed_retest`、`leftover`、其它未完成任务或未解决环境事件
的终端继续保留且不设置 TTL;receipt 与测试证据不会随终端删除。
## 开始一个需求
初始化完成后可以直接说:
```text
/ack 处理这个需求:<一句话需求>
```
Coordinator 会先读取项目状态和 `references/kickoff.md`,生成产品文档、任务拆分与
可观测验收信号;用户确认后才派发实现和复测。任务 `verified` 后会把本轮黑盒路径
收进 `.pouch/ack/regression.yaml`,确认后才写入。
## 修复 bug
可以直接说:
```text
/ack 修这个 bug:<现象、复现、期望>
```
Coordinator 写短问题说明、复现步骤和可观测验收,不写大 PRD;你确认后再派发。
Developer 先补会失败的用例再修,后续闭环与做需求相同。
配了飞书 `bugIntake` 时,飞书是审核前的唯一协作区。你只需维护标题、详细描述和
附件;Coordinator 整理问题说明、期望效果和验收标准并写回飞书。你针对当前
revision 审核通过,并亲自把状态改成「已确认」之前,不创建任务、不派 worker。
```text
/ack 处理飞书里待整理的 bug
```
## 运行回归
平时做需求和修 bug 结束后,ACK 按本轮内容更新 `.pouch/ack/regression.yaml`。
之后可以单独跑:
```text
/ack 回归 /ack 回归
/ack 跑 full 回归 /ack 跑 full 回归
/ack 回归 REG-login-001 /ack 回归 REG-login-001
/ack 修这些回归失败
``` ```
ACK 先按 deployer 布测试环境,再派独立 Test 按用例用浏览器或 API 执行。失败只 首次配发版也可以一次说清:
报告,不会自动开修;要修再说 `/ack 修这些回归失败`。默认跑 smoke。细则见
`references/regression.md`。
首次配置交付可以说:
```text ```text
/ack 更新项目交付配置:用 make build-deb 构建 DEB,发布到 preview APT 仓库, /ack 更新项目交付配置:用 make build-deb 构建 DEB,发布到 preview APT 仓库,
部署到 test-server 并跑健康检查;完成后创建 PR,停在 review_ready 给我审核。 部署到 test-server 并跑健康检查;完成后创建 PR,停在 review_ready 给我审核。
``` ```
之后处理需求时只需在确认计划中选择 profile: 之后做需求时只要在确认计划里选 profile:
```text ```text
/ack 处理这个需求:<一句话需求>。任务验证通过后执行 review profile。 /ack 处理这个需求:…。任务验证通过后执行 review profile。
``` ```
ACK 会自动读取 `delivery.yaml`,无需再逐步提醒它构建、上传、部署或开 PR;目标或 ## Agent 会做什么
权限发生漂移、缺少凭据、进入 stable/production approval 时才停下来请求决策。
1. 初始化或检查 `.pouch/ack/`,缺项按统一格式列出;不从 README/CI 猜测并启用交付或知识。
2. 做需求:先写产品文档、任务拆分和可观测验收;修 bug 只写短说明、复现和验收。你确认后再派 Developer 和独立 Test。
3. 需要测试环境时加载 deployer,不自己编 compose,也不把本地 `npm run dev` 当成测试环境。
4. 任务 `verified` 后提议把本轮黑盒路径写入回归目录,你确认后才写。
5. 发版读同一份 `delivery.yaml`。默认最多停在 `validation_ready` 或 `review_ready`;稳定发布或生产部署必须再经过对应批准。
6. 回归失败只报告,不会自动开修。
7. 未获授权不提交、不推送、不创建终端、不部署,也不改项目的 `AGENTS.md` / `CLAUDE.md`。
飞书收件时,你维护标题、详细描述和附件。Coordinator 整理问题说明、期望和验收并写回飞书。你针对当前 revision 审核通过,并亲自把状态改成「已确认」之前,不创建任务、不派 worker。
## 如何判断完成
- 初始化:五个状态文件齐,报告为完成,或列出待配置项和下一步。
- 需求 / bug:任务变为 `verified`,或三轮后记为 `leftover`;回归目录有本轮用例,或明确「无回归」。
- 测试环境:目标环境容器 Up,健康检查通过。
- 发版:停在约定档位;stable / 生产有单独批准记录。
- 回归:列出通过和失败的用例;失败不会被自动拿去修。
## 版本 ## 版本
当前 Skill 版本见 `VERSION`。新项目在 `tasks.yaml` 中以合法 SemVer 记录 当前 Skill 版本见 `VERSION`。新项目在 `tasks.yaml` 里记 `ackVersion`。
`ackVersion`。从 `0.10.0` 起,`project.orchestration` 与顶层 `workerReceipts` 必须
同时存在;从 `0.11.0` 起,新项目还会生成默认关闭的 `delivery.yaml`,并在任务板声明 从 0.19.0 起,测试环境写成 `{via: deployer, env: <env>}`,回归目录是 `.pouch/ack/regression.yaml`。旧的测试环境 profile ID 字符串不再执行,需要迁过去。旧项目可以不迁回归目录,继续用原来的闭环。旧的 `kitVersion` 仍可读,建议改成 `ackVersion`。
`project.deliveryFile` 与 `deliveryRuns`;从 `0.13.0` 起,Coordinator 使用
`select_tasks.py` 获取有预算的任务上下文,不再把完整任务板注入模型;从 `0.16.0` 起,
`delivery.yaml` 可用 `intents.testEnvironment` 与 `intents.release` 把测试环境部署和
版本发布写成用户可单独触发的操作;从 `0.17.0` 起,结构化 worker 路由支持
`cli: grok`(与 Codex、Cursor 并列);从 `0.18.0` 起支持 OMP 的
`cli: omp` profile(精确 provider/model、thinking 与 approval-mode);从 `0.17.1` 起 Grok worker argv 固定带
`--always-approve`,sandbox 仍必开;从 `0.19.0` 起 `intents.testEnvironment` 改为
deployer 绑定,ACK 内部调用 deployer skill 布测试环境,并增加
`.pouch/ack/regression.yaml` 与「运行回归」模式。旧的测试环境 profile ID 字符串不再
执行,需要迁到 `{via: deployer, env: <env>}`。旧项目可以不迁移回归目录而继续使用
原闭环。旧项目的 `kitVersion` 可以继续读取,但建议迁移为 `ackVersion`。
+33
View File
@@ -14,6 +14,23 @@
只构建不上传时明确说明即可;上传永远需要你显式授权。 只构建不上传时明确说明即可;上传永远需要你显式授权。
## 安装
```bash
pouch add builder # 当前项目
pouch add builder -g # 全局
```
不要 `pouch init builder`。对新项目说「用 builder 初始化」。
## 使用前准备
- Python 3 和 `make`。
- DEB 轨道需要 `dpkg-deb`;Docker 轨道需要 Docker 和 buildx。
- 发布时在项目根 `.env.builder` 填写仓库地址和 token(不要写进 `.env`,也不要进 git)。
- 正式版本号由 [manage-release](../manage-release/README.md) 打 annotated tag;builder 只读当前 HEAD。
- 脏工作树默认拒绝发布。
## 项目接入契约 ## 项目接入契约
对新项目说「用 builder 初始化」。Agent 会探测轨道、按 对新项目说「用 builder 初始化」。Agent 会探测轨道、按
@@ -98,6 +115,22 @@ detached HEAD 用 `detached`,CI 可注入 `BUILD_BRANCH` / `CI_COMMIT_BRANCH`
用 builder 发布多平台 linux/amd64,linux/arm64 镜像。 用 builder 发布多平台 linux/amd64,linux/arm64 镜像。
``` ```
## Agent 会做什么
1. 初始化:探测 DEB / Docker 轨道,写出 `makefile.builder` 和 `.env.builder` 骨架,不改你已有的 `Makefile` 或 `.env`。
2. 检查:只跑 `scripts/check.py --ready`,不改文件。
3. 构建:走 `make -f makefile.builder`;只获构建授权时不走会自动上传的目标。
4. 发布:先校验产物,再上传。缺发布键、未授权、或可能覆盖同版本时停下问你。
Docker 只有你点名镜像时才走。token 不会写进输出、提交或回复。
## 如何判断完成
- 初始化:`check.py --ready` 的构建项 PASS。只缺发布键算部分完成,仍可构建、不能上传。
- 构建:产物路径、规范版本、架构与 `make -f makefile.builder version` 一致。
- 发布:HTTP 成功或远端 digest 对得上;回复里有包名或镜像引用、SHA-256 / digest、源 commit。
- 测试包不会通过无参 `apt upgrade` 盖住正式包。
## 脚本一览 ## 脚本一览
| 脚本 | 用途 | | 脚本 | 用途 |
@@ -0,0 +1,55 @@
# declarative-openspec-loop
你提供「怎样算符合预期」的校验方式,Agent 按 OpenSpec 走 propose → apply → 校验,不通过就分析原因再迭代,直到通过。
## 什么时候使用
- 已经能说清目标,也有办法判断结果对不对,希望 Agent 自动做到通过为止。
- 会说「声明式编程」「按预期循环」「做到校验通过」。
- 不适合:还在选型、还没有校验手段、只想改一小处且自己盯着看。
## 安装
```bash
pouch add declarative-openspec-loop # 当前项目
pouch add declarative-openspec-loop -g # 全局
```
## 使用前准备
- 本机已安装 [openspec](https://openspec.dev/) CLI。
- 当前 Agent 已安装 OpenSpec 的三个子 skill:`openspec-explore`、`openspec-propose`、`openspec-apply-change`。
- 你必须给出校验方式,缺了 Agent 会先问,不会空跑。可用下面任意组合:
- 校验命令,退出码 0 即通过,比如 `make test-one2`
- 预期文档,里面有校验项表
- 清单:日志关键字、输出文件、数值容差
- 自由描述:Agent 会先归纳成可执行检查,向你确认后再跑
## 使用示例
```text
按声明式循环做:让登录失败时返回统一错误码。
校验:跑 pytest tests/test_login.py,全部 green 即通过。最多 3 轮。
```
```text
目标:拟合轮廓与 docs/tmp/expect_desc.md 一致。
校验:make test-one2,且日志覆盖文档「校验项」表全部 8 项。
```
## Agent 会做什么
1. 记下 change 名称和校验方式,写到 `docs/tmp/<change>/expectation.md`。
2. 必要时先探索;然后 propose 出 proposal / design / tasks,每个任务尽量带验收。
3. apply 实现,再跑你给的校验。
4. 失败按原因分级:小 bug 直接修;任务遗漏就补 tasks;方向错了才改设计。默认最多 5 轮(你可改上限)。同一错误连续 2 轮会停下来问你。
5. 通过后给出变更摘要和思路,过程记录留在 `docs/tmp/<change>/`。
不会在没校验方式时开跑,也不会到上限后自己放弃或无限重试。
## 如何判断完成
- 你指定的校验命令退出码 0,或校验项全部满足。
- Agent 给出变更文件列表和思路。
- `docs/tmp/<change>/` 里有 `expectation.md`、每轮 `iteration_*.md` 和校验原始输出。
- 到上限或反复失败时停下,并说明当前进度和未通过项,而不是假装完成。
+14 -4
View File
@@ -22,6 +22,15 @@
- 需要一张「哪台机器跑哪些服务」的清单 - 需要一张「哪台机器跑哪些服务」的清单
- 镜像要进 Kubernetes,走 Argo CD:改 GitOps、开 MR、合并后部署 - 镜像要进 Kubernetes,走 Argo CD:改 GitOps、开 MR、合并后部署
## 安装
```bash
pouch add deployer # 当前项目
pouch add deployer -g # 全局
```
不要 `pouch init deployer`。对新项目说「用 deployer 初始化」。
## 使用前准备 ## 使用前准备
- Compose:本机 Python 3、`rsync`、`ssh`;目标机器 Docker Compose v2;`~/.ssh/config` 免密(或 agent) - Compose:本机 Python 3、`rsync`、`ssh`;目标机器 Docker Compose v2;`~/.ssh/config` 免密(或 agent)
@@ -50,6 +59,7 @@ web1 能出网,直接让它从 https://... 把包拉下来装
# 项目内环境 # 项目内环境
给这个项目建好 .pouch/deployer,prod 和 test 分别放到两台机器上 给这个项目建好 .pouch/deployer,prod 和 test 分别放到两台机器上
把 test 环境重新部署一下 把 test 环境重新部署一下
本机测试镜像已经有了,直接重启,别 sync
prod 的 compose 加个 redis,改完发上去 prod 的 compose 加个 redis,改完发上去
# Argo CD # Argo CD
@@ -62,8 +72,8 @@ GitOps 我已经 clone 在 ../infra-gitops,用那个目录开 MR
1. 读服务/环境目录(及共享的父目录)的 `_config.yaml`,确定目标机器和远程路径; 1. 读服务/环境目录(及共享的父目录)的 `_config.yaml`,确定目标机器和远程路径;
项目内布局从 `.pouch/deployer/` 自动发现,无需额外配置 项目内布局从 `.pouch/deployer/` 自动发现,无需额外配置
2. 用 skill 自带脚本把本地目录同步到远程(rsync,自动排除 `data/`、`_data/`) 2. 目标是本机且镜像已在本机 docker:不 sync、不 SSH,在该环境目录直接 `docker compose`
3. 在远程执行对应的 `docker compose` 操作(启动 / 重建 / 升级 / 重启) 3. 否则用 skill 自带脚本把本地目录同步到远程(rsync,自动排除 `data/`、`_data/`),再远程 `docker compose`(启动 / 重建 / 升级 / 重启)
4. deb 包安装走独立脚本:scp 上传到暂存目录后远程 apt 安装,失败自动修依赖 4. deb 包安装走独立脚本:scp 上传到暂存目录后远程 apt 安装,失败自动修依赖
5. 同步后查看容器状态和日志确认生效 5. 同步后查看容器状态和日志确认生效
6. 只针对你指定的那一个服务操作,不会批量动整台机器 6. 只针对你指定的那一个服务操作,不会批量动整台机器
@@ -78,8 +88,8 @@ GitOps 我已经 clone 在 ../infra-gitops,用那个目录开 MR
## 如何判断完成 ## 如何判断完成
- 脚本输出显示同步完成、远程命令执行成功 - 本机:`docker compose ps` 容器 Up。远程:sync 成功且 `remote.py ps` 容器 Up
- `ps` 显示容器 Up、`logs` 无报错;升级后镜像 tag 与配置一致 - `logs` 无报错;升级后镜像 tag 与配置一致
- deb 安装后 `ssh <node> dpkg -l` 能看到目标包,服务能正常启动 - deb 安装后 `ssh <node> dpkg -l` 能看到目标包,服务能正常启动
- 域名/端口类服务能 curl 通 - 域名/端口类服务能 curl 通
- Argo CD:给出 MR 链接;合入后 Application Synced,域名/healthz 可访问 - Argo CD:给出 MR 链接;合入后 Application Synced,域名/healthz 可访问
+15 -4
View File
@@ -19,10 +19,10 @@ description: >-
- 初始化、接入 deployer,或给新项目建测试/生产环境:执行「初始化」。 - 初始化、接入 deployer,或给新项目建测试/生产环境:执行「初始化」。
- 检查 `.pouch/deployer`、node、compose 是否齐全:执行「检查」。 - 检查 `.pouch/deployer`、node、compose 是否齐全:执行「检查」。
- 部署、同步、升级、重启、装 deb、新增/下线服务:执行「Compose 操作」。发现不了服务或解析不出 node 时停止,转入「初始化」。不要静默初始化。 - 部署、重新部署、同步、升级、重启、布测试环境、装 deb、新增/下线服务:执行「Compose 操作」。发现不了服务或解析不出 node 时停止,转入「初始化」。不要静默初始化。
- ArgoCD / GitOps / 开 MR 部署:执行「Argo CD」。步骤见 [argocd.md](references/argocd.md)。 - ArgoCD / GitOps / 开 MR 部署:执行「Argo CD」。步骤见 [argocd.md](references/argocd.md)。
- 两者都有且意图不清:先问。 - 两者都有且意图不清:先问。
- 不适用:单机 docker、Nomad、常规 `kubectl apply`、构建并推送镜像(走 builder)。 - 不适用:没有 `.pouch/deployer/` 的随手 `docker compose`、Nomad、常规 `kubectl apply`、构建并推送镜像(走 builder)。本机 node 仍走本 skill,镜像已在本机则跳过 sync。
## 边界 ## 边界
@@ -66,11 +66,22 @@ python3 -I -S <skill-dir>/scripts/deploy/check.py --project <project-root>
## 被 ACK 调用 ## 被 ACK 调用
ACK 的「运行测试环境」和回归前布环境会加载本 skill,对 `.pouch/deployer/<env>`(通常是 `test`)按「Compose 操作」执行。ACK 只负责何时布、把访问地址写入 `deliveryRuns`;不要把本 skill 的脚本复制进 ACK。生产环境、Argo CD 合入和节点级批量仍须用户明确要求。 ACK 的「运行测试环境」和回归前布环境会加载本 skill,按「Compose 操作」的默认路径布测试环境。ACK 只负责何时布、把访问地址写入 `deliveryRuns`;不要把本 skill 的脚本复制进 ACK。生产环境、Argo CD 合入和节点级批量仍须用户明确要求。
## Compose 操作 ## Compose 操作
读 [compose.md](references/compose.md)。用 `list.py` 摸底,再按意图对**单个服务**执行 sync/up/recreate/upgrade/restart。解析 `_config.yaml` 见 [config-reference.md](references/config-reference.md)。sync 报错但 ssh 正常时,按 compose.md 的 NAS 兜底处理。每次操作后 `remote.py <svc> ps` / `logs` 验证。 重启 / 重新部署 / 同步 / 布测试环境走默认路径。不要读 compose.md、不要初始化、不要读 Makefile。
1. 在项目根执行:`python3 -I -S <skill-dir>/scripts/deploy/list.py`
2. 只操作**一个**服务:用户点名的 env;「测试」→ `test`,没有 `test` 就用非 `prod` 的那个。未点名 `prod` 不要动它。
3. 不要改 compose tag、不要构建镜像、不要看其它环境。
4. 本机镜像:node 的 ssh `HostName` 是 `127.0.0.1`/`localhost`,且 `docker image inspect` 看得到 compose 里的 tag → **不要** `sync.py` / `remote.py`。进 `.pouch/deployer/<env>/` 执行 `docker compose up -d --force-recreate`,再 `ps`。完。
5. 否则才 sync + remote:`python3 -I -S <skill-dir>/scripts/deploy/sync.py <svc>` 然后 `python3 -I -S <skill-dir>/scripts/deploy/remote.py <svc> recreate`。只要重启:`remote.py <svc> restart`。
6. 远程路径验证:`remote.py <svc> ps`;异常再 `logs`。完。
compose 用 `image:` 且用户说刚合并了代码:现有 tag 对不上这次提交时,**一句问**要不要先重建镜像,然后停。不要自己改 tag、不要走 builder。
新增/下线、deb、镜像 tag 升级、NAS 失败:再读 [compose.md](references/compose.md)。字段见 [config-reference.md](references/config-reference.md)。
## Argo CD ## Argo CD
+3 -1
View File
@@ -81,6 +81,8 @@ python3 <skill-dir>/scripts/deploy/sync.py <service-path>
| 意图 | 命令 | | 意图 | 命令 |
|------|------| |------|------|
| 本机已有镜像(node 是 localhost,image 已在本机 docker) | 不要 sync。在 env 目录 `docker compose up -d --force-recreate` |
| 重新部署远程环境 | `sync.py <svc> && remote.py <svc> recreate` |
| 只同步文件,不动容器 | `sync.py <svc>` | | 只同步文件,不动容器 | `sync.py <svc>` |
| 应用 compose/配置变更 | `sync.py <svc> && remote.py <svc> up` | | 应用 compose/配置变更 | `sync.py <svc> && remote.py <svc> up` |
| 改配置后强制重建 | `remote.py <svc> recreate`(配合前置 sync) | | 改配置后强制重建 | `remote.py <svc> recreate`(配合前置 sync) |
@@ -88,7 +90,7 @@ python3 <skill-dir>/scripts/deploy/sync.py <service-path>
| 仅重启,不同步文件 | `remote.py <svc> restart` | | 仅重启,不同步文件 | `remote.py <svc> restart` |
| 排查 | `remote.py <svc> ps` / `remote.py <svc> logs` | | 排查 | `remote.py <svc> ps` / `remote.py <svc> logs` |
若项目有 Makefile 封装,命令形如 `make deploy TGT=<服务路径>`。没有 Makefile 时直接调 python 脚本,不要新建封装层。 脚本一律 `python3 -I -S <skill-dir>/scripts/deploy/<name>.py`。有 `make deploy TGT=` 才用 Makefile;没有就直接调脚本,不要读 Makefile 去发明封装。
`sync.py` 使用 `rsync -avz --delete`;本机无 rsync 时 tar over SSH 兜底。运行时数据必须放在默认排除的 `data/`、`_data/`,或 compose 挂载的远程绝对路径,否则会被清掉。 `sync.py` 使用 `rsync -avz --delete`;本机无 rsync 时 tar over SSH 兜底。运行时数据必须放在默认排除的 `data/`、`_data/`,或 compose 挂载的远程绝对路径,否则会被清掉。
+5 -1
View File
@@ -31,7 +31,11 @@ import subprocess
import sys import sys
from pathlib import Path from pathlib import Path
from lib import ( _SCRIPT_DIR = Path(__file__).resolve().parent
if str(_SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(_SCRIPT_DIR))
from lib import ( # noqa: E402
project_root, project_root,
run_remote, run_remote,
scp_base_args, scp_base_args,
+6 -1
View File
@@ -6,8 +6,13 @@ from __future__ import annotations
import os import os
import sys import sys
from collections import defaultdict from collections import defaultdict
from pathlib import Path
from lib import discover_services, project_root _SCRIPT_DIR = Path(__file__).resolve().parent
if str(_SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(_SCRIPT_DIR))
from lib import discover_services, project_root # noqa: E402
def main() -> int: def main() -> int:
+6 -1
View File
@@ -20,8 +20,13 @@ import argparse
import os import os
import subprocess import subprocess
import sys import sys
from pathlib import Path
from lib import project_root, run_remote, service_info _SCRIPT_DIR = Path(__file__).resolve().parent
if str(_SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(_SCRIPT_DIR))
from lib import project_root, run_remote, service_info # noqa: E402
REMOTE_COMMANDS = { REMOTE_COMMANDS = {
"up": "docker compose up -d", "up": "docker compose up -d",
+6 -1
View File
@@ -16,8 +16,13 @@ import shlex
import shutil import shutil
import subprocess import subprocess
import sys import sys
from pathlib import Path
from lib import DEFAULT_BASE_PATH, project_root, rsync_ssh_args, service_info, ssh_cmd _SCRIPT_DIR = Path(__file__).resolve().parent
if str(_SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(_SCRIPT_DIR))
from lib import DEFAULT_BASE_PATH, project_root, rsync_ssh_args, service_info, ssh_cmd # noqa: E402
def sync_directory(info: dict) -> int: def sync_directory(info: dict) -> int:
+57
View File
@@ -0,0 +1,57 @@
# discussion-notes
边讨论边把可复用结论写进项目里的 Markdown 笔记。笔记是活的:理解变了就改,不另存聊天稿。
## 什么时候使用
- 要进入讨论模式、沉淀到文档、整理笔记、单开文档。
- 适合学习概念、做技术选型、把讨论变成可回顾的结论。
- 不适合:一次性问答、必须进 skill 记忆库的沉淀(用 `memory-distill`)、也不要用来当会议逐字稿。
说「结束讨论」或「不用记了」就退出;之后不再自动改笔记。
## 安装
```bash
pouch add discussion-notes # 当前项目
pouch add discussion-notes -g # 全局
```
## 使用前准备
- 指定笔记路径,或接受默认 `000inbox/<topic>.md`(按 Obsidian 收件箱习惯)。
- 有明确话题名更好;没有的话 Agent 会问一次。
- 笔记写在当前项目仓库内。默认不 git commit。
## 使用示例
```text
进入讨论模式,记到 000inbox/podman-vs-docker.md:公司 Debian VM 上容器运行时选 Podman 还是 Docker。
```
```text
讨论 Quadlet 是什么,单开一篇概念笔记,并链到刚才的选型文档。
```
```text
这轮不用记,先只回答。
```
```text
结束讨论。
```
## Agent 会做什么
1. 确认路径:你指定的文件,或默认 `000inbox/<topic>.md`。新话题新建,续聊改已有文件。
2. 按内容选结构:选型/决策,或概念/原理。模板见 [reference.md](reference.md)。
3. 每轮先用中文回答你,再更新笔记里的结论、对比、约束;纠正旧理解时直接改,不留错误版本。
4. 只写可复用知识,不把问答全文抄进文档,不创建 `.raw.md`。
5. 你说结束时,可选加一节「更新」:搞懂了、纠正了、待动手。
## 如何判断完成
- 约定路径上有一份正在维护的 `.md`,结构能看出结论而不是聊天记录。
- 本轮新结论已经写进对应章节;过时说法被改掉,而不是追加在文末。
- 没有出现 `xxx.raw.md`,也没有未经确认的 git commit。
- 退出讨论后,Agent 不再自动改那份笔记。
+8 -1
View File
@@ -8,6 +8,13 @@
- 已有 `.drawio` 原型,希望补齐跳转、异常态、权限或规则说明。 - 已有 `.drawio` 原型,希望补齐跳转、异常态、权限或规则说明。
- 需要一张能用于产品评审、开发对齐和测试设计的产品说明板。 - 需要一张能用于产品评审、开发对齐和测试设计的产品说明板。
## 安装
```bash
pouch add draw-prototype-flow # 当前项目
pouch add draw-prototype-flow -g # 全局
```
## 使用前准备 ## 使用前准备
最好提供产品目标、目标用户、核心任务和已有需求材料。缺少细节时,Agent 会先列出必要假设;只有关键范围无法判断时才会询问。 最好提供产品目标、目标用户、核心任务和已有需求材料。缺少细节时,Agent 会先列出必要假设;只有关键范围无法判断时才会询问。
@@ -34,4 +41,4 @@
## 如何判断完成 ## 如何判断完成
最终会得到可在 diagrams.net/Draw.io 打开的 `.drawio` 源文件。核心任务应能从入口走到结果,每个关键按钮有明确去向,异常状态有恢复动作,原型、动线和规格使用同一套页面与状态 ID。 最终会得到可在 diagrams.net/Draw.io 打开的 `.drawio` 源文件。你指定路径就用指定路径,没指定时 Agent 会在项目里给出文件名。核心任务应能从入口走到结果,每个关键按钮有明确去向,异常状态有恢复动作,原型、动线和规格使用同一套页面与状态 ID。
+7
View File
@@ -4,6 +4,13 @@
版本文件、worktree、分支、PR/MR 和合并。普通发布优先走“更新说明 + annotated tag”的 版本文件、worktree、分支、PR/MR 和合并。普通发布优先走“更新说明 + annotated tag”的
短路径;只有发布要求修改仓库内容时,才进入准备版本的分支流程。 短路径;只有发布要求修改仓库内容时,才进入准备版本的分支流程。
## 安装
```bash
pouch add manage-release # 当前项目
pouch add manage-release -g # 全局
```
## 使用前准备 ## 使用前准备
- 项目已经是 Git 仓库,并配置了可访问的远端。 - 项目已经是 Git 仓库,并配置了可访问的远端。
+7
View File
@@ -12,6 +12,13 @@ memory-distill 提炼一下刚刚 ack 执行过程中可以沉淀的知识。
也会响应「沉淀记忆」「回顾刚才记下来」「更新记忆」等说法。 也会响应「沉淀记忆」「回顾刚才记下来」「更新记忆」等说法。
## 安装
```bash
pouch add memory-distill # 当前项目
pouch add memory-distill -g # 全局
```
## 使用前准备 ## 使用前准备
通常不用准备路径。Agent 会: 通常不用准备路径。Agent 会:
+9
View File
@@ -11,6 +11,15 @@ ORC 是显式调用的薄路由器:只把开发、版本发布、DEB 和 Docke
只做单一领域任务时可以直接调用对应 Skill;ORC 不替代它们的安全规则。 只做单一领域任务时可以直接调用对应 Skill;ORC 不替代它们的安全规则。
## 安装
```bash
pouch add orc # 当前项目
pouch add orc -g # 全局
```
`$orc` / `/orc` 只在显式调用时运行。下游阶段还需要已安装 `$ack`、`$manage-release` 或 `$builder`。
## 使用前准备 ## 使用前准备
- Orca 正在运行并启用了 orchestration。 - Orca 正在运行并启用了 orchestration。
+56 -16
View File
@@ -1,7 +1,6 @@
# pouch # pouch
`pouch` 用于创建、维护、安装和发布团队自研的 Agent Skill。Skill 的唯一来源位于 创建、维护、安装和发布团队自研的 Agent Skill。唯一来源在 `~/.pouch/skills/<name>/`,装到各 Agent 时用软链。
`~/.pouch/skills/<name>/`,安装到各 Agent 时使用软链接。
## 什么时候使用 ## 什么时候使用
@@ -11,6 +10,25 @@
- 想检查并修复 skill 软链接。 - 想检查并修复 skill 软链接。
- 想优化某个已有 skill 的结构,减少点中后灌进上下文的内容。 - 想优化某个已有 skill 的结构,减少点中后灌进上下文的内容。
## 使用前准备
- 已把本仓库 clone 到 `~/.pouch` 并跑过 `./install.sh`(会把本 skill 装到所有 Agent)。
- Python 3;`~/.local/bin` 已加入 `PATH`。
- 至少一种 Agent:Cursor、Claude Code、Codex,或 agents 标准目录。
- 要推到远端时,本仓库的 git remote 可写。
CLI 命令细节见仓库里的 [pouch/README.md](../../pouch/README.md)。
## 使用示例
```text
帮我把「评审 PR 时按这份清单走」做成一个自研 skill。
pouch 帮我检查 discussion-notes 这个 skill。
pouch 帮忙优化 ack 这个 skill 结构,减少 token 浪费。
把 ack 装到当前项目的 Cursor。
检查一下 skill 软链,坏了就修。
```
## 创建一个 skill ## 创建一个 skill
```bash ```bash
@@ -32,6 +50,8 @@ pouch check my-skill
pouch finalize my-skill pouch finalize my-skill
``` ```
转正不会自动 commit、push 或安装。
## 优化已有 skill 的结构 ## 优化已有 skill 的结构
skill 能用但正文太长、所有模式写在一份 `SKILL.md` 里,或点中后把用不到的参考一并读进上下文时: skill 能用但正文太长、所有模式写在一份 `SKILL.md` 里,或点中后把用不到的参考一并读进上下文时:
@@ -40,42 +60,62 @@ skill 能用但正文太长、所有模式写在一份 `SKILL.md` 里,或点
pouch 帮忙优化 ack 这个 skill 结构,减少 token 浪费 pouch 帮忙优化 ack 这个 skill 结构,减少 token 浪费
``` ```
Agent 会先测量该 skill 的体积,再按分层加载改 SSOT:`SKILL.md` 只留路由和全模式边界,细节按条件读 `references/`。不会为了缩字删掉发版/部署一类的安全限制。改完仍需 `pouch check` 通过。 Agent 会先测量该 skill 的体积,再按分层加载改 SSOT:`SKILL.md` 只留路由和全模式边界,细节按条件读 `references/`。不会为了缩字删掉发版、部署一类的安全限制。改完仍需 `pouch check` 通过。
不要对社区 catalog skill 的安装目录直接改;那些不是本仓库的 SSOT。 不要对社区 catalog skill 的安装目录直接改;那些不是本仓库的 SSOT。
## 提交和发布 ## 提交和发布
只提交:
```bash ```bash
pouch publish skills/my-skill -m "add my-skill" pouch publish skills/my-skill -m "add my-skill"
```
提交并推送:
```bash
pouch publish skills/my-skill -m "add my-skill" --push pouch publish skills/my-skill -m "add my-skill" --push
``` ```
## 安装 ## 安装与维护
安装到当前项目的 Codex: `./install.sh` 会执行 `pouch bootstrap`,把本 skill 全局装到所有 Agent。也可以随时重跑:
```bash ```bash
pouch add my-skill -a codex -y pouch bootstrap
``` ```
安装到全局 Codex: 安装其它 skill:
```bash ```bash
pouch add my-skill -a codex -g -y pouch add my-skill -a codex -y # 当前项目的 Codex
pouch add my-skill -a codex -g -y # 全局 Codex
pouch select # 交互多选,默认当前项目
pouch select -g # 交互多选,装到全局
``` ```
项目状态初始化(目前用于 ack):
```bash
pouch init ack
pouch init ack --project ~/app
```
builder / deployer 不要 `pouch init`,对 Agent 说「用 builder / deployer 初始化」。
软链出问题:
```bash
pouch status
pouch doctor
pouch doctor --fix
```
## Agent 会做什么
1. 创建草稿到 `~/.pouch/.drafts/`,按你的想法完善 `SKILL.md` 和给人看的 `README.md`。
2. 跑 `pouch check`;你确认后再 `finalize` 到 `skills/`。
3. 安装只建指向 SSOT 的软链,不复制文件。默认装当前项目;只有你明确要求时才加 `-g`。
4. 优化结构时先测量再改 SSOT,不删发版、部署类安全限制。
5. 未要求时不 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` 显示目标软链接正常。
- 优化结构时:审计报告有前后体积,安全限制仍在 `SKILL.md`,没有为缩字删掉模式。 - 优化结构时:审计报告有前后体积,安全限制仍在 `SKILL.md`,没有为缩字删掉模式。
+43 -2
View File
@@ -2,6 +2,8 @@ from __future__ import annotations
import importlib.util import importlib.util
import os import os
import subprocess
import sys
import tempfile import tempfile
import unittest import unittest
from io import StringIO from io import StringIO
@@ -10,8 +12,10 @@ from unittest import mock
REPO_ROOT = Path(__file__).resolve().parents[1] REPO_ROOT = Path(__file__).resolve().parents[1]
CHECK_PY = REPO_ROOT / "skills" / "deployer" / "scripts" / "deploy" / "check.py" DEPLOY_SCRIPTS = REPO_ROOT / "skills" / "deployer" / "scripts" / "deploy"
LIB_PY = REPO_ROOT / "skills" / "deployer" / "scripts" / "deploy" / "lib.py" CHECK_PY = DEPLOY_SCRIPTS / "check.py"
LIST_PY = DEPLOY_SCRIPTS / "list.py"
LIB_PY = DEPLOY_SCRIPTS / "lib.py"
lib_spec = importlib.util.spec_from_file_location("deployer_lib", LIB_PY) lib_spec = importlib.util.spec_from_file_location("deployer_lib", LIB_PY)
assert lib_spec is not None and lib_spec.loader is not None assert lib_spec is not None and lib_spec.loader is not None
@@ -55,6 +59,43 @@ class DeployerCheckTests(unittest.TestCase):
self.assertIn("scripts/deploy/check.py", skill) self.assertIn("scripts/deploy/check.py", skill)
self.assertIn("不要写假 node", skill) self.assertIn("不要写假 node", skill)
def test_skill_documents_redeploy_happy_path(self) -> None:
skill = (REPO_ROOT / "skills" / "deployer" / "SKILL.md").read_text(
encoding="utf-8"
)
compose_section = skill.split("## Compose 操作", 1)[1].split("## Argo CD", 1)[0]
self.assertIn("不要读 compose.md", compose_section)
self.assertIn("remote.py <svc> recreate", compose_section)
self.assertIn("一句问", compose_section)
self.assertIn("没有 `test` 就用非 `prod` 的那个", compose_section)
self.assertIn("不要** `sync.py` / `remote.py`", compose_section)
self.assertIn("docker compose up -d --force-recreate", compose_section)
self.assertNotIn(
"读 [compose.md](references/compose.md)。用 `list.py` 摸底",
compose_section,
)
def test_list_py_isolated_mode_imports_lib(self) -> None:
with tempfile.TemporaryDirectory() as temp:
project = Path(temp)
root = project / ".pouch" / "deployer"
env = root / "test"
env.mkdir(parents=True)
(root / "_config.yaml").write_text("node: my-vps\n", encoding="utf-8")
(env / "compose.yaml").write_text(
"services:\n web:\n image: nginx\n", encoding="utf-8"
)
proc = subprocess.run(
[sys.executable, "-I", "-S", str(LIST_PY)],
cwd=str(project),
capture_output=True,
text=True,
check=False,
)
self.assertEqual(proc.returncode, 0, proc.stderr + proc.stdout)
self.assertIn("test", proc.stdout)
self.assertNotIn("No module named 'lib'", proc.stderr)
def test_missing_layout_fails(self) -> None: def test_missing_layout_fails(self) -> None:
with tempfile.TemporaryDirectory() as temp: with tempfile.TemporaryDirectory() as temp:
project = Path(temp) project = Path(temp)