Compare commits
3 Commits
262c138bee
...
076d87e303
| Author | SHA1 | Date | |
|---|---|---|---|
| 076d87e303 | |||
| e308d6a04c | |||
| e667f58bf1 |
@@ -34,21 +34,20 @@ skiff status
|
|||||||
```
|
```
|
||||||
skills/
|
skills/
|
||||||
├── _template/ # 新建 skill 的脚手架
|
├── _template/ # 新建 skill 的脚手架
|
||||||
|
├── ack/ # 完整 ACK skill,含 references/templates/scripts
|
||||||
├── declarative-openspec-loop/ # 自研 skill,必须含 SKILL.md
|
├── declarative-openspec-loop/ # 自研 skill,必须含 SKILL.md
|
||||||
│ ├── SKILL.md
|
│ ├── SKILL.md
|
||||||
│ └── reference.md
|
│ └── reference.md
|
||||||
├── discussion-notes/ # 讨论沉淀笔记
|
├── discussion-notes/ # 讨论沉淀笔记
|
||||||
│ ├── SKILL.md
|
│ ├── SKILL.md
|
||||||
│ └── reference.md
|
│ └── reference.md
|
||||||
kits/
|
|
||||||
└── agent-collaboration-kit/ # 可复制到项目的协作规范包,不是 skill
|
|
||||||
skiff/ # CLI 源码(Python 3)
|
skiff/ # CLI 源码(Python 3)
|
||||||
bin/skiff # CLI 入口
|
bin/skiff # CLI 入口
|
||||||
registry.yaml # 外部 Git skill 来源目录
|
registry.yaml # 外部 Git skill 来源目录
|
||||||
AGENTS.md # 本文档
|
AGENTS.md # 本文档
|
||||||
```
|
```
|
||||||
|
|
||||||
**本仓库包含**:`skills/`、`kits/`、`skiff/`、`bin/skiff`、`registry.yaml`、`AGENTS.md`
|
**本仓库包含**:`skills/`、`skiff/`、`bin/skiff`、`registry.yaml`、`AGENTS.md`
|
||||||
**本仓库不包含**:各项目的 skill 启用清单
|
**本仓库不包含**:各项目的 skill 启用清单
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -68,20 +67,13 @@ AGENTS.md # 本文档
|
|||||||
|
|
||||||
新建 skill:复制 `skills/_template/` → `skills/<name>/`,编辑 `SKILL.md`,在本仓库 commit。
|
新建 skill:复制 `skills/_template/` → `skills/<name>/`,编辑 `SKILL.md`,在本仓库 commit。
|
||||||
|
|
||||||
### 规范包(Kits,不是 Skill)
|
Skill 需要的稳定规范、模板、示例和脚本直接放在自己的目录中,例如
|
||||||
|
`skills/ack/references/`、`templates/`、`examples/` 和 `scripts/`。项目初始化只生成
|
||||||
|
项目状态,不复制或链接 Skill 内容:
|
||||||
|
|
||||||
`kits/` 放可复制到项目的流程规范、模板、检查清单和示例资料。它们不是 Agent Skill:
|
```bash
|
||||||
|
skiff init ack
|
||||||
- 不放 `SKILL.md` frontmatter
|
```
|
||||||
- 不由 `skiff install` / `skiff add` 安装
|
|
||||||
- 不会被 Agent 自动触发加载
|
|
||||||
- 使用方式是复制到目标项目文档目录,或在目标项目 `AGENTS.md` 中引用
|
|
||||||
|
|
||||||
| Kit | 说明 | 推荐落地位置 |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| [ack](kits/ack/README.md) | 多 Agent 协作闭环规范包 | `docs/ack/` |
|
|
||||||
|
|
||||||
新建规范包:创建 `kits/<name>/README.md`,写清适用场景、复制到项目后的推荐目录、需要项目填充的占位符,并在本仓库 commit。已有 kit 可通过 `skiff kit init <name>` 初始化到项目。
|
|
||||||
|
|
||||||
### 外部(External Git)
|
### 外部(External Git)
|
||||||
|
|
||||||
@@ -136,8 +128,8 @@ npx skills find typescript
|
|||||||
1. **SSOT** — 自研 skill 只存在于 `skills/<name>/`,不在 Agent 目录直接创建
|
1. **SSOT** — 自研 skill 只存在于 `skills/<name>/`,不在 Agent 目录直接创建
|
||||||
2. **项目自治** — 每个项目自己维护 `.skills.yaml`,本仓库不维护项目清单
|
2. **项目自治** — 每个项目自己维护 `.skills.yaml`,本仓库不维护项目清单
|
||||||
3. **软链优先** — 通过 symlink 映射到 Agent 目录,改 skill 即改 SSOT
|
3. **软链优先** — 通过 symlink 映射到 Agent 目录,改 skill 即改 SSOT
|
||||||
4. **类型分离** — `skills/` 只放可安装 skill;非 skill 的规范包、模板包放 `kits/`
|
4. **能力内聚** — Skill 使用的规范、模板和脚本与 `SKILL.md` 同目录维护
|
||||||
5. **一体维护** — skill、规范包与 CLI 同仓库维护;需要时再与独立 skiff 仓库同步
|
5. **一体维护** — skill 与 CLI 同仓库维护;需要时再与独立 skiff 仓库同步
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -146,7 +138,6 @@ npx skills find typescript
|
|||||||
```
|
```
|
||||||
skills 仓库(本仓库) skiff CLI
|
skills 仓库(本仓库) skiff CLI
|
||||||
skills/<name>/ ←── skiff install / enable
|
skills/<name>/ ←── skiff install / enable
|
||||||
kits/<name>/ ←── 手动复制 / 项目文档引用
|
|
||||||
skiff/ ←── python3 -m skiff
|
skiff/ ←── python3 -m skiff
|
||||||
registry.yaml ←── skiff add / fetch
|
registry.yaml ←── skiff add / fetch
|
||||||
↑
|
↑
|
||||||
@@ -173,7 +164,6 @@ registry.yaml ←── skiff add / fetch
|
|||||||
|
|
||||||
| 类型 | 位置 | 维护方式 |
|
| 类型 | 位置 | 维护方式 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| **Kit** | `kits/<name>/` | 本仓库 commit;复制到目标项目 `docs/` 或由项目 `AGENTS.md` 引用 |
|
|
||||||
| **OpenSpec 配置** | `openspec/` | 本仓库 commit;服务于本仓库自身的规格流程 |
|
| **OpenSpec 配置** | `openspec/` | 本仓库 commit;服务于本仓库自身的规格流程 |
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ skiff status
|
|||||||
|
|
||||||
```
|
```
|
||||||
skills/ # 自研 skill(SSOT):每个子目录必须有 SKILL.md
|
skills/ # 自研 skill(SSOT):每个子目录必须有 SKILL.md
|
||||||
kits/ # 可复制到项目的规范包 / 模板包,不是 Agent Skill
|
|
||||||
skiff/ # CLI 源码(Python 3)
|
skiff/ # CLI 源码(Python 3)
|
||||||
bin/skiff # CLI 入口
|
bin/skiff # CLI 入口
|
||||||
registry.yaml # 外部 Git skill 目录
|
registry.yaml # 外部 Git skill 目录
|
||||||
@@ -28,8 +27,7 @@ AGENTS.md # 详细规范与架构说明
|
|||||||
|
|
||||||
| 路径 | 说明 |
|
| 路径 | 说明 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| [skills/](skills/) | 自研 skill,每个子目录含 `SKILL.md` |
|
| [skills/](skills/) | 自研 skill,每个子目录含 `SKILL.md`,可附带 references、templates 和 scripts |
|
||||||
| [kits/](kits/) | 项目规范包、协作模板、可复制资料,不由 skiff 安装 |
|
|
||||||
| [skiff/](skiff/README.md) | 安装、软链、健康检查 CLI |
|
| [skiff/](skiff/README.md) | 安装、软链、健康检查 CLI |
|
||||||
| [registry.yaml](registry.yaml) | 外部 Git skill 注册表 |
|
| [registry.yaml](registry.yaml) | 外部 Git skill 注册表 |
|
||||||
| [AGENTS.md](AGENTS.md) | 设计原则、编写规范、架构详解 |
|
| [AGENTS.md](AGENTS.md) | 设计原则、编写规范、架构详解 |
|
||||||
@@ -43,18 +41,11 @@ AGENTS.md # 详细规范与架构说明
|
|||||||
| [declarative-openspec-loop](skills/declarative-openspec-loop/SKILL.md) | 声明式编程循环:用户提供校验方式,Agent 自动迭代直到通过 |
|
| [declarative-openspec-loop](skills/declarative-openspec-loop/SKILL.md) | 声明式编程循环:用户提供校验方式,Agent 自动迭代直到通过 |
|
||||||
| [discussion-notes](skills/discussion-notes/SKILL.md) | 讨论沉淀:边讨论边维护 Markdown 笔记 |
|
| [discussion-notes](skills/discussion-notes/SKILL.md) | 讨论沉淀:边讨论边维护 Markdown 笔记 |
|
||||||
|
|
||||||
## 规范包(不是 Skill)
|
ACK 是包含规范、模板与校验脚本的完整 Skill。安装 Skill 后可初始化当前项目状态:
|
||||||
|
|
||||||
`kits/` 用来维护可复制到项目里的规范说明、模板和检查清单。它们没有 `SKILL.md`,不会被 `skiff install`、`skiff add` 或 Agent 自动加载。
|
|
||||||
|
|
||||||
| Kit | 说明 |
|
|
||||||
|-----|------|
|
|
||||||
| [ack](kits/ack/README.md) | 多 Agent 协作闭环规范包,通过 `skiff kit init ack` 初始化到项目 `docs/ack/` |
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
skiff kit init ack # 当前项目,软链接到 SSOT(推荐)
|
skiff init ack
|
||||||
skiff kit init ack --project ~/app # 指定项目
|
skiff init ack --project ~/app
|
||||||
skiff kit init ack --copy # 整份复制,后续需手动升级
|
|
||||||
```
|
```
|
||||||
|
|
||||||
新建 skill:
|
新建 skill:
|
||||||
@@ -122,7 +113,6 @@ npx skills find typescript
|
|||||||
```
|
```
|
||||||
本仓库
|
本仓库
|
||||||
├── skills/<name>/ ←── skiff install / enable
|
├── skills/<name>/ ←── skiff install / enable
|
||||||
├── kits/<name>/ ←── 手动复制 / 项目文档引用
|
|
||||||
├── registry.yaml ←── skiff add / fetch
|
├── registry.yaml ←── skiff add / fetch
|
||||||
└── skiff/ ←── python3 -m skiff
|
└── skiff/ ←── python3 -m skiff
|
||||||
↑
|
↑
|
||||||
@@ -140,7 +130,7 @@ npx skills find typescript
|
|||||||
1. **SSOT** — 自研 skill 只存在于 `skills/<name>/`
|
1. **SSOT** — 自研 skill 只存在于 `skills/<name>/`
|
||||||
2. **项目自治** — 各项目自行维护 `.skills.yaml`
|
2. **项目自治** — 各项目自行维护 `.skills.yaml`
|
||||||
3. **软链优先** — 通过 symlink 映射到 Agent 目录,改 skill 即改 SSOT
|
3. **软链优先** — 通过 symlink 映射到 Agent 目录,改 skill 即改 SSOT
|
||||||
4. **规范包分离** — 非 skill 的流程规范、模板包放在 `kits/<name>/`,不混入 `skills/`
|
4. **能力内聚** — Skill 所需规范、模板和脚本与 `SKILL.md` 放在同一目录
|
||||||
5. **一体维护** — skill 与 CLI 同仓库,Python 3 直接运行,无需编译
|
5. **一体维护** — skill 与 CLI 同仓库,Python 3 直接运行,无需编译
|
||||||
|
|
||||||
## 文档
|
## 文档
|
||||||
|
|||||||
@@ -1,175 +0,0 @@
|
|||||||
# ack — Agent Collaboration Kit
|
|
||||||
|
|
||||||
`ack`(Agent Collaboration Kit)是一套可复用到其它项目的多 Agent 协作规范,默认三个独立角色:**Coordinator (PM) 拆解需求并调度闭环,Developer 实现与白盒验证,Test 独立黑盒复测**。关键属性是验证者 ≠ 实现者。角色默认按成本分层:Coordinator 用强模型(且不亲自跑测试),Test/Developer 用中低模型(见 `core/model-routing.md`)。
|
|
||||||
|
|
||||||
当前版本见 `VERSION`。这不是 Agent Skill(无 `SKILL.md`),不由 skiff 安装,而是复制/引用到目标项目。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 适用场景
|
|
||||||
|
|
||||||
- 多个 Agent 分工协作,而非单个 Agent 从需求写到代码。
|
|
||||||
- 需要区分规格、测试、实现、复测的责任边界(Coordinator / Test / Developer 三角色)。
|
|
||||||
- 需要把失败项派发给 Developer,`worker_done` 后由独立的 Test 复测、Coordinator 终检。
|
|
||||||
- 需要连续修复多个问题,并把三轮仍未修好的问题留档。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 目录结构
|
|
||||||
|
|
||||||
```text
|
|
||||||
ack/
|
|
||||||
VERSION # kit 版本,接入时记入项目
|
|
||||||
README.md
|
|
||||||
init-new-project.md # 给 Agent 的新项目初始化手册
|
|
||||||
adoption-checklist.md
|
|
||||||
core/ # 稳定核心:跨项目通用,随 kit 升级,尽量别改
|
|
||||||
kickoff.md # 如何开始一个需求(启动手册 + 开场指令模板)
|
|
||||||
roles-and-permissions.md # 角色/权限/状态机/完成定义(SSOT)
|
|
||||||
model-routing.md # 三角色默认模型档位 + 升级规则(SSOT)
|
|
||||||
closed-loop.md # 编排无关闭环 + 手动模式 + worktree 对齐
|
|
||||||
optimization-method.md # 验收信号 + 三轮失败策略(SSOT)
|
|
||||||
prompt-templates.md # 派发 prompt 模板
|
|
||||||
orca-adapter.md # Orca 具体命令(一种编排实现,可选)
|
|
||||||
templates/ # 项目覆盖层:复制一次并填空
|
|
||||||
project.template.md # 项目覆盖层模板(文件名可配置,默认不占用 AGENTS.md)
|
|
||||||
tasks.template.yaml
|
|
||||||
tasks.schema.json # 任务板权威结构(跨语言)
|
|
||||||
examples/ # 填好的最小可跑示例
|
|
||||||
project.example.md
|
|
||||||
tasks.example.yaml
|
|
||||||
scripts/
|
|
||||||
validate_tasks.py # 校验 tasks.yaml(参考实现)
|
|
||||||
```
|
|
||||||
|
|
||||||
**核心原则**:`core/` 是稳定核心,每个概念只定义一次;`templates/` 是项目覆盖层。二者分离,让 kit 升级和项目定制互不干扰。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 项目目录布局(推荐)
|
|
||||||
|
|
||||||
每个项目在 `docs/ack/` 下只放**一个软链接**加两个项目文件,稳定核心全部走软链接:
|
|
||||||
|
|
||||||
```text
|
|
||||||
<project>/docs/ack/
|
|
||||||
kit -> <此框架目录> # 唯一软链接:core/templates/examples/scripts 全在里面
|
|
||||||
project.md # 项目覆盖层(差异),可改名,见下
|
|
||||||
tasks.yaml # 项目任务板
|
|
||||||
```
|
|
||||||
|
|
||||||
引用稳定核心时统一走 `kit/` 前缀,例如 `docs/ack/kit/core/roles-and-permissions.md`。
|
|
||||||
这样「一个项目一个软链接」,kit 升级自动生效,项目定制(`project.md` + `tasks.yaml`)互不干扰。
|
|
||||||
|
|
||||||
## 分发方式(二选一)
|
|
||||||
|
|
||||||
### 方式 A:单软链接引用(推荐,可升级)
|
|
||||||
|
|
||||||
把本框架目录整体 symlink 成项目的 `docs/ack/kit`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p <project>/docs/ack
|
|
||||||
ln -s <此框架绝对路径> <project>/docs/ack/kit
|
|
||||||
```
|
|
||||||
|
|
||||||
也可用 git submodule / sparse checkout 达到同样效果。项目只维护 `project.md` 与 `tasks.yaml`。
|
|
||||||
|
|
||||||
### 方式 B:整份复制(简单,手动升级)
|
|
||||||
|
|
||||||
把本框架整目录复制成 `docs/ack/kit/`(或直接铺平到 `docs/ack/`,此时引用去掉 `kit/` 前缀)。**务必**在 `project.md` 与 `tasks.yaml` 的 `kitVersion` 记录来源版本,日后照 `VERSION` diff 升级。
|
|
||||||
|
|
||||||
### 覆盖层文件名(重要)
|
|
||||||
|
|
||||||
项目差异(路径、命令、handle、模型档位)写在**项目覆盖层文件**里,**默认 `docs/ack/project.md`,不占用 `AGENTS.md`**,以免与团队已有的 `AGENTS.md` 约定冲突。文件名可配置:想让 Agent 自动加载,可在 `AGENTS.md` 加一行指向它,或直接命名为 `AGENTS.md`。无论叫什么,都在 `tasks.yaml` 的 `project.overlayFile` 记录实际路径。覆盖层只填差异,不复制 `core/` 内容。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 快速接入
|
|
||||||
|
|
||||||
让 Agent 在新项目执行初始化时,优先给它 `init-new-project.md`。
|
|
||||||
|
|
||||||
1. 建 `docs/ack/`,按方式 A 建单软链接 `kit`。
|
|
||||||
2. 复制 `kit/templates/project.template.md` → `docs/ack/project.md`,填项目差异与模型档位,记 `kitVersion`。
|
|
||||||
3. 复制 `kit/templates/tasks.template.yaml` → `docs/ack/tasks.yaml`,填 `project`(含 `overlayFile`)与首个任务。
|
|
||||||
4. 跑 `python3 docs/ack/kit/scripts/validate_tasks.py docs/ack/tasks.yaml` 确认结构。
|
|
||||||
5. 按 `kit/core/closed-loop.md` 跑闭环(Orca 见 `kit/core/orca-adapter.md`,无 Orca 用手动模式)。
|
|
||||||
6. 逐项对照 `kit/adoption-checklist.md`。
|
|
||||||
|
|
||||||
参考 `examples/` 里填好的 `project.example.md` 与 `tasks.example.yaml`。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 常用使用方法
|
|
||||||
|
|
||||||
### 开一个新需求(最常用)
|
|
||||||
|
|
||||||
你(强模型会话)就是 Coordinator(PM)。完整手册见 `core/kickoff.md`,开场指令:
|
|
||||||
|
|
||||||
```text
|
|
||||||
我要做一个新需求:<一句话需求>。你作为 ack 的 Coordinator(PM),按 docs/ack/kit/core 规范:
|
|
||||||
先读 project.md 与 core/*;写产品文档到 docs/ 并把需求拆成带可观测验收信号的任务写进 docs/ack/tasks.yaml;
|
|
||||||
先给我确认产品文档+任务拆分,再按 project.md 优先复用空闲 DEV/TEST worker,没有时才校验并创建,核对实际模型后循环派发/复测/终检;
|
|
||||||
每个任务最多三轮,三轮不过记 leftover。
|
|
||||||
```
|
|
||||||
|
|
||||||
### 复用或启动 worker
|
|
||||||
|
|
||||||
先按 `core/orca-adapter.md` §「解析并复用 worker」运行 `terminal list` 和活跃任务查询。只有没有同 worktree、同角色、配置兼容的空闲 worker 时,才执行下面的创建命令:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
DEV_CMD='codex --dangerously-bypass-approvals-and-sandbox -m gpt-5.6-terra -c model_reasoning_effort=medium'
|
|
||||||
TEST_CMD='codex --dangerously-bypass-approvals-and-sandbox -m gpt-5.6-luna -c model_reasoning_effort=low'
|
|
||||||
python3 docs/ack/validate_worker_command.py --role developer --command "$DEV_CMD"
|
|
||||||
python3 docs/ack/validate_worker_command.py --role test --command "$TEST_CMD"
|
|
||||||
orca terminal create --worktree active --command "$DEV_CMD" --title "ACK-DEV-CODEX-TERRA-1" --json
|
|
||||||
orca terminal create --worktree active --command "$TEST_CMD" --title "ACK-TEST-CODEX-LUNA-1" --json
|
|
||||||
|
|
||||||
# Cursor worker 使用同一项目校验器
|
|
||||||
CURSOR_CMD='cursor-agent --yolo --model auto'
|
|
||||||
python3 docs/ack/validate_worker_command.py --role developer --command "$CURSOR_CMD"
|
|
||||||
```
|
|
||||||
|
|
||||||
派发成功后把实际 handle 写入任务的 `dispatch.worker`,下次优先复用。需要隔离/并行时先建 worktree:`orca worktree create --name <feature> --base-branch <base> --json`(选择依据见 `core/closed-loop.md`)。
|
|
||||||
|
|
||||||
### 派发与等待(Orca)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
orca orchestration task-create --spec "<任务与验收>" --json
|
|
||||||
orca orchestration dispatch --task <task_id> --to <dev_handle> --json
|
|
||||||
orca orchestration check --terminal <coordinator_handle> --wait \
|
|
||||||
--types worker_done,retest_result,escalation,decision_gate --timeout-ms 900000 --json
|
|
||||||
```
|
|
||||||
|
|
||||||
不能 `--inject` 时手动投递 `core/prompt-templates.md` 的模板;命令细节见 `core/orca-adapter.md`。
|
|
||||||
|
|
||||||
### 校验任务板结构
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 docs/ack/kit/scripts/validate_tasks.py docs/ack/tasks.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
### 只处理已有未通过项
|
|
||||||
|
|
||||||
用「默认口令」(见下)让 Coordinator 直接扫 `tasks.yaml` 里的 `open` / `failed_retest` 继续闭环。
|
|
||||||
|
|
||||||
### 速查:想做什么 → 看哪个文件
|
|
||||||
|
|
||||||
| 想做的事 | 文件 |
|
|
||||||
|----------|------|
|
|
||||||
| 开一个新需求怎么起步 | `core/kickoff.md` |
|
|
||||||
| 角色/权限/状态机/完成定义 | `core/roles-and-permissions.md` |
|
|
||||||
| 三角色各自该怎么做(规划/写测试/复测能力清单) | `core/roles-and-permissions.md` §三角色能力清单 |
|
|
||||||
| 用哪个模型、怎么固定、何时升级 | `core/model-routing.md` |
|
|
||||||
| 闭环步骤、手动模式、worktree 决策与对齐 | `core/closed-loop.md` |
|
|
||||||
| 验收信号写法、三轮失败策略 | `core/optimization-method.md` |
|
|
||||||
| 派发/复测/回报的 prompt 文案 | `core/prompt-templates.md` |
|
|
||||||
| Orca 具体命令 | `core/orca-adapter.md` |
|
|
||||||
| 让 Agent 在新项目初始化 ack | `init-new-project.md` |
|
|
||||||
| 新项目接入步骤 | `adoption-checklist.md` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 默认口令
|
|
||||||
|
|
||||||
```text
|
|
||||||
用三角色协作闭环处理 docs/ack/tasks.yaml 里的未通过项:Coordinator 派发给 Developer 修复,再交给 Test 独立复测并终检;每个问题最多修三轮,三轮仍不过就记录为遗留,然后继续下一个。
|
|
||||||
```
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
# 接入清单
|
|
||||||
|
|
||||||
把本工具包引用/复制到新项目后,按此清单接入。术语与规则详见 `core/`。
|
|
||||||
|
|
||||||
## 1. 分发与版本
|
|
||||||
|
|
||||||
- [ ] 选定分发方式(README §「分发方式」A 单软链接 / B 复制)。
|
|
||||||
- [ ] 在 `docs/ack/` 下建单软链接 `kit`(指向本框架)。
|
|
||||||
- [ ] 覆盖层文件与 `tasks.yaml` 记录 `kitVersion`(对齐 `kit/VERSION`)。
|
|
||||||
|
|
||||||
## 2. 项目级规范(覆盖层)
|
|
||||||
|
|
||||||
- [ ] 由 `kit/templates/project.template.md` 生成覆盖层文件(默认 `docs/ack/project.md`,**不占用 `AGENTS.md`**)。
|
|
||||||
- [ ] 在 `tasks.yaml` 的 `project.overlayFile` 记录覆盖层实际路径。
|
|
||||||
- [ ] 覆盖层引用 `docs/ack/kit/core/`,不复制其内容。
|
|
||||||
- [ ] 填清项目简介、技术栈、运行/构建/单测/集成测试命令。
|
|
||||||
- [ ] 若要 Agent 自动加载覆盖层:在 `AGENTS.md` 加一行指向它,或直接命名为 `AGENTS.md`(可选)。
|
|
||||||
|
|
||||||
## 2b. 模型档位
|
|
||||||
|
|
||||||
- [ ] Coordinator 默认强模型且不亲自跑测试。
|
|
||||||
- [ ] Test、Developer 默认中低模型。
|
|
||||||
- [ ] 记录升级规则:三轮失败升级 Coordinator 复盘;复杂实现升级 Developer 档位(见 `kit/core/model-routing.md`)。
|
|
||||||
|
|
||||||
## 3. 路径权限
|
|
||||||
|
|
||||||
- [ ] 填实际的 Coordinator (PM) 可写路径(规格)。
|
|
||||||
- [ ] 填实际的 Test 可写路径(集成测试、复测记录)。
|
|
||||||
- [ ] 填实际的 Developer 可写路径(源码、单元测试、配置模板)。
|
|
||||||
- [ ] 本地私有配置标记只读或不提交。
|
|
||||||
- [ ] 确认 `tasks.yaml` 只有 Coordinator 写(见 `kit/core/roles-and-permissions.md`)。
|
|
||||||
|
|
||||||
## 4. 任务板
|
|
||||||
|
|
||||||
- [ ] 由 `kit/templates/tasks.template.yaml` 生成 `docs/ack/tasks.yaml`。
|
|
||||||
- [ ] 替换 `<project_name>`、`<repo_path>`、`<base_url>`、`<dev_worktree>`、`overlayFile`。
|
|
||||||
- [ ] 至少加一个真实任务,验收写成可观测信号(见 `kit/core/optimization-method.md` §1)。
|
|
||||||
- [ ] 跑 `python3 docs/ack/kit/scripts/validate_tasks.py docs/ack/tasks.yaml` 通过。
|
|
||||||
|
|
||||||
## 5. 编排
|
|
||||||
|
|
||||||
- [ ] 决定用 Orca(`kit/core/orca-adapter.md`)还是手动模式(`kit/core/closed-loop.md`)。
|
|
||||||
- [ ] Orca:`orca status`、`terminal list` 可用,Coordinator / Developer / Test 三个终端都在,回报能发回 Coordinator。
|
|
||||||
- [ ] 派发前会先查空闲 worker:按 `ACK-<ROLE>-<CLI>-<TIER>-<N>` 复用同 worktree、同角色、同配置终端;仅在不存在或都 busy 时新建,并把实际 handle 写入 `dispatch.worker`。
|
|
||||||
- [ ] 决定子任务放当前 worktree 起子 agent,还是新建隔离 worktree(决策速查见 `kit/core/closed-loop.md` §「子任务放哪」)。
|
|
||||||
- [ ] 多 worktree 时确认 `tasks.yaml`(SSOT)只保留一个权威副本,由 Coordinator 单写。
|
|
||||||
|
|
||||||
## 6. 验证命令
|
|
||||||
|
|
||||||
- [ ] Test 有黑盒复测命令,Developer 有白盒验证命令。
|
|
||||||
- [ ] 构建产物与服务启动命令写清。
|
|
||||||
- [ ] 浏览器测试 `BASE_URL` 写清,复测前能确认服务来自正确 worktree。
|
|
||||||
|
|
||||||
## 7. 三轮失败策略
|
|
||||||
|
|
||||||
- [ ] 每个任务记录 `dispatch.rounds`。
|
|
||||||
- [ ] 三轮失败标记 `leftover` 并填 `resolution.leftoverReason`。
|
|
||||||
- [ ] `leftover` 不阻塞其它任务;最终报告列出所有 `leftover` 与最后失败证据。
|
|
||||||
|
|
||||||
## 8. 第一次试运行
|
|
||||||
|
|
||||||
选一个低风险 bug 跑完整闭环,结束后检查是否出现:
|
|
||||||
|
|
||||||
- Developer 修改了越权路径,或 Test 改了源码。
|
|
||||||
- worker_done 或复测报告没有验证证据。
|
|
||||||
- Test 复测到了旧服务。
|
|
||||||
- Coordinator 没做终检就直接 verified。
|
|
||||||
- 任务板字段不够记录失败原因。
|
|
||||||
- 三轮失败策略没被执行。
|
|
||||||
|
|
||||||
把发现的问题回写到项目覆盖层文件;若属通用问题,回流到 kit 的 `kit/core/` 并升 `VERSION`。
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
# 在新项目初始化 ack(给 Agent 的操作手册)
|
|
||||||
|
|
||||||
本文件指导 Agent 把 `ack` 接入一个目标项目。它是**执行手册**:按步骤在目标项目创建 `docs/ack/`、挂载 kit、生成项目覆盖层和任务板,并完成一次结构校验。
|
|
||||||
|
|
||||||
若只想人工核对是否接入完整,看 `adoption-checklist.md`。若要开始一个真实需求,看 `core/kickoff.md`。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0. 前提与安全边界
|
|
||||||
|
|
||||||
开始前先确认三件事:
|
|
||||||
|
|
||||||
1. 目标项目根目录:运行或读取当前环境的项目根,不要假设路径。
|
|
||||||
2. ack kit 目录:本框架目录的绝对路径,例如 `<ack_kit_path>`。
|
|
||||||
3. 分发方式:默认用**单软链接引用**,只有用户明确要求时才整份复制。
|
|
||||||
|
|
||||||
硬规则:
|
|
||||||
|
|
||||||
- 不要覆盖已有 `docs/ack/project.md`、`docs/ack/tasks.yaml` 或团队现有 `AGENTS.md`。如果文件已存在,先读内容并向用户说明差异,再询问是否合并。
|
|
||||||
- 不要把 `core/` 内容复制进项目覆盖层。项目覆盖层只写项目差异。
|
|
||||||
- 不要提交、推送或改 git 配置,除非用户明确要求。
|
|
||||||
- 不要把私有配置、token、`.env` 内容写进 `tasks.yaml` 或 `project.md`。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 建目录并挂载 kit
|
|
||||||
|
|
||||||
在目标项目根目录下创建推荐布局:
|
|
||||||
|
|
||||||
```text
|
|
||||||
docs/ack/
|
|
||||||
kit -> <ack_kit_path>
|
|
||||||
project.md
|
|
||||||
tasks.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
默认方式(推荐,可升级):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p docs/ack
|
|
||||||
ln -s <ack_kit_path> docs/ack/kit
|
|
||||||
```
|
|
||||||
|
|
||||||
如果 `docs/ack/kit` 已存在:
|
|
||||||
|
|
||||||
- 是指向同一个 ack 目录的软链接:继续。
|
|
||||||
- 是指向旧版本的软链接:报告当前指向与目标指向,询问是否更新。
|
|
||||||
- 是普通目录:不要删除;询问用户是保留、备份还是改用复制模式。
|
|
||||||
|
|
||||||
复制模式(仅用户明确要求):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p docs/ack
|
|
||||||
cp -R <ack_kit_path> docs/ack/kit
|
|
||||||
```
|
|
||||||
|
|
||||||
复制模式接入后,后续升级需要人工对比 `docs/ack/kit/VERSION`。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 生成项目覆盖层 `project.md`
|
|
||||||
|
|
||||||
从模板生成:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp docs/ack/kit/templates/project.template.md docs/ack/project.md
|
|
||||||
```
|
|
||||||
|
|
||||||
然后填实际项目差异:
|
|
||||||
|
|
||||||
- 项目名、技术栈、运行命令、Base URL。
|
|
||||||
- 覆盖层文件路径:默认 `docs/ack/project.md`。
|
|
||||||
- 三角色模型档位:默认 Coordinator 强模型,Developer/Test 中低模型。
|
|
||||||
- 路径权限:规格文档、集成测试、复测记录、源码、单元测试、配置模板、本地私有配置。
|
|
||||||
- Developer 白盒验证命令:单测、构建、服务启动或重启。
|
|
||||||
- Test 黑盒复测命令:preflight、API smoke、浏览器回归。
|
|
||||||
|
|
||||||
如果是网站项目,务必在命令里写清:
|
|
||||||
|
|
||||||
- Developer 改完后如何重启服务或确认热更新生效。
|
|
||||||
- Test 使用哪个 Base URL。
|
|
||||||
- Test 浏览器复测入口、用例目录或手工检查路径。
|
|
||||||
|
|
||||||
可选:若希望 Agent 自动加载覆盖层,在目标项目 `AGENTS.md` 加一行指向它:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
本项目使用 ack 三角色协作协议,项目覆盖层见 `docs/ack/project.md`。
|
|
||||||
```
|
|
||||||
|
|
||||||
如果目标项目已有 `AGENTS.md`,只追加这一行或一个短小段落;不要重写原文件。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 生成任务板 `tasks.yaml`
|
|
||||||
|
|
||||||
从模板生成:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp docs/ack/kit/templates/tasks.template.yaml docs/ack/tasks.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
至少替换这些字段:
|
|
||||||
|
|
||||||
- `updatedAt`:当前时间,带时区。
|
|
||||||
- `kitVersion`:读取 `docs/ack/kit/VERSION`。
|
|
||||||
- `project.name`:项目名。
|
|
||||||
- `project.repoPath`:目标项目根目录绝对路径。
|
|
||||||
- `project.baseUrl`:网站或 API 的默认访问地址;非服务项目可写 `n/a`。
|
|
||||||
- `project.devWorktree`:默认当前 worktree;如果会用独立 worktree,写实际路径。
|
|
||||||
- `project.overlayFile`:默认 `docs/ack/project.md`。
|
|
||||||
|
|
||||||
任务可以先保留一个低风险示例任务,但更推荐写入真实首个任务。每个任务的验收必须是可观测信号:
|
|
||||||
|
|
||||||
- 可见文本:页面或 CLI 输出出现什么。
|
|
||||||
- API 结果:状态码、字段、响应值。
|
|
||||||
- 交互结果:点击、跳转、提交、取消后的真实状态。
|
|
||||||
|
|
||||||
不要只写“功能正常”“体验更好”。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 校验结构
|
|
||||||
|
|
||||||
在目标项目根目录运行:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 docs/ack/kit/scripts/validate_tasks.py docs/ack/tasks.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
通过后继续;失败则按错误修正 `tasks.yaml`,不要绕过校验。
|
|
||||||
|
|
||||||
同时人工检查:
|
|
||||||
|
|
||||||
- `docs/ack/kit` 是否能访问 `core/`、`templates/`、`scripts/`。
|
|
||||||
- `docs/ack/project.md` 是否只写项目差异,没有复制大段 core 文本。
|
|
||||||
- `docs/ack/tasks.yaml` 的 `project.overlayFile` 是否指向真实文件。
|
|
||||||
- 路径权限是否能防止 Developer 改规格、Test 改源码、多人写 `tasks.yaml`。
|
|
||||||
- 网站项目是否写清服务重启和浏览器复测方式。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 给用户的初始化报告
|
|
||||||
|
|
||||||
初始化完成后,Agent 应给用户一段短报告:
|
|
||||||
|
|
||||||
```text
|
|
||||||
ack 初始化完成。
|
|
||||||
|
|
||||||
创建/确认:
|
|
||||||
- docs/ack/kit -> <ack_kit_path>
|
|
||||||
- docs/ack/project.md
|
|
||||||
- docs/ack/tasks.yaml
|
|
||||||
|
|
||||||
已填项目差异:
|
|
||||||
- 技术栈:<...>
|
|
||||||
- Developer 验证命令:<...>
|
|
||||||
- Test 复测命令:<...>
|
|
||||||
- Base URL:<...>
|
|
||||||
|
|
||||||
校验:
|
|
||||||
- python3 docs/ack/kit/scripts/validate_tasks.py docs/ack/tasks.yaml: passed
|
|
||||||
|
|
||||||
下一步:
|
|
||||||
- 用 docs/ack/kit/core/kickoff.md 开一个真实需求;或
|
|
||||||
- 选一个低风险 bug 跑一次完整三角色闭环。
|
|
||||||
```
|
|
||||||
|
|
||||||
如果有未完成项,不要说“初始化完成”。改用:
|
|
||||||
|
|
||||||
```text
|
|
||||||
ack 初始化已部分完成,阻塞在:<具体原因>。
|
|
||||||
需要用户决定:<选项或缺失信息>。
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 一句话口令
|
|
||||||
|
|
||||||
当用户在新项目里说“初始化 ack”时,Agent 可以按下面执行:
|
|
||||||
|
|
||||||
```text
|
|
||||||
请在当前项目初始化 ack:确认项目根和 ack kit 路径;创建 docs/ack/kit 软链接;由 kit/templates 生成 docs/ack/project.md 与 docs/ack/tasks.yaml;填入项目实际路径、命令、Base URL、模型档位和首个任务;运行 validate_tasks.py;不要覆盖已有文件,不要提交或推送,完成后报告创建文件、校验结果和下一步。
|
|
||||||
```
|
|
||||||
+4
-4
@@ -66,7 +66,7 @@ skiff bootstrap
|
|||||||
|------|------|
|
|------|------|
|
||||||
| `skiff bootstrap` | 将本项目的 `skiff` skill 全局安装到所有 Agent |
|
| `skiff bootstrap` | 将本项目的 `skiff` skill 全局安装到所有 Agent |
|
||||||
| `skiff update` | 在 `~/.skills` 执行 `git pull`,更新 skiff 自身 |
|
| `skiff update` | 在 `~/.skills` 执行 `git pull`,更新 skiff 自身 |
|
||||||
| `skiff kit init <name> [--project DIR] [--copy]` | 在项目的 `docs/<name>/` 初始化规范包;默认软链接到 SSOT |
|
| `skiff init <name> [--project DIR]` | 使用 builtin skill 自带模板初始化项目状态 |
|
||||||
|
|
||||||
### 全局安装(自研 skill)
|
### 全局安装(自研 skill)
|
||||||
|
|
||||||
@@ -176,8 +176,8 @@ skiff 不保存 token。可以使用 `company/code-review`,也可以使用
|
|||||||
|
|
||||||
| 命令 | 说明 |
|
| 命令 | 说明 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| `skiff create <name> [--idea TEXT] [--from-project PATH]` | 从模板创建草稿到 `~/.skills/.drafts/` |
|
| `skiff create <name> [--idea TEXT] [--from-project PATH]` | 从模板创建含 `SKILL.md`、`README.md` 的草稿 |
|
||||||
| `skiff check <name>` | 校验草稿或正式 skill |
|
| `skiff check <name>` | 校验草稿或正式 skill,包括人类使用说明 |
|
||||||
| `skiff finalize <name>` | 校验草稿并移动到正式 `skills/` |
|
| `skiff finalize <name>` | 校验草稿并移动到正式 `skills/` |
|
||||||
| `skiff doctor [--target all] [--fix]` | 检查软链健康状态,`--fix` 自动修复 |
|
| `skiff doctor [--target all] [--fix]` | 检查软链健康状态,`--fix` 自动修复 |
|
||||||
|
|
||||||
@@ -187,7 +187,7 @@ skiff 不保存 token。可以使用 `company/code-review`,也可以使用
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
skiff create my-skill --idea "描述要解决的重复问题" --from-project .
|
skiff create my-skill --idea "描述要解决的重复问题" --from-project .
|
||||||
# 由 Agent 完善 ~/.skills/.drafts/my-skill/SKILL.md
|
# 由 Agent 完善草稿中的 SKILL.md 和 README.md
|
||||||
skiff check my-skill
|
skiff check my-skill
|
||||||
skiff finalize my-skill
|
skiff finalize my-skill
|
||||||
skiff publish skills/my-skill -m "add my-skill" --push
|
skiff publish skills/my-skill -m "add my-skill" --push
|
||||||
|
|||||||
+33
-43
@@ -4,7 +4,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import re
|
import re
|
||||||
import shutil
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -18,7 +17,6 @@ from skiff.paths import (
|
|||||||
DRAFTS_DIR,
|
DRAFTS_DIR,
|
||||||
EXTERNALS_DIR,
|
EXTERNALS_DIR,
|
||||||
CONFIG_FILE,
|
CONFIG_FILE,
|
||||||
KITS_DIR,
|
|
||||||
SKILLS_DIR,
|
SKILLS_DIR,
|
||||||
SKILLS_HOME,
|
SKILLS_HOME,
|
||||||
TEMPLATE_DIR,
|
TEMPLATE_DIR,
|
||||||
@@ -83,7 +81,7 @@ def _project_root(explicit: str | None = None) -> Path:
|
|||||||
return find_repo_root() or Path.cwd()
|
return find_repo_root() or Path.cwd()
|
||||||
|
|
||||||
|
|
||||||
def _render_kit_template(source: Path, destination: Path, values: dict[str, str]) -> None:
|
def _render_template(source: Path, destination: Path, values: dict[str, str]) -> None:
|
||||||
content = source.read_text(encoding="utf-8")
|
content = source.read_text(encoding="utf-8")
|
||||||
for placeholder, value in values.items():
|
for placeholder, value in values.items():
|
||||||
content = content.replace(placeholder, value)
|
content = content.replace(placeholder, value)
|
||||||
@@ -1001,6 +999,16 @@ def cmd_create(args: argparse.Namespace) -> None:
|
|||||||
flags=re.MULTILINE,
|
flags=re.MULTILINE,
|
||||||
)
|
)
|
||||||
skill_md.write_text(content, encoding="utf-8")
|
skill_md.write_text(content, encoding="utf-8")
|
||||||
|
readme = dst / "README.md"
|
||||||
|
if readme.is_file():
|
||||||
|
readme_content = re.sub(
|
||||||
|
r"^#\s+skill-name\s*$",
|
||||||
|
f"# {args.name}",
|
||||||
|
readme.read_text(encoding="utf-8"),
|
||||||
|
count=1,
|
||||||
|
flags=re.MULTILINE,
|
||||||
|
)
|
||||||
|
readme.write_text(readme_content, encoding="utf-8")
|
||||||
brief = {
|
brief = {
|
||||||
"name": args.name,
|
"name": args.name,
|
||||||
"idea": args.idea or "",
|
"idea": args.idea or "",
|
||||||
@@ -1122,66 +1130,55 @@ def cmd_doctor(args: argparse.Namespace) -> None:
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def cmd_kit_init(args: argparse.Namespace) -> None:
|
def cmd_init(args: argparse.Namespace) -> None:
|
||||||
"""将 owned kit 初始化到目标项目。"""
|
"""使用 builtin skill 自带的模板初始化目标项目状态。"""
|
||||||
ensure_skills_home()
|
ensure_skills_home()
|
||||||
kit_source = KITS_DIR / args.name
|
skill_source = SKILLS_DIR / args.name
|
||||||
if not kit_source.is_dir():
|
if not (skill_source / "SKILL.md").is_file():
|
||||||
available = sorted(path.name for path in KITS_DIR.iterdir() if path.is_dir()) if KITS_DIR.is_dir() else []
|
raise SystemExit(f"builtin skill 不存在: {args.name}")
|
||||||
suffix = f";可用 kit: {', '.join(available)}" if available else ""
|
|
||||||
raise SystemExit(f"kit 不存在: {args.name}{suffix}")
|
|
||||||
|
|
||||||
project = _project_root(args.project)
|
project = _project_root(args.project)
|
||||||
if not project.is_dir():
|
if not project.is_dir():
|
||||||
raise SystemExit(f"项目目录不存在: {project}")
|
raise SystemExit(f"项目目录不存在: {project}")
|
||||||
destination = project / "docs" / args.name
|
destination = project / "docs" / args.name
|
||||||
kit_target = destination / "kit"
|
|
||||||
project_file = destination / "project.md"
|
project_file = destination / "project.md"
|
||||||
tasks_file = destination / "tasks.yaml"
|
tasks_file = destination / "tasks.yaml"
|
||||||
managed_targets = (kit_target, project_file, tasks_file)
|
managed_targets = (project_file, tasks_file)
|
||||||
existing = [path for path in managed_targets if path.exists() or path.is_symlink()]
|
existing = [path for path in managed_targets if path.exists() or path.is_symlink()]
|
||||||
if existing:
|
if existing:
|
||||||
paths = ", ".join(str(path.relative_to(project)) for path in existing)
|
paths = ", ".join(str(path.relative_to(project)) for path in existing)
|
||||||
raise SystemExit(f"拒绝覆盖已有路径: {paths}")
|
raise SystemExit(f"拒绝覆盖已有路径: {paths}")
|
||||||
|
|
||||||
project_template = kit_source / "templates" / "project.template.md"
|
project_template = skill_source / "templates" / "project.template.md"
|
||||||
tasks_template = kit_source / "templates" / "tasks.template.yaml"
|
tasks_template = skill_source / "templates" / "tasks.template.yaml"
|
||||||
missing = [path for path in (project_template, tasks_template) if not path.is_file()]
|
missing = [path for path in (project_template, tasks_template) if not path.is_file()]
|
||||||
if missing:
|
if missing:
|
||||||
paths = ", ".join(str(path.relative_to(SKILLS_HOME)) for path in missing)
|
paths = ", ".join(str(path.relative_to(SKILLS_HOME)) for path in missing)
|
||||||
raise SystemExit(f"kit 缺少初始化模板: {paths}")
|
raise SystemExit(f"skill 缺少初始化模板: {paths}")
|
||||||
|
|
||||||
destination.mkdir(parents=True, exist_ok=True)
|
destination.mkdir(parents=True, exist_ok=True)
|
||||||
if args.copy:
|
|
||||||
shutil.copytree(kit_source, kit_target)
|
|
||||||
mode = "copy"
|
|
||||||
else:
|
|
||||||
kit_target.symlink_to(kit_source.resolve(), target_is_directory=True)
|
|
||||||
mode = "symlink"
|
|
||||||
|
|
||||||
version_file = kit_source / "VERSION"
|
version_file = skill_source / "VERSION"
|
||||||
kit_version = version_file.read_text(encoding="utf-8").strip() if version_file.is_file() else "unknown"
|
ack_version = version_file.read_text(encoding="utf-8").strip() if version_file.is_file() else "unknown"
|
||||||
now = datetime.now().astimezone().isoformat(timespec="seconds")
|
now = datetime.now().astimezone().isoformat(timespec="seconds")
|
||||||
values = {
|
values = {
|
||||||
"<project_name>": project.name,
|
"<project_name>": project.name,
|
||||||
"<repo_path>": str(project),
|
"<repo_path>": str(project),
|
||||||
"<dev_worktree>": str(project),
|
"<dev_worktree>": str(project),
|
||||||
"<overlay_file_path>": f"docs/{args.name}/project.md",
|
"<overlay_file_path>": f"docs/{args.name}/project.md",
|
||||||
"<kit_version>": kit_version,
|
"<ack_version>": ack_version,
|
||||||
"<接入时的 ack 版本,见 kit 根 VERSION>": kit_version,
|
"<接入时的 ack skill 版本>": ack_version,
|
||||||
"<YYYY-MM-DDTHH:mm:ss+TZ>": now,
|
"<YYYY-MM-DDTHH:mm:ss+TZ>": now,
|
||||||
}
|
}
|
||||||
_render_kit_template(project_template, project_file, values)
|
_render_template(project_template, project_file, values)
|
||||||
_render_kit_template(tasks_template, tasks_file, values)
|
_render_template(tasks_template, tasks_file, values)
|
||||||
|
|
||||||
validator = kit_target / "scripts" / "validate_tasks.py"
|
validator = skill_source / "scripts" / "validate_tasks.py"
|
||||||
if validator.is_file():
|
if validator.is_file():
|
||||||
subprocess.run([sys.executable, str(validator), str(tasks_file)], check=True)
|
subprocess.run([sys.executable, str(validator), str(tasks_file)], check=True)
|
||||||
|
|
||||||
_print(f"✓ kit 初始化完成: {args.name}")
|
_print(f"✓ skill 项目状态初始化完成: {args.name}")
|
||||||
_print(f" 项目: {project}")
|
_print(f" 项目: {project}")
|
||||||
_print(f" 模式: {mode}")
|
|
||||||
_print(f" kit: {kit_target}")
|
|
||||||
_print(f" 覆盖层: {project_file}")
|
_print(f" 覆盖层: {project_file}")
|
||||||
_print(f" 任务板: {tasks_file}")
|
_print(f" 任务板: {tasks_file}")
|
||||||
_print("下一步: 填写 project.md 中的项目命令、路径权限和 Base URL")
|
_print("下一步: 填写 project.md 中的项目命令、路径权限和 Base URL")
|
||||||
@@ -1353,23 +1350,16 @@ def build_parser() -> argparse.ArgumentParser:
|
|||||||
p_finalize.add_argument("name", help="skill 名称")
|
p_finalize.add_argument("name", help="skill 名称")
|
||||||
p_finalize.set_defaults(func=cmd_finalize)
|
p_finalize.set_defaults(func=cmd_finalize)
|
||||||
|
|
||||||
|
p_init = sub.add_parser("init", help="使用 skill 模板初始化项目状态")
|
||||||
|
p_init.add_argument("name", help="skill 名称")
|
||||||
|
p_init.add_argument("--project", help="项目根目录(默认自动检测或当前目录)")
|
||||||
|
p_init.set_defaults(func=cmd_init)
|
||||||
|
|
||||||
p_doctor = sub.add_parser("doctor", help="软链健康检查")
|
p_doctor = sub.add_parser("doctor", help="软链健康检查")
|
||||||
p_doctor.add_argument("-a", "--agent", dest="agents", nargs="+", action="append")
|
p_doctor.add_argument("-a", "--agent", dest="agents", nargs="+", action="append")
|
||||||
p_doctor.add_argument("--fix", action="store_true", help="自动修复可修复的软链")
|
p_doctor.add_argument("--fix", action="store_true", help="自动修复可修复的软链")
|
||||||
p_doctor.set_defaults(func=cmd_doctor)
|
p_doctor.set_defaults(func=cmd_doctor)
|
||||||
|
|
||||||
p_kit = sub.add_parser("kit", help="管理项目规范包")
|
|
||||||
kit_sub = p_kit.add_subparsers(dest="kit_command", required=True)
|
|
||||||
p_kit_init = kit_sub.add_parser("init", help="在项目中初始化 kit")
|
|
||||||
p_kit_init.add_argument("name", help="kit 名称")
|
|
||||||
p_kit_init.add_argument("--project", help="项目根目录(默认自动检测或当前目录)")
|
|
||||||
p_kit_init.add_argument(
|
|
||||||
"--copy",
|
|
||||||
action="store_true",
|
|
||||||
help="复制 kit,而不是创建指向 SSOT 的软链接",
|
|
||||||
)
|
|
||||||
p_kit_init.set_defaults(func=cmd_kit_init)
|
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ from pathlib import Path
|
|||||||
HOME = Path.home()
|
HOME = Path.home()
|
||||||
SKILLS_HOME = HOME / ".skills"
|
SKILLS_HOME = HOME / ".skills"
|
||||||
SKILLS_DIR = SKILLS_HOME / "skills"
|
SKILLS_DIR = SKILLS_HOME / "skills"
|
||||||
KITS_DIR = SKILLS_HOME / "kits"
|
|
||||||
TEMPLATE_DIR = SKILLS_DIR / "_template"
|
TEMPLATE_DIR = SKILLS_DIR / "_template"
|
||||||
DRAFTS_DIR = SKILLS_HOME / ".drafts"
|
DRAFTS_DIR = SKILLS_HOME / ".drafts"
|
||||||
REGISTRY_FILE = SKILLS_HOME / "registry.yaml"
|
REGISTRY_FILE = SKILLS_HOME / "registry.yaml"
|
||||||
|
|||||||
@@ -196,8 +196,24 @@ def validate_skill_dir(skill_dir: Path, expected_name: str) -> list[str]:
|
|||||||
"""返回 skill 目录中的校验问题;空列表表示通过。"""
|
"""返回 skill 目录中的校验问题;空列表表示通过。"""
|
||||||
issues: list[str] = []
|
issues: list[str] = []
|
||||||
skill_md = skill_dir / "SKILL.md"
|
skill_md = skill_dir / "SKILL.md"
|
||||||
|
readme = skill_dir / "README.md"
|
||||||
if not skill_md.is_file():
|
if not skill_md.is_file():
|
||||||
return ["缺少 SKILL.md"]
|
return ["缺少 SKILL.md"]
|
||||||
|
if not readme.is_file():
|
||||||
|
issues.append("缺少 README.md(面向人类的使用说明)")
|
||||||
|
else:
|
||||||
|
readme_text = readme.read_text(encoding="utf-8")
|
||||||
|
if not readme_text.strip():
|
||||||
|
issues.append("README.md 为空")
|
||||||
|
if re.search(r"^#\s+skill-name\s*$", readme_text, re.MULTILINE):
|
||||||
|
issues.append("README.md 存在模板占位内容: skill-name")
|
||||||
|
for placeholder in (
|
||||||
|
"用一句话告诉使用者这个 skill 能解决什么问题",
|
||||||
|
"给出用户可以直接说出的典型请求",
|
||||||
|
"给出一条可以直接交给 Agent 的示例请求",
|
||||||
|
):
|
||||||
|
if placeholder in readme_text:
|
||||||
|
issues.append(f"README.md 存在模板占位内容: {placeholder}")
|
||||||
|
|
||||||
text = skill_md.read_text(encoding="utf-8")
|
text = skill_md.read_text(encoding="utf-8")
|
||||||
if not text.strip():
|
if not text.strip():
|
||||||
|
|||||||
@@ -0,0 +1,219 @@
|
|||||||
|
# Skill 来源模型
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
skiff 当前使用 `owned` 表示本仓库 `skills/` 中维护的 Skill,同时还支持
|
||||||
|
`registry` 外部仓库和用户配置的 custom source。
|
||||||
|
|
||||||
|
这些名称混合了不同维度:
|
||||||
|
|
||||||
|
- `owned` 表示所有权。
|
||||||
|
- `registry` 表示来源配置存放在哪里。
|
||||||
|
- `external` 表示内容不在当前仓库。
|
||||||
|
- `collection` 表示一个来源中包含多个 Skill。
|
||||||
|
|
||||||
|
它们不能作为同一层级的互斥类型。例如 custom source 既可能来自外部 Git 仓库,
|
||||||
|
也可能是 collection;因此 `custom` 与 `external` 并不互斥。
|
||||||
|
|
||||||
|
本文建议将用户可见的来源注册方式统一为三类:
|
||||||
|
|
||||||
|
- `builtin`:随当前 skiff 仓库提供的 Skill。
|
||||||
|
- `catalog:<name>`:由 skiff 自带目录预先登记的来源。
|
||||||
|
- `custom:<name>`:用户在本机显式配置的命名来源。
|
||||||
|
|
||||||
|
来源注册方式与获取方式、仓库布局相互独立。builtin、catalog 和 custom 中的任意
|
||||||
|
来源都可以包含一个或多个 Skill;除 builtin 外,catalog 和 custom 都可以使用
|
||||||
|
Git 仓库或本地目录。
|
||||||
|
|
||||||
|
## 当前模型
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
S[skiff 可发现的 Skills]
|
||||||
|
|
||||||
|
S --> O["owned<br/>~/.skills/skills/*"]
|
||||||
|
S --> C["custom source<br/>~/.config/skiff/config.yaml"]
|
||||||
|
S --> R["registry<br/>registry.yaml"]
|
||||||
|
|
||||||
|
O --> O1["本仓库维护<br/>随 skiff 一起分发"]
|
||||||
|
|
||||||
|
C --> C1["本地目录<br/>--local PATH"]
|
||||||
|
C --> C2["指定 Git 仓库<br/>repo + skills_path"]
|
||||||
|
|
||||||
|
R --> R1["外部单 Skill 仓库"]
|
||||||
|
R --> R2["外部 Skill Collection"]
|
||||||
|
R1 --> E["~/.local/share/skills/externals/"]
|
||||||
|
R2 --> E
|
||||||
|
```
|
||||||
|
|
||||||
|
当前实现中:
|
||||||
|
|
||||||
|
- `list` 和 `status` 支持 owned、registry 和 custom source。
|
||||||
|
- `resolve_skill_source` 可以解析三种来源并处理同名歧义。
|
||||||
|
- `select` 只组装 owned 和 registry 条目,尚未展示 custom source。
|
||||||
|
- `.skills.yaml` 默认将未声明来源的 Skill 解释为 `owned`。
|
||||||
|
- custom source 的 `skills_path` 已经可以包含多个 Skill,本质上也是 collection。
|
||||||
|
|
||||||
|
## 推荐模型
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A[Skill Provider]
|
||||||
|
|
||||||
|
A --> B["builtin<br/>仓库内隐式注册"]
|
||||||
|
A --> C["catalog:waza<br/>skiff 预置目录"]
|
||||||
|
A --> D["custom:company<br/>用户本机配置"]
|
||||||
|
|
||||||
|
B --> B1["skills/ack"]
|
||||||
|
B --> B2["skills/skiff"]
|
||||||
|
B --> B3["skills/deb-publisher"]
|
||||||
|
|
||||||
|
C --> C1["Git 或本地目录"]
|
||||||
|
C1 --> C2["单 Skill"]
|
||||||
|
C1 --> C3["Collection"]
|
||||||
|
C3 --> C4["waza/think"]
|
||||||
|
C3 --> C5["waza/check"]
|
||||||
|
|
||||||
|
D --> D1["Git 或本地目录"]
|
||||||
|
D1 --> D2["单 Skill"]
|
||||||
|
D1 --> D3["Collection"]
|
||||||
|
D3 --> D4["company/security-review"]
|
||||||
|
D3 --> D5["company/release"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 来源注册方式
|
||||||
|
|
||||||
|
| 类型 | 含义 | 配置来源 | 用户界面展示 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `builtin` | 随当前 skiff 仓库提供 | `skills/` | `builtin` |
|
||||||
|
| `catalog` | skiff 预先登记、所有用户可发现的来源 | `catalog.yaml`,迁移前为 `registry.yaml` | `catalog:<name>` |
|
||||||
|
| `custom` | 用户在本机显式注册的命名来源 | `~/.config/skiff/config.yaml` | `custom:<name>` |
|
||||||
|
|
||||||
|
`builtin` 比 `owned` 更适合作为用户可见名称,因为它表达 Skill 的分发位置和可用
|
||||||
|
方式,而不是仓库的所有权关系。
|
||||||
|
|
||||||
|
`catalog` 比 `registry` 更准确:当前文件只是 skiff 随仓库维护的预置来源目录,
|
||||||
|
并不是远程注册中心,也不是一种 Skill 来源协议。
|
||||||
|
|
||||||
|
### 三个正交维度
|
||||||
|
|
||||||
|
每个 provider 应由三个维度描述:
|
||||||
|
|
||||||
|
```text
|
||||||
|
registration: builtin | catalog | custom
|
||||||
|
transport: bundled | local | git
|
||||||
|
layout: single | collection
|
||||||
|
```
|
||||||
|
|
||||||
|
custom source 示例:
|
||||||
|
|
||||||
|
```text
|
||||||
|
registration: custom
|
||||||
|
name: company
|
||||||
|
transport: local | git
|
||||||
|
layout: single | collection
|
||||||
|
```
|
||||||
|
|
||||||
|
catalog entry 示例:
|
||||||
|
|
||||||
|
```text
|
||||||
|
registration: catalog
|
||||||
|
name: waza
|
||||||
|
transport: local | git
|
||||||
|
layout: single | collection
|
||||||
|
```
|
||||||
|
|
||||||
|
`external` 只适合描述物理位置,可作为 catalog 和部分 custom provider 的统称,
|
||||||
|
不应成为与 builtin、custom 并列的持久化类型。
|
||||||
|
|
||||||
|
## `select` 展示
|
||||||
|
|
||||||
|
`skiff select` 应同时展示 builtin、catalog 和 custom Skill:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[ ] ack builtin
|
||||||
|
[ ] deb-publisher builtin
|
||||||
|
[-] company custom source
|
||||||
|
[ ] company/release custom:company
|
||||||
|
[ ] company/security-review custom:company
|
||||||
|
[-] waza catalog source
|
||||||
|
[ ] waza/check catalog:waza
|
||||||
|
[ ] waza/think catalog:waza
|
||||||
|
```
|
||||||
|
|
||||||
|
建议遵循以下规则:
|
||||||
|
|
||||||
|
- builtin Skill 直接显示 Skill 名称。
|
||||||
|
- catalog 和 custom collection 都使用来源名称作为父节点。
|
||||||
|
- catalog 和 custom Skill 都使用 `<source>/<skill>` 作为选择器键。
|
||||||
|
- 单 Skill 来源可以省略父节点,但仍显示注册方式和来源名称。
|
||||||
|
- 不同来源安装后名称相同时,继续拒绝同时选择,避免覆盖同一安装路径。
|
||||||
|
|
||||||
|
## 配置表示
|
||||||
|
|
||||||
|
新写入的 `.skills.yaml` 使用以下形式:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
skills:
|
||||||
|
- name: ack
|
||||||
|
source: builtin
|
||||||
|
- name: security-review
|
||||||
|
source: company
|
||||||
|
- name: think
|
||||||
|
source: catalog:waza
|
||||||
|
```
|
||||||
|
|
||||||
|
custom source 在 manifest 中继续保存其逻辑名称,例如 `company`。这样不同机器可以
|
||||||
|
独立配置仓库地址,而项目只依赖稳定的来源名称。
|
||||||
|
|
||||||
|
## 兼容迁移
|
||||||
|
|
||||||
|
这是一次用户可见术语调整,应提供兼容层,避免已有项目立即失效:
|
||||||
|
|
||||||
|
1. 对外文档、CLI 输出和 selector 统一使用 `builtin`、`catalog:<name>` 和
|
||||||
|
`custom:<name>`。
|
||||||
|
2. 新生成的 `.skills.yaml` 对内置 Skill 写入 `source: builtin`。
|
||||||
|
3. 读取旧 manifest 时继续接受 `source: owned`,并在解析时归一化为 `builtin`。
|
||||||
|
4. CLI 参数在过渡期继续接受 `--source owned`,但帮助和输出只推荐 `builtin`。
|
||||||
|
5. 读取旧 manifest 中的 `source: registry` 和 `registry: <name>`,归一化为
|
||||||
|
`catalog:<name>`。
|
||||||
|
6. `registry.yaml` 可以先保留文件名,仅将用户界面术语改为 catalog;单独迁移为
|
||||||
|
`catalog.yaml` 时,应兼容读取旧文件。
|
||||||
|
7. custom source 的逻辑名称和现有 `config.yaml` 结构保持不变。
|
||||||
|
8. 将 `builtin`、`catalog` 和兼容别名 `owned`、`registry` 设为 custom source
|
||||||
|
保留字。
|
||||||
|
9. `select` 同步接入 custom Skill,并让 custom collection 与 catalog collection
|
||||||
|
使用相同的父子展示逻辑。
|
||||||
|
|
||||||
|
## 影响范围
|
||||||
|
|
||||||
|
实施时预计涉及:
|
||||||
|
|
||||||
|
- `skiff/skills.py`:来源解析、归一化和 builtin 命名。
|
||||||
|
- `skiff/project.py`:manifest 默认值、序列化与旧值兼容。
|
||||||
|
- `skiff/sources.py`:来源保留字。
|
||||||
|
- `skiff/registry.py`:逐步重命名为 catalog 概念。
|
||||||
|
- `skiff/cli.py`:`list`、`status`、`add`、`select` 和输出文案。
|
||||||
|
- `skiff/selector.py`:统一 catalog/custom collection 的父子展示。
|
||||||
|
- CLI 与来源解析测试。
|
||||||
|
- 根 README、`skiff/README.md`、`skills/skiff/SKILL.md` 和相关示例。
|
||||||
|
|
||||||
|
## 验证要求
|
||||||
|
|
||||||
|
实施完成后至少覆盖:
|
||||||
|
|
||||||
|
- builtin Skill 可以通过新名称列出、选择、安装和写入 manifest。
|
||||||
|
- 旧的 `source: owned` manifest 仍可同步。
|
||||||
|
- custom local source 和 custom Git source 都出现在 selector 中。
|
||||||
|
- catalog 单 Skill 和 Collection 都保持可选择。
|
||||||
|
- custom 单 Skill 和 Collection 都保持可选择。
|
||||||
|
- 多来源同名 Skill 必须显式指定来源。
|
||||||
|
- 两个选择最终安装为同一个名称时,selector 拒绝冲突。
|
||||||
|
- `list`、`status`、`select` 对同一来源使用一致术语。
|
||||||
|
|
||||||
|
## 设计前提
|
||||||
|
|
||||||
|
本方案假设 `registry.yaml` 当前的真实职责是维护 skiff 预置的来源目录,而不是提供
|
||||||
|
远程发布、版本解析或可信签名等注册中心能力。因此推荐逐步将用户可见概念改为
|
||||||
|
`catalog`。如果未来实现真正的远程 registry,再单独定义其协议和与 catalog 的同步
|
||||||
|
关系,不复用当前含义模糊的名称。
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# skill-name
|
||||||
|
|
||||||
|
用一句话告诉使用者这个 skill 能解决什么问题。
|
||||||
|
|
||||||
|
## 什么时候使用
|
||||||
|
|
||||||
|
- 给出用户可以直接说出的典型请求。
|
||||||
|
- 说明适用范围。
|
||||||
|
|
||||||
|
## 使用前准备
|
||||||
|
|
||||||
|
- 列出需要用户提前提供或确认的信息、权限和依赖。
|
||||||
|
|
||||||
|
## 使用示例
|
||||||
|
|
||||||
|
```text
|
||||||
|
给出一条可以直接交给 Agent 的示例请求。
|
||||||
|
```
|
||||||
|
|
||||||
|
## Agent 会做什么
|
||||||
|
|
||||||
|
1. 用面向使用者的语言概括主要步骤。
|
||||||
|
2. 说明可能发生的外部写操作或重要边界。
|
||||||
|
|
||||||
|
## 如何判断完成
|
||||||
|
|
||||||
|
- 说明使用者最终会得到什么,以及如何确认结果正确。
|
||||||
|
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# ACK
|
||||||
|
|
||||||
|
ACK 是一个显式调用的 Agent Skill,用三种独立角色运行工程协作闭环:
|
||||||
|
|
||||||
|
- Coordinator 拆解需求、派发任务并终检。
|
||||||
|
- Developer 实现并执行白盒验证。
|
||||||
|
- Test 独立执行黑盒复测。
|
||||||
|
|
||||||
|
关键约束是验证者不等于实现者。每个任务最多修复三轮,仍未通过时记录为
|
||||||
|
`leftover`,然后继续处理其它任务。
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
全局安装:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
skiff add ack -g
|
||||||
|
```
|
||||||
|
|
||||||
|
或只安装到当前项目:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
skiff add ack
|
||||||
|
```
|
||||||
|
|
||||||
|
ACK 只在用户显式调用 `/ack` 或 `$ack` 时运行。
|
||||||
|
|
||||||
|
## 初始化项目
|
||||||
|
|
||||||
|
```bash
|
||||||
|
skiff init ack
|
||||||
|
skiff init ack --project ~/code/my-app
|
||||||
|
```
|
||||||
|
|
||||||
|
初始化后,项目只保存自己的 ACK 状态:
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/ack/
|
||||||
|
├── project.md
|
||||||
|
└── tasks.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
不会在项目中复制或链接 ACK Skill。通用规范、模板和脚本始终从已安装的 Skill
|
||||||
|
目录读取。
|
||||||
|
|
||||||
|
## Skill 结构
|
||||||
|
|
||||||
|
```text
|
||||||
|
skills/ack/
|
||||||
|
├── SKILL.md
|
||||||
|
├── README.md
|
||||||
|
├── VERSION
|
||||||
|
├── references/ # 三角色规范、闭环流程和初始化说明
|
||||||
|
├── templates/ # project.md 与 tasks.yaml 模板和 schema
|
||||||
|
├── examples/ # 完整示例
|
||||||
|
└── scripts/ # tasks.yaml 与 worker 命令校验器
|
||||||
|
```
|
||||||
|
|
||||||
|
`SKILL.md` 是 Agent 的工作流入口。`references/` 是按需读取的稳定规范;
|
||||||
|
`docs/ack/project.md` 只保存当前项目的命令、路径和权限差异;
|
||||||
|
`docs/ack/tasks.yaml` 保存当前任务状态。
|
||||||
|
|
||||||
|
## 检查任务板
|
||||||
|
|
||||||
|
Agent 会从当前 ACK Skill 目录解析校验脚本:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 <ack-skill-dir>/scripts/validate_tasks.py docs/ack/tasks.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## 开始一个需求
|
||||||
|
|
||||||
|
初始化完成后可以直接说:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/ack 处理这个需求:<一句话需求>
|
||||||
|
```
|
||||||
|
|
||||||
|
Coordinator 会先读取项目状态和 `references/kickoff.md`,生成产品文档、任务拆分与
|
||||||
|
可观测验收信号;用户确认后才派发实现和复测。
|
||||||
|
|
||||||
|
## 版本
|
||||||
|
|
||||||
|
当前 Skill 版本见 `VERSION`。新项目在 `tasks.yaml` 中记录 `ackVersion`。旧项目的
|
||||||
|
`kitVersion` 可以继续读取,但建议迁移为 `ackVersion`。
|
||||||
+27
-22
@@ -1,15 +1,19 @@
|
|||||||
---
|
---
|
||||||
name: ack
|
name: ack
|
||||||
description: >-
|
description: >-
|
||||||
初始化、检查并运行 ACK(Agent Collaboration Kit)三角色协作闭环。仅在用户显式调用
|
初始化、检查并运行 ACK 三角色协作闭环。仅在用户显式调用 /ack 或 $ack,并要求
|
||||||
/ack 或 $ack,并要求初始化 ACK、检查 docs/ack 配置、按 ACK 规划需求或指挥
|
初始化 ACK、检查 docs/ack 配置、按 ACK 规划需求或指挥 Coordinator/Developer/Test
|
||||||
Coordinator/Developer/Test 工作时使用。
|
工作时使用。
|
||||||
---
|
---
|
||||||
|
|
||||||
# ACK 项目协作入口
|
# ACK 项目协作入口
|
||||||
|
|
||||||
把全局 skill 作为入口,把项目状态留在 `docs/ack/`,把通用规范留在
|
本 Skill 是 ACK 的完整能力包:`references/` 保存通用规范,`templates/` 保存项目
|
||||||
`~/.skills/kits/ack/`。不要在本 skill 复制或改写 kit 的核心规范。
|
状态模板,`scripts/` 保存校验工具。目标项目只在 `docs/ack/` 保存 `project.md` 和
|
||||||
|
`tasks.yaml`,不要复制或链接 Skill 内容。
|
||||||
|
|
||||||
|
开始时解析当前 `SKILL.md` 所在目录,记为 `<ack-skill-dir>`。所有通用规范、模板和
|
||||||
|
脚本都相对此目录访问,不依赖固定的全局安装路径。
|
||||||
|
|
||||||
## 选择模式
|
## 选择模式
|
||||||
|
|
||||||
@@ -27,10 +31,11 @@ description: >-
|
|||||||
2. 不存在时执行:
|
2. 不存在时执行:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
skiff kit init ack --project <project-root>
|
skiff init ack --project <project-root>
|
||||||
```
|
```
|
||||||
|
|
||||||
默认使用软链接模式。只有用户明确要求项目自带完整副本时才加 `--copy`。
|
该命令从本 Skill 的 `templates/` 生成项目状态,不会在项目中创建 Skill
|
||||||
|
软链接或资源副本。
|
||||||
3. 如果 `docs/ack` 已存在,不重复初始化、不覆盖文件;转入“检查”,报告缺失项并
|
3. 如果 `docs/ack` 已存在,不重复初始化、不覆盖文件;转入“检查”,报告缺失项并
|
||||||
只补用户授权且能安全确定的内容。
|
只补用户授权且能安全确定的内容。
|
||||||
4. 读取项目的公开配置和文档,例如 README、语言清单、包管理清单、测试配置与
|
4. 读取项目的公开配置和文档,例如 README、语言清单、包管理清单、测试配置与
|
||||||
@@ -39,33 +44,32 @@ description: >-
|
|||||||
- 用实际项目值替换全部占位符。
|
- 用实际项目值替换全部占位符。
|
||||||
- 无服务地址时把 Base URL 写为 `n/a`,不要虚构端口。
|
- 无服务地址时把 Base URL 写为 `n/a`,不要虚构端口。
|
||||||
- 无法从项目证据确定的命令写为 `n/a`,并在结果中列为待配置项。
|
- 无法从项目证据确定的命令写为 `n/a`,并在结果中列为待配置项。
|
||||||
- 保留 `docs/ack/kit/core/` 引用,不复制 core 内容。
|
- 只写项目差异,不复制 `references/` 中的通用规范。
|
||||||
6. 完善 `docs/ack/tasks.yaml` 的项目信息。纯初始化且用户没有提供真实任务时,
|
6. 完善 `docs/ack/tasks.yaml` 的项目信息。纯初始化且用户没有提供真实任务时,
|
||||||
删除模板示例任务并保留 `tasks: []`;不要虚构需求或缺陷。
|
删除模板示例任务并保留 `tasks: []`;不要虚构需求或缺陷。
|
||||||
7. 更新 `updatedAt`,并运行:
|
7. 更新 `updatedAt`,并运行:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 docs/ack/kit/scripts/validate_tasks.py docs/ack/tasks.yaml
|
python3 <ack-skill-dir>/scripts/validate_tasks.py docs/ack/tasks.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
8. 检查 `project.md` 与 `tasks.yaml` 是否仍有 `<...>` 占位符。结构校验通过且
|
8. 检查 `project.md` 与 `tasks.yaml` 是否仍有 `<...>` 占位符。结构校验通过且
|
||||||
必填项目事实完整时才称“初始化完成”;否则称“部分完成”并列出缺失值。
|
必填项目事实完整时才称“初始化完成”;否则称“部分完成”并列出缺失值。
|
||||||
9. 报告创建的路径、软链接或复制模式、检测到的命令、校验结果和下一步。除非用户
|
9. 报告创建的路径、检测到的命令、校验结果和下一步。除非用户明确要求,不提交、
|
||||||
明确要求,不提交、不推送。
|
不推送。
|
||||||
|
|
||||||
## 检查
|
## 检查
|
||||||
|
|
||||||
1. 检查以下路径:
|
1. 检查以下路径:
|
||||||
- `docs/ack/kit`
|
|
||||||
- `docs/ack/project.md`
|
- `docs/ack/project.md`
|
||||||
- `docs/ack/tasks.yaml`
|
- `docs/ack/tasks.yaml`
|
||||||
2. 确认 `kit` 可访问 `VERSION`、`core/`、`templates/` 和 `scripts/`。软链接模式下
|
2. 读取 `<ack-skill-dir>/VERSION`,对比 `tasks.yaml` 的 `ackVersion`。旧项目只有
|
||||||
同时报告其真实目标。
|
`ackVersion` 时仍可读取,但建议迁移为 `ackVersion`。
|
||||||
3. 对比 `tasks.yaml` 的 `kitVersion` 与 `kit/VERSION`。
|
3. 查找未替换占位符,并核对项目路径、覆盖层路径、Developer 白盒命令、Test
|
||||||
4. 查找未替换占位符,并核对项目路径、覆盖层路径、Developer 白盒命令、Test
|
|
||||||
黑盒命令和 Base URL。
|
黑盒命令和 Base URL。
|
||||||
5. 运行任务板校验器。只报告证据明确的问题,不因可选字段缺失而宣称失败。
|
4. 使用 `<ack-skill-dir>/scripts/validate_tasks.py` 校验任务板。只报告证据明确的
|
||||||
6. 检查不会自动修复或覆盖现有配置;用户明确要求修复后再修改。
|
问题,不因可选字段缺失而宣称失败。
|
||||||
|
5. 检查不会自动修复或覆盖现有配置;用户明确要求修复后再修改。
|
||||||
|
|
||||||
## 工作
|
## 工作
|
||||||
|
|
||||||
@@ -73,8 +77,8 @@ description: >-
|
|||||||
2. 依次读取:
|
2. 依次读取:
|
||||||
- `docs/ack/project.md`
|
- `docs/ack/project.md`
|
||||||
- `docs/ack/tasks.yaml`
|
- `docs/ack/tasks.yaml`
|
||||||
- `docs/ack/kit/core/kickoff.md`
|
- `<ack-skill-dir>/references/kickoff.md`
|
||||||
- kickoff 指定且与当前任务相关的 core 文件
|
- kickoff 指定且与当前任务相关的 references 文件
|
||||||
3. 当前会话担任 Coordinator,遵守项目覆盖层中的命令、路径权限、模型路由和
|
3. 当前会话担任 Coordinator,遵守项目覆盖层中的命令、路径权限、模型路由和
|
||||||
worker 复用规则。项目覆盖层优先于通用示例命令。
|
worker 复用规则。项目覆盖层优先于通用示例命令。
|
||||||
4. 新需求先写产品文档、任务拆分与可观测验收信号,更新 `tasks.yaml` 并校验,
|
4. 新需求先写产品文档、任务拆分与可观测验收信号,更新 `tasks.yaml` 并校验,
|
||||||
@@ -87,7 +91,8 @@ description: >-
|
|||||||
## 边界
|
## 边界
|
||||||
|
|
||||||
- 不修改或追加任何项目 Agent 指令文件,包括 `AGENTS.md`。
|
- 不修改或追加任何项目 Agent 指令文件,包括 `AGENTS.md`。
|
||||||
- 不在 skill 中维护第二份 ACK core、模板或任务 schema。
|
- 不在项目中维护第二份 ACK 通用规范、模板或任务 schema。
|
||||||
- 不猜测项目命令、服务地址、worker handle 或模型名称。
|
- 不猜测项目命令、服务地址、worker handle 或模型名称。
|
||||||
- 不覆盖已有 `docs/ack` 文件,不擅自提交、推送、创建终端或新 worktree。
|
- 不覆盖已有 `docs/ack` 文件,不擅自提交、推送、创建终端或新 worktree。
|
||||||
- kit 初始化的机械操作始终委托给 `skiff kit init ack`。
|
- 项目只保存 `docs/ack/project.md` 和 `docs/ack/tasks.yaml`;通用资源始终从当前
|
||||||
|
ACK Skill 目录读取。
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# notes-web Agent 协作协议(示例,项目覆盖层)
|
# notes-web Agent 协作协议(示例,项目覆盖层)
|
||||||
|
|
||||||
> 本项目基于 ack v0.8.1。
|
> 本项目基于 ack v0.8.1。
|
||||||
> 稳定规范引用 `docs/ack/kit/core/`,本文件只填项目差异。
|
> 通用规范由 `/ack` 从 Skill 自身的 `references/` 读取,本文件只填项目差异。
|
||||||
> 覆盖层文件放在 `docs/ack/project.md`,不占用 `AGENTS.md`。
|
> 覆盖层文件放在 `docs/ack/project.md`,不占用 `AGENTS.md`。
|
||||||
> 目录下只有一个软链接 `kit/` + `project.md` + `tasks.yaml`。
|
> `docs/ack/` 只保存 `project.md` 与 `tasks.yaml`。
|
||||||
|
|
||||||
## 项目概览
|
## 项目概览
|
||||||
|
|
||||||
@@ -16,13 +16,13 @@
|
|||||||
|
|
||||||
## 稳定规范(引用,不重复)
|
## 稳定规范(引用,不重复)
|
||||||
|
|
||||||
- 开始一个需求(启动手册):`docs/ack/kit/core/kickoff.md`
|
- 开始一个需求(启动手册):`references/kickoff.md`
|
||||||
- 角色 / 权限 / 状态机 / 完成定义:`docs/ack/kit/core/roles-and-permissions.md`
|
- 角色 / 权限 / 状态机 / 完成定义:`references/roles-and-permissions.md`
|
||||||
- 模型档位与升级规则:`docs/ack/kit/core/model-routing.md`
|
- 模型档位与升级规则:`references/model-routing.md`
|
||||||
- 闭环流程(含手动模式、worktree 对齐):`docs/ack/kit/core/closed-loop.md`
|
- 闭环流程(含手动模式、worktree 对齐):`references/closed-loop.md`
|
||||||
- 优化方法(验收信号、三轮策略):`docs/ack/kit/core/optimization-method.md`
|
- 优化方法(验收信号、三轮策略):`references/optimization-method.md`
|
||||||
- 派发 prompt 模板:`docs/ack/kit/core/prompt-templates.md`
|
- 派发 prompt 模板:`references/prompt-templates.md`
|
||||||
- Orca 编排命令:`docs/ack/kit/core/orca-adapter.md`
|
- Orca 编排命令:`references/orca-adapter.md`
|
||||||
|
|
||||||
## 模型档位
|
## 模型档位
|
||||||
|
|
||||||
@@ -63,16 +63,12 @@ curl -s -X POST http://localhost:5173/api/fix/preview -d @fixtures/preview.json
|
|||||||
# 浏览器回归:tests/browser/cases/*.md
|
# 浏览器回归:tests/browser/cases/*.md
|
||||||
```
|
```
|
||||||
|
|
||||||
任务板校验:
|
任务板校验由 `/ack` 使用 Skill 自带的 `scripts/validate_tasks.py` 执行。
|
||||||
|
|
||||||
```bash
|
## 硬规则(其余见 references/)
|
||||||
python3 docs/ack/kit/scripts/validate_tasks.py docs/ack/tasks.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
## 硬规则(其余见 kit/core/)
|
|
||||||
|
|
||||||
- 三角色独立:Coordinator 只编排、Test 只验证、Developer 只实现。
|
- 三角色独立:Coordinator 只编排、Test 只验证、Developer 只实现。
|
||||||
- 模型分层:Coordinator 强模型不跑测试,Test/Developer 中低模型(见 kit/core/model-routing.md)。
|
- 模型分层:Coordinator 强模型不跑测试,Test/Developer 中低模型(见 references/model-routing.md)。
|
||||||
- `worker_done` 与复测报告都不等于完成,必须 Test 独立复测 + Coordinator 终检后才能 `verified`。
|
- `worker_done` 与复测报告都不等于完成,必须 Test 独立复测 + Coordinator 终检后才能 `verified`。
|
||||||
- 只有 Coordinator 写 `tasks.yaml`;Test 与 Developer 只读。
|
- 只有 Coordinator 写 `tasks.yaml`;Test 与 Developer 只读。
|
||||||
- 每个任务最多派发 3 轮,仍不过标记 `leftover` 并继续。
|
- 每个任务最多派发 3 轮,仍不过标记 `leftover` 并继续。
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
version: 1
|
version: 1
|
||||||
updatedAt: "2026-07-06T09:40:00+08:00"
|
updatedAt: "2026-07-06T09:40:00+08:00"
|
||||||
source: "Coordinator (PM) Agent"
|
source: "Coordinator (PM) Agent"
|
||||||
kitVersion: "0.3.0"
|
ackVersion: "0.8.1"
|
||||||
project:
|
project:
|
||||||
name: "notes-web"
|
name: "notes-web"
|
||||||
repoPath: "/home/dev/notes-web"
|
repoPath: "/home/dev/notes-web"
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# ACK 接入清单
|
||||||
|
|
||||||
|
## 安装与初始化
|
||||||
|
|
||||||
|
- [ ] ACK Skill 已全局安装或安装到当前项目。
|
||||||
|
- [ ] 已运行 `skiff init ack --project <project-root>`。
|
||||||
|
- [ ] `docs/ack/` 只包含项目自己的 `project.md` 与 `tasks.yaml`。
|
||||||
|
- [ ] 项目中没有 ACK Skill 的复制目录或 `kit`、`framework` 软链接。
|
||||||
|
- [ ] `tasks.yaml` 使用 `ackVersion` 记录 ACK Skill 版本。
|
||||||
|
|
||||||
|
## 项目覆盖层
|
||||||
|
|
||||||
|
- [ ] `project.md` 只保存项目差异,不复制 Skill 的通用规范。
|
||||||
|
- [ ] `tasks.yaml` 的 `project.overlayFile` 指向实际覆盖层。
|
||||||
|
- [ ] 技术栈、运行、构建、单测和集成测试命令均来自项目证据。
|
||||||
|
- [ ] Coordinator、Developer、Test 的模型档位和升级规则已明确。
|
||||||
|
|
||||||
|
## 路径权限
|
||||||
|
|
||||||
|
- [ ] Coordinator 可写规格和任务板。
|
||||||
|
- [ ] Test 可写集成测试与复测记录,但不能改源码。
|
||||||
|
- [ ] Developer 可写源码与单元测试,但不能改规格或黑盒验收。
|
||||||
|
- [ ] 私有配置只读且不提交。
|
||||||
|
- [ ] `tasks.yaml` 只有 Coordinator 写。
|
||||||
|
|
||||||
|
## 任务板
|
||||||
|
|
||||||
|
- [ ] 已替换项目名、仓库路径、Base URL、worktree 和覆盖层路径。
|
||||||
|
- [ ] 没有真实任务时使用 `tasks: []`。
|
||||||
|
- [ ] 真实任务的验收是可观测信号。
|
||||||
|
- [ ] 已运行 `<ack-skill-dir>/scripts/validate_tasks.py` 并通过。
|
||||||
|
|
||||||
|
## 编排
|
||||||
|
|
||||||
|
- [ ] 已选择 Orca 或手动模式。
|
||||||
|
- [ ] 派发前优先复用同 worktree、同角色、同配置的空闲 worker。
|
||||||
|
- [ ] Developer 与 Test 的启动命令通过校验。
|
||||||
|
- [ ] 多 worktree 场景只有一个权威 `tasks.yaml`。
|
||||||
|
- [ ] Test 使用的服务来自正确 worktree。
|
||||||
|
|
||||||
|
## 闭环
|
||||||
|
|
||||||
|
- [ ] `worker_done` 不直接视为完成。
|
||||||
|
- [ ] Test 独立复测后由 Coordinator 读取证据终检。
|
||||||
|
- [ ] 每轮写入 `dispatch.rounds`。
|
||||||
|
- [ ] 三轮失败后标记 `leftover` 并填写原因。
|
||||||
|
- [ ] `leftover` 不阻塞其它任务。
|
||||||
|
|
||||||
|
首次接入建议选择一个低风险问题跑完整闭环。项目差异写回
|
||||||
|
`docs/ack/project.md`;通用问题回流到 ACK Skill 的 `references/`、`templates/`
|
||||||
|
或 `scripts/`,并更新 `VERSION`。
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# 在新项目初始化 ACK
|
||||||
|
|
||||||
|
本文件说明如何把 ACK 的项目状态初始化到目标项目。ACK Skill 自身已经通过 Agent
|
||||||
|
的 Skill 机制安装;项目不复制、不链接 Skill 内容。
|
||||||
|
|
||||||
|
## 前提与边界
|
||||||
|
|
||||||
|
开始前确认:
|
||||||
|
|
||||||
|
1. 目标项目根目录。
|
||||||
|
2. ACK Skill 已全局安装或安装到当前项目。
|
||||||
|
3. `skiff` 命令可用。
|
||||||
|
|
||||||
|
不要覆盖已有的 `docs/ack/project.md`、`docs/ack/tasks.yaml`、`AGENTS.md` 或其它
|
||||||
|
Agent 指令文件。不要把 token、`.env` 内容或其它私有配置写入 ACK 项目状态。
|
||||||
|
|
||||||
|
## 初始化
|
||||||
|
|
||||||
|
在目标项目执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
skiff init ack
|
||||||
|
```
|
||||||
|
|
||||||
|
或显式指定项目:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
skiff init ack --project <project-root>
|
||||||
|
```
|
||||||
|
|
||||||
|
命令从 ACK Skill 自带的 `templates/` 生成:
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/ack/
|
||||||
|
├── project.md
|
||||||
|
└── tasks.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
如果任一目标文件已经存在,命令会拒绝覆盖。初始化过程不会创建 `kit`、
|
||||||
|
`framework` 或其它指向 Skill 的软链接。
|
||||||
|
|
||||||
|
## 完善项目覆盖层
|
||||||
|
|
||||||
|
编辑 `docs/ack/project.md`,填入:
|
||||||
|
|
||||||
|
- 项目名、技术栈、运行命令和 Base URL。
|
||||||
|
- Coordinator、Developer、Test 的实际模型档位。
|
||||||
|
- 规格、集成测试、源码、单元测试和私有配置的路径权限。
|
||||||
|
- Developer 白盒验证命令。
|
||||||
|
- Test 黑盒复测命令。
|
||||||
|
|
||||||
|
无法从项目证据确定的值写为 `n/a`,不要猜测。
|
||||||
|
|
||||||
|
## 完善任务板
|
||||||
|
|
||||||
|
编辑 `docs/ack/tasks.yaml`:
|
||||||
|
|
||||||
|
- `ackVersion` 使用 ACK Skill 的 `VERSION`。
|
||||||
|
- `updatedAt` 使用当前带时区时间。
|
||||||
|
- `project.name`、`repoPath`、`devWorktree` 和 `overlayFile` 使用真实值。
|
||||||
|
- 非服务项目的 `baseUrl` 写为 `n/a`。
|
||||||
|
- 没有真实任务时使用 `tasks: []`,不要保留或虚构示例任务。
|
||||||
|
|
||||||
|
每个真实任务的验收必须是可观测信号,例如可见文本、API 状态和字段,或明确的交互
|
||||||
|
结果;不要只写“功能正常”。
|
||||||
|
|
||||||
|
## 校验
|
||||||
|
|
||||||
|
Agent 从当前 `SKILL.md` 解析 ACK Skill 目录后运行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 <ack-skill-dir>/scripts/validate_tasks.py docs/ack/tasks.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
同时确认:
|
||||||
|
|
||||||
|
- `project.md` 和 `tasks.yaml` 没有未替换的 `<...>` 占位符。
|
||||||
|
- `project.overlayFile` 指向真实文件。
|
||||||
|
- Developer 与 Test 的验证命令可执行。
|
||||||
|
- 网站或 API 项目写清服务启动、重启和 Base URL。
|
||||||
|
|
||||||
|
## 初始化报告
|
||||||
|
|
||||||
|
完成后报告:
|
||||||
|
|
||||||
|
- 创建或确认的两个项目文件。
|
||||||
|
- 检测到的技术栈和验证命令。
|
||||||
|
- 任务板校验结果。
|
||||||
|
- 仍需用户补充的值。
|
||||||
|
|
||||||
|
只有结构校验通过且必填项目事实完整时才称“初始化完成”;否则称“部分完成”,并列出
|
||||||
|
具体阻塞项。除非用户明确要求,不提交、不推送。
|
||||||
@@ -14,9 +14,9 @@
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
我要做一个新需求:<一句话需求>。
|
我要做一个新需求:<一句话需求>。
|
||||||
你作为 ack 的 Coordinator(PM),按 docs/ack/kit/core 的规范执行:
|
你作为 ack 的 Coordinator(PM),按 ACK Skill 的 references 规范执行:
|
||||||
|
|
||||||
1. 先读 docs/ack/project.md、docs/ack/kit/core/roles-and-permissions.md、closed-loop.md、optimization-method.md。
|
1. 先读 docs/ack/project.md、references/roles-and-permissions.md、closed-loop.md、optimization-method.md。
|
||||||
2. 写产品文档到 docs/(PRD / 交互 / 验收),把需求拆成任务,每个任务的验收写成可观测信号(可见文本 / API 结果 / 交互结果)。
|
2. 写产品文档到 docs/(PRD / 交互 / 验收),把需求拆成任务,每个任务的验收写成可观测信号(可见文本 / API 结果 / 交互结果)。
|
||||||
3. 把任务写进 docs/ack/tasks.yaml(只有你写),跑 validate 校验结构。
|
3. 把任务写进 docs/ack/tasks.yaml(只有你写),跑 validate 校验结构。
|
||||||
4. 先把「产品文档 + 任务拆分 + 验收信号」给我确认,不要急着派发。
|
4. 先把「产品文档 + 任务拆分 + 验收信号」给我确认,不要急着派发。
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
3. 校验结构:
|
3. 校验结构:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 docs/ack/kit/scripts/validate_tasks.py docs/ack/tasks.yaml
|
python3 <ack-skill-dir>/scripts/validate_tasks.py docs/ack/tasks.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **停下来给人确认**——这是强模型该花时间的地方,不要跳过。
|
4. **停下来给人确认**——这是强模型该花时间的地方,不要跳过。
|
||||||
@@ -58,14 +58,14 @@ python3 docs/ack/kit/scripts/validate_tasks.py docs/ack/tasks.yaml
|
|||||||
# Codex 示例(模型与执行模式以项目覆盖层为准)
|
# Codex 示例(模型与执行模式以项目覆盖层为准)
|
||||||
DEV_CMD='codex --dangerously-bypass-approvals-and-sandbox -m gpt-5.6-terra -c model_reasoning_effort=medium'
|
DEV_CMD='codex --dangerously-bypass-approvals-and-sandbox -m gpt-5.6-terra -c model_reasoning_effort=medium'
|
||||||
TEST_CMD='codex --dangerously-bypass-approvals-and-sandbox -m gpt-5.6-luna -c model_reasoning_effort=low'
|
TEST_CMD='codex --dangerously-bypass-approvals-and-sandbox -m gpt-5.6-luna -c model_reasoning_effort=low'
|
||||||
python3 docs/ack/validate_worker_command.py --role developer --command "$DEV_CMD"
|
python3 <ack-skill-dir>/scripts/validate_worker_command.py --role developer --command "$DEV_CMD"
|
||||||
python3 docs/ack/validate_worker_command.py --role test --command "$TEST_CMD"
|
python3 <ack-skill-dir>/scripts/validate_worker_command.py --role test --command "$TEST_CMD"
|
||||||
orca terminal create --worktree active --command "$DEV_CMD" --title "ACK-DEV-CODEX-TERRA-1" --json
|
orca terminal create --worktree active --command "$DEV_CMD" --title "ACK-DEV-CODEX-TERRA-1" --json
|
||||||
orca terminal create --worktree active --command "$TEST_CMD" --title "ACK-TEST-CODEX-LUNA-1" --json
|
orca terminal create --worktree active --command "$TEST_CMD" --title "ACK-TEST-CODEX-LUNA-1" --json
|
||||||
|
|
||||||
# Cursor 示例
|
# Cursor 示例
|
||||||
CURSOR_CMD='cursor-agent --yolo --model auto'
|
CURSOR_CMD='cursor-agent --yolo --model auto'
|
||||||
python3 docs/ack/validate_worker_command.py --role developer --command "$CURSOR_CMD"
|
python3 <ack-skill-dir>/scripts/validate_worker_command.py --role developer --command "$CURSOR_CMD"
|
||||||
orca terminal create --worktree active --command "$CURSOR_CMD" --title "ACK-DEV-CURSOR-AUTO-1" --json
|
orca terminal create --worktree active --command "$CURSOR_CMD" --title "ACK-DEV-CURSOR-AUTO-1" --json
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
|
|
||||||
执行模式也必须在创建 worker 时固定,并服从项目覆盖层或用户的明确要求。Cursor 的 YOLO 参数是 `--yolo`;Codex 的等价参数是 `--dangerously-bypass-approvals-and-sandbox`。不要把 Cursor 的 `--yolo` 原样传给 Codex,也不要把裸启动 `codex` 当作“自动选择了正确角色模型”。如果项目提供 worker 命令校验脚本,校验通过是 `terminal create` 的前置条件。
|
执行模式也必须在创建 worker 时固定,并服从项目覆盖层或用户的明确要求。Cursor 的 YOLO 参数是 `--yolo`;Codex 的等价参数是 `--dangerously-bypass-approvals-and-sandbox`。不要把 Cursor 的 `--yolo` 原样传给 Codex,也不要把裸启动 `codex` 当作“自动选择了正确角色模型”。如果项目提供 worker 命令校验脚本,校验通过是 `terminal create` 的前置条件。
|
||||||
|
|
||||||
**本 kit 对 Cursor 的默认建议**:Test 与 Developer worker 用 `cursor-agent --yolo --model auto`(自动选型,天然偏向高效模型,符合"中低档位"意图,并按当前项目授权启用 YOLO);需要更强时改成具体强模型并保留 `--yolo`。Coordinator 作为强模型脑,通常就是发起编排的那个会话本身。
|
**ACK 对 Cursor 的默认建议**:Test 与 Developer worker 用 `cursor-agent --yolo --model auto`(自动选型,天然偏向高效模型,符合"中低档位"意图,并按当前项目授权启用 YOLO);需要更强时改成具体强模型并保留 `--yolo`。Coordinator 作为强模型脑,通常就是发起编排的那个会话本身。
|
||||||
|
|
||||||
### Codex 默认映射
|
### Codex 默认映射
|
||||||
|
|
||||||
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
## 角色模型(三角色)
|
## 角色模型(三角色)
|
||||||
|
|
||||||
本 kit 默认三个独立 Agent:**Coordinator 只编排、Test 只验证、Developer 只实现**。关键属性是**验证者 ≠ 实现者**:Developer 不能给自己盖章,验证权在独立的 Test。
|
ACK 默认三个独立 Agent:**Coordinator 只编排、Test 只验证、Developer 只实现**。关键属性是**验证者 ≠ 实现者**:Developer 不能给自己盖章,验证权在独立的 Test。
|
||||||
|
|
||||||
| 角色 | 主要职责 | 验证方式 | 不应做的事 |
|
| 角色 | 主要职责 | 验证方式 | 不应做的事 |
|
||||||
|------|----------|----------|------------|
|
|------|----------|----------|------------|
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
# <项目名> Agent 协作协议(项目覆盖层)
|
# <项目名> Agent 协作协议(项目覆盖层)
|
||||||
|
|
||||||
> 本项目基于 ack v<kit_version>(见 `docs/ack/kit/VERSION`)。
|
> 本项目基于 ACK Skill v<ack_version>。通用规范由 `/ack` 从 Skill 自身的
|
||||||
> 稳定规范引用 `docs/ack/kit/core/`,不复制其内容;本文件只填项目自己的差异。
|
> `references/` 读取;本文件只保存项目差异。
|
||||||
>
|
>
|
||||||
> **本文件是「项目覆盖层」,文件名可配置。** 默认放 `docs/ack/project.md`,
|
> **本文件是「项目覆盖层」,文件名可配置。** 默认放 `docs/ack/project.md`,
|
||||||
> 不占用 `AGENTS.md`,避免与团队已有的 `AGENTS.md` 约定冲突。
|
> 不占用 `AGENTS.md`,避免与团队已有的 `AGENTS.md` 约定冲突。
|
||||||
> 若希望 Agent 自动加载,可在项目 `AGENTS.md` 里加一行指向本文件,或直接把本文件命名为 `AGENTS.md`。
|
> 若希望 Agent 自动加载,可在项目 `AGENTS.md` 里加一行指向本文件,或直接把本文件命名为 `AGENTS.md`。
|
||||||
> 无论叫什么,都在 `tasks.yaml` 的 `project.overlayFile` 记录实际路径。
|
> 无论叫什么,都在 `tasks.yaml` 的 `project.overlayFile` 记录实际路径。
|
||||||
>
|
> `docs/ack/` 只保存本项目的 `project.md` 与 `tasks.yaml`,不复制或链接 Skill。
|
||||||
> 目录约定:`docs/ack/` 下只有一个软链接 `kit/`(指向共享框架),加本项目 `project.md` + `tasks.yaml`。
|
|
||||||
|
|
||||||
## 项目概览
|
## 项目概览
|
||||||
|
|
||||||
@@ -19,17 +18,17 @@
|
|||||||
- 任务板:`docs/ack/tasks.yaml`
|
- 任务板:`docs/ack/tasks.yaml`
|
||||||
- 覆盖层文件:`<overlay_file_path>`(默认 `docs/ack/project.md`)
|
- 覆盖层文件:`<overlay_file_path>`(默认 `docs/ack/project.md`)
|
||||||
|
|
||||||
## 稳定规范(不在此重复,直接引用)
|
## 通用规范(由 ACK Skill 按需读取)
|
||||||
|
|
||||||
- 开始一个需求(启动手册):`docs/ack/kit/core/kickoff.md`
|
- 开始需求:`references/kickoff.md`
|
||||||
- 角色模型 / 权限 / 状态机 / 完成定义:`docs/ack/kit/core/roles-and-permissions.md`
|
- 角色、权限、状态机与完成定义:`references/roles-and-permissions.md`
|
||||||
- 模型档位与升级规则:`docs/ack/kit/core/model-routing.md`
|
- 模型档位与升级规则:`references/model-routing.md`
|
||||||
- 闭环流程(含手动模式、worktree 对齐):`docs/ack/kit/core/closed-loop.md`
|
- 闭环流程与 worktree 对齐:`references/closed-loop.md`
|
||||||
- 优化方法(验收信号、三轮策略):`docs/ack/kit/core/optimization-method.md`
|
- 验收信号与三轮策略:`references/optimization-method.md`
|
||||||
- 派发 prompt 模板:`docs/ack/kit/core/prompt-templates.md`
|
- 派发 prompt 模板:`references/prompt-templates.md`
|
||||||
- Orca 编排命令(可选):`docs/ack/kit/core/orca-adapter.md`
|
- Orca 编排命令(可选):`references/orca-adapter.md`
|
||||||
|
|
||||||
## 模型档位(项目可覆盖,默认见 core/model-routing.md)
|
## 模型档位(项目可覆盖,默认见 references/model-routing.md)
|
||||||
|
|
||||||
| 角色 | 默认档位 | 本项目实际 |
|
| 角色 | 默认档位 | 本项目实际 |
|
||||||
|------|----------|------------|
|
|------|----------|------------|
|
||||||
@@ -68,16 +67,12 @@ Test 黑盒复测:
|
|||||||
<browser_regression_command>
|
<browser_regression_command>
|
||||||
```
|
```
|
||||||
|
|
||||||
任务板校验:
|
任务板校验由 `/ack` 使用 Skill 自带的 `scripts/validate_tasks.py` 执行。
|
||||||
|
|
||||||
```bash
|
## 硬规则(其余见 references/)
|
||||||
python3 docs/ack/kit/scripts/validate_tasks.py docs/ack/tasks.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
## 硬规则(其余见 kit/core/)
|
|
||||||
|
|
||||||
- 三角色独立:Coordinator 只编排、Test 只验证、Developer 只实现(验证者 ≠ 实现者)。
|
- 三角色独立:Coordinator 只编排、Test 只验证、Developer 只实现(验证者 ≠ 实现者)。
|
||||||
- 模型分层:Coordinator 用强模型且不亲自跑测试,Test/Developer 用中低模型,必要时升级(见 kit/core/model-routing.md)。
|
- 模型分层:Coordinator 用强模型且不亲自跑测试,Test/Developer 用中低模型,必要时升级(见 references/model-routing.md)。
|
||||||
- `worker_done` 与复测报告都不等于完成。必须 Test 独立复测 + Coordinator 终检后才能 `verified`。
|
- `worker_done` 与复测报告都不等于完成。必须 Test 独立复测 + Coordinator 终检后才能 `verified`。
|
||||||
- 只有 Coordinator 写 `tasks.yaml`;Developer 与 Test 都只读,通过消息回报。
|
- 只有 Coordinator 写 `tasks.yaml`;Developer 与 Test 都只读,通过消息回报。
|
||||||
- 每个任务最多派发 3 轮,仍不过标记 `leftover` 并继续下一个。
|
- 每个任务最多派发 3 轮,仍不过标记 `leftover` 并继续下一个。
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"$id": "https://git.yumee.top/laily/skills/kits/ack/tasks.schema.json",
|
"$id": "https://git.yumee.top/laily/skills/skills/ack/templates/tasks.schema.json",
|
||||||
"title": "Agent Collaboration Kit task board",
|
"title": "ACK task board",
|
||||||
"description": "tasks.yaml 的权威结构。跨语言可用;参考校验实现见 scripts/validate_tasks.py。",
|
"description": "tasks.yaml 的权威结构。跨语言可用;参考校验实现见 scripts/validate_tasks.py。",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["version", "project", "tasks"],
|
"required": ["version", "project", "tasks"],
|
||||||
@@ -10,10 +10,14 @@
|
|||||||
"version": { "type": "integer", "minimum": 1 },
|
"version": { "type": "integer", "minimum": 1 },
|
||||||
"updatedAt": { "type": "string" },
|
"updatedAt": { "type": "string" },
|
||||||
"source": { "type": "string" },
|
"source": { "type": "string" },
|
||||||
"kitVersion": {
|
"ackVersion": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "接入时所基于的 ack 版本,便于日后 diff 升级"
|
"description": "接入时所基于的 ack 版本,便于日后 diff 升级"
|
||||||
},
|
},
|
||||||
|
"kitVersion": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "旧版 ACK 版本字段;兼容读取,新项目应使用 ackVersion"
|
||||||
|
},
|
||||||
"project": {
|
"project": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["name"],
|
"required": ["name"],
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
version: 1
|
version: 1
|
||||||
updatedAt: "<YYYY-MM-DDTHH:mm:ss+TZ>"
|
updatedAt: "<YYYY-MM-DDTHH:mm:ss+TZ>"
|
||||||
source: "Coordinator (PM) Agent"
|
source: "Coordinator (PM) Agent"
|
||||||
kitVersion: "<接入时的 ack 版本,见 kit 根 VERSION>"
|
ackVersion: "<接入时的 ack skill 版本>"
|
||||||
project:
|
project:
|
||||||
name: "<project_name>"
|
name: "<project_name>"
|
||||||
repoPath: "<repo_path>"
|
repoPath: "<repo_path>"
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
# deb-publisher
|
||||||
|
|
||||||
|
帮助 Agent 复用项目已有的 DEB 打包与发布方式,完成构建、包检查、上传和发布验证。
|
||||||
|
|
||||||
|
## 什么时候使用
|
||||||
|
|
||||||
|
当你希望 Agent 处理以下任务时使用:
|
||||||
|
|
||||||
|
- “帮我构建这个项目的 DEB 包”
|
||||||
|
- “把 1.2.3 版本的 DEB 发布到包仓库”
|
||||||
|
- “看看项目里的 DEB 发布流程”
|
||||||
|
- “检查这个 DEB 是否可以发布”
|
||||||
|
|
||||||
|
只想在本机安装一个 `.deb`,或者要构建 RPM、Docker 镜像时,不需要使用这个 skill。
|
||||||
|
|
||||||
|
## 使用前准备
|
||||||
|
|
||||||
|
请准备或确认:
|
||||||
|
|
||||||
|
- 要构建或发布的版本号。
|
||||||
|
- 项目已经配置好构建脚本、Make 目标或 `debian/` 目录;上传脚本由 skill 自带,
|
||||||
|
不需要放进项目。
|
||||||
|
- 发布所需的令牌已经放入项目约定的环境变量或密钥系统。
|
||||||
|
- 如果要真实发布,明确告诉 Agent 目标仓库以及是否允许覆盖同版本。
|
||||||
|
|
||||||
|
不要把令牌直接粘贴到对话、命令参数或项目文件中。
|
||||||
|
|
||||||
|
## 使用示例
|
||||||
|
|
||||||
|
仅分析,不产生或上传包:
|
||||||
|
|
||||||
|
```text
|
||||||
|
看看这个项目的 DEB 是怎么构建和发布的。
|
||||||
|
```
|
||||||
|
|
||||||
|
只构建和检查,不上传:
|
||||||
|
|
||||||
|
```text
|
||||||
|
使用 deb-publisher 构建 1.2.3 的 DEB,检查包元数据和内容,不要上传。
|
||||||
|
```
|
||||||
|
|
||||||
|
构建并发布:
|
||||||
|
|
||||||
|
```text
|
||||||
|
使用 deb-publisher 构建并发布 1.2.3 的 amd64 DEB 到项目已配置的测试仓库。
|
||||||
|
```
|
||||||
|
|
||||||
|
发布已有产物:
|
||||||
|
|
||||||
|
```text
|
||||||
|
使用 deb-publisher 检查并发布 ./dist/example_1.2.3_amd64.deb。
|
||||||
|
```
|
||||||
|
|
||||||
|
## Agent 会做什么
|
||||||
|
|
||||||
|
Agent 会优先发现和复用项目已有入口,然后:
|
||||||
|
|
||||||
|
1. 确认版本、架构、产物路径、目标仓库和授权范围。
|
||||||
|
2. 构建 DEB,或定位你指定的已有产物。
|
||||||
|
3. 检查包的元数据、内容和 SHA-256。
|
||||||
|
4. 在你明确要求发布时,通过 skill 自带的 `scripts/upload_deb.sh` 上传。
|
||||||
|
5. 检查服务端响应,并在仓库支持时确认该版本已经可见。
|
||||||
|
|
||||||
|
## 如何判断完成
|
||||||
|
|
||||||
|
结果中应包含包名、版本、架构、产物路径、SHA-256,以及构建、上传和仓库可见性的
|
||||||
|
独立状态。异步索引尚未完成时,Agent 应明确说明“上传已接受,索引待更新”。
|
||||||
|
|
||||||
|
## 上传协议
|
||||||
|
|
||||||
|
自带脚本适用于接收 multipart 字段 `package`、`token` 和 `repository_name` 的
|
||||||
|
HTTP DEB 仓库,默认路径是 `/api/v2/upload/package`。使用以下环境变量:
|
||||||
|
|
||||||
|
- `DEB_SERVER_URL`:仓库服务地址。
|
||||||
|
- `DEB_TOKEN`:认证令牌。
|
||||||
|
- `DEB_REPOSITORY`:目标仓库名。
|
||||||
|
- `DEB_UPLOAD_PATH`:可选的上传路径覆盖。
|
||||||
|
|
||||||
|
令牌只从环境变量读取,不作为命令行参数传递。若你的仓库使用其他 API 协议,应先告诉
|
||||||
|
Agent,不要直接套用该脚本。
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
---
|
||||||
|
name: deb-publisher
|
||||||
|
description: >-
|
||||||
|
构建并发布 Debian DEB 包:发现项目已有的 Makefile 和打包入口,使用 skill 自带的
|
||||||
|
通用上传脚本提交包,校验包元数据与内容,并验证发布结果。触发词:构建 deb、
|
||||||
|
发布 deb、上传 deb、提交 deb、推送 apt 仓库、打 Debian 包。仅分析打包逻辑时也可使用,
|
||||||
|
但不会在未获授权时执行上传。
|
||||||
|
---
|
||||||
|
|
||||||
|
# DEB Publisher
|
||||||
|
|
||||||
|
复用项目已有发布约定,安全地完成“发现入口 → 构建 → 检查 → 上传 → 验证”。
|
||||||
|
|
||||||
|
## 何时使用
|
||||||
|
|
||||||
|
- 用户要求构建、发布、上传或提交 `.deb` 包。
|
||||||
|
- 用户要求梳理或接通项目现有的 DEB 发布流程。
|
||||||
|
- 用户要求把已经生成的 `.deb` 推送到 APT/DEB 包仓库。
|
||||||
|
|
||||||
|
## 不适用
|
||||||
|
|
||||||
|
- 只需要安装或卸载本地 DEB 包。
|
||||||
|
- 目标是 RPM、APK、容器镜像或语言包管理器。
|
||||||
|
- 用户只要求设计全新的 Debian 打包体系;此时应先完成方案设计。
|
||||||
|
|
||||||
|
## 工作流
|
||||||
|
|
||||||
|
### 1. 发现项目约定
|
||||||
|
|
||||||
|
从项目根目录查找,不预设文件位置:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rg -n -i --hidden --glob '!.git' \
|
||||||
|
'build-deb|upload-deb|publish-deb|dpkg-deb|debuild|curl.*deb|\.deb\b|aptly|reprepro'
|
||||||
|
```
|
||||||
|
|
||||||
|
重点检查:
|
||||||
|
|
||||||
|
- `Makefile`、CI 配置、`debian/`、构建脚本和发布文档。
|
||||||
|
- 版本号、包名、架构、产物目录和仓库名如何传入。
|
||||||
|
- 发布端点、认证方式以及发布是否由构建目标自动触发。
|
||||||
|
- 当前工作树和目标版本是否匹配。
|
||||||
|
|
||||||
|
优先复用已有构建入口。上传默认使用本 skill 的 `scripts/upload_deb.sh`,不要把它
|
||||||
|
复制到项目中;仅当目标仓库协议不兼容时才复用或修改项目专属上传逻辑。
|
||||||
|
|
||||||
|
### 2. 确认发布边界
|
||||||
|
|
||||||
|
上传是外部写操作。仅当用户明确要求发布、上传或提交时执行;若用户只要求查看、
|
||||||
|
诊断或构建,则停在相应阶段。
|
||||||
|
|
||||||
|
执行上传前确认:
|
||||||
|
|
||||||
|
- 目标服务和仓库来自项目配置或用户输入,不猜测生产端点。
|
||||||
|
- 认证令牌已通过环境变量或密钥系统提供。
|
||||||
|
- 目标版本、架构和产物路径能够从构建配置推导。
|
||||||
|
- 相同版本是否允许覆盖;无法确认且可能覆盖时,先询问用户。
|
||||||
|
|
||||||
|
绝不把令牌写入命令输出、文件、提交或最终回复。不要用 `set -x` 执行含凭据的脚本。
|
||||||
|
|
||||||
|
### 3. 构建包
|
||||||
|
|
||||||
|
使用项目声明的构建目标,并显式传入版本。例如项目提供 Make 目标时:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make build-deb VERSION="$RELEASE_VERSION"
|
||||||
|
```
|
||||||
|
|
||||||
|
如果构建目标会自动上传,而当前仅获构建授权,应改用其纯构建子目标。执行前检查
|
||||||
|
所需工具和环境,例如 Docker、`dpkg-deb`、编译器、SSH 访问或前端工具链。
|
||||||
|
|
||||||
|
不得擅自清理宽泛目录。若脚本包含 `rm -rf`,先解析并确认目标是明确、受限的构建目录。
|
||||||
|
|
||||||
|
### 4. 上传前检查
|
||||||
|
|
||||||
|
定位唯一目标产物;若匹配多个包,不凭文件时间猜测:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
find <artifact-dir> -maxdepth 2 -type f -name '*.deb' -print
|
||||||
|
dpkg-deb --info <package.deb>
|
||||||
|
dpkg-deb --contents <package.deb>
|
||||||
|
```
|
||||||
|
|
||||||
|
至少验证:
|
||||||
|
|
||||||
|
- 文件存在、非空且 `dpkg-deb --info` 成功。
|
||||||
|
- `Package`、`Version`、`Architecture` 与本次发布一致。
|
||||||
|
- 包内容包含预期的主程序或关键文件。
|
||||||
|
- maintainer scripts 存在时权限正确,且没有明显的宿主机破坏性操作。
|
||||||
|
|
||||||
|
建议记录 SHA-256:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sha256sum <package.deb>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 发布
|
||||||
|
|
||||||
|
解析当前 `SKILL.md` 所在目录,以绝对路径调用随 skill 分发的脚本:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DEB_SERVER_URL="$DEB_SERVER_URL" \
|
||||||
|
DEB_TOKEN="$DEB_TOKEN" \
|
||||||
|
DEB_REPOSITORY="$DEB_REPOSITORY" \
|
||||||
|
<skill-dir>/scripts/upload_deb.sh <exact-package-path.deb>
|
||||||
|
```
|
||||||
|
|
||||||
|
不要把脚本复制进当前项目,也不要将 token 作为命令行参数。脚本默认请求
|
||||||
|
`/api/v2/upload/package`,以 multipart 字段 `package`、`token`、
|
||||||
|
`repository_name` 上传,接受 `200` 和 `201` 为成功。端点路径不同时可设置
|
||||||
|
`DEB_UPLOAD_PATH`。
|
||||||
|
|
||||||
|
调用前确认目标服务使用上述协议;不兼容时不要强行调用。传入刚刚校验过的确切路径,
|
||||||
|
不要使用宽泛 glob。项目已有 `make upload-deb` 时,检查它是否只是包装了同一协议:
|
||||||
|
如果是,直接使用 skill 脚本;若 CI 或其他人仍依赖 Make 目标,可将目标改为调用已安装
|
||||||
|
skill 的脚本,但不要提交脚本副本。
|
||||||
|
|
||||||
|
脚本支持多个确切文件路径,会汇总每个文件的结果,并在任一失败时返回非零。
|
||||||
|
|
||||||
|
### 6. 验证与汇报
|
||||||
|
|
||||||
|
发布成功不能只依据“curl 已执行”。综合检查:
|
||||||
|
|
||||||
|
- 上传命令退出码为零。
|
||||||
|
- HTTP 状态和响应体明确表示成功。
|
||||||
|
- 若仓库提供只读查询、索引或下载地址,再确认该包和版本已可见。
|
||||||
|
- 若索引更新是异步的,报告“上传已接受,索引尚待更新”,不要声称已完全可用。
|
||||||
|
|
||||||
|
最终回复给出:
|
||||||
|
|
||||||
|
- 包名、版本、架构。
|
||||||
|
- 产物路径和 SHA-256。
|
||||||
|
- 目标服务/仓库的非敏感标识。
|
||||||
|
- 构建、上传及仓库可见性各自的验证结果。
|
||||||
|
- 任何未完成项或回滚/覆盖风险。
|
||||||
|
|
||||||
|
## 修改已有发布逻辑时
|
||||||
|
|
||||||
|
- 保持项目现有变量名和调用入口,避免无关重构。
|
||||||
|
- 修复行为缺陷时增加最小静态检查或可离线运行的测试。
|
||||||
|
- 可用 `bash -n` 检查脚本语法;项目有 ShellCheck 时一并运行。
|
||||||
|
- skill 自带上传脚本是 SSOT;通用上传行为的修改应落在
|
||||||
|
`skills/deb-publisher/scripts/upload_deb.sh`,不要同步复制到业务项目。
|
||||||
|
- 不通过真实生产上传来测试脚本,除非用户明确授权并给出测试版本或测试仓库。
|
||||||
|
|
||||||
|
## 完成标准
|
||||||
|
|
||||||
|
- 仅分析:入口、调用链、配置来源和风险已被准确说明。
|
||||||
|
- 仅构建:DEB 已生成,元数据、内容和校验和通过检查,未发生上传。
|
||||||
|
- 发布:构建检查通过,服务端接受上传,且仓库可见性已验证或被准确标记为待更新。
|
||||||
Executable
+133
@@ -0,0 +1,133 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
DEB_SERVER_URL=https://deb.example.com \
|
||||||
|
DEB_TOKEN=secret \
|
||||||
|
DEB_REPOSITORY=main \
|
||||||
|
upload_deb.sh FILE.deb [FILE.deb ...]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-s SERVER_URL Override DEB_SERVER_URL
|
||||||
|
-n REPOSITORY Override DEB_REPOSITORY
|
||||||
|
-p UPLOAD_PATH Override DEB_UPLOAD_PATH (default: /api/v2/upload/package)
|
||||||
|
-h Show help
|
||||||
|
|
||||||
|
The endpoint must accept multipart fields named package, token, and
|
||||||
|
repository_name. Authentication is read only from DEB_TOKEN so it is not
|
||||||
|
exposed in the process command line.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
server_url=${DEB_SERVER_URL:-}
|
||||||
|
repository=${DEB_REPOSITORY:-}
|
||||||
|
upload_path=${DEB_UPLOAD_PATH:-/api/v2/upload/package}
|
||||||
|
token=${DEB_TOKEN:-}
|
||||||
|
|
||||||
|
while getopts ":s:n:p:h" option; do
|
||||||
|
case "$option" in
|
||||||
|
s) server_url=$OPTARG ;;
|
||||||
|
n) repository=$OPTARG ;;
|
||||||
|
p) upload_path=$OPTARG ;;
|
||||||
|
h) usage; exit 0 ;;
|
||||||
|
:) echo "Error: -$OPTARG requires a value" >&2; usage >&2; exit 2 ;;
|
||||||
|
\?) echo "Error: unknown option -$OPTARG" >&2; usage >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
shift $((OPTIND - 1))
|
||||||
|
|
||||||
|
if [[ -z "$server_url" || -z "$repository" || -z "$token" || $# -eq 0 ]]; then
|
||||||
|
echo "Error: DEB_SERVER_URL, DEB_TOKEN, DEB_REPOSITORY, and at least one file are required." >&2
|
||||||
|
usage >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
if [[ "$upload_path" != /* ]]; then
|
||||||
|
echo "Error: upload path must start with /" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
if ! command -v curl >/dev/null 2>&1; then
|
||||||
|
echo "Error: curl is required." >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
server_url=${server_url%/}
|
||||||
|
success_count=0
|
||||||
|
fail_count=0
|
||||||
|
response_file=
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
if [[ -n "$response_file" && -f "$response_file" ]]; then
|
||||||
|
rm -f -- "$response_file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
print_response() {
|
||||||
|
local file=$1
|
||||||
|
if command -v jq >/dev/null 2>&1 && jq -e . "$file" >/dev/null 2>&1; then
|
||||||
|
jq . "$file"
|
||||||
|
else
|
||||||
|
cat -- "$file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
for package_file in "$@"; do
|
||||||
|
if [[ ! -f "$package_file" ]]; then
|
||||||
|
echo "Skip: file not found: $package_file" >&2
|
||||||
|
fail_count=$((fail_count + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [[ "$package_file" != *.deb ]]; then
|
||||||
|
echo "Skip: not a .deb file: $package_file" >&2
|
||||||
|
fail_count=$((fail_count + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [[ ! -s "$package_file" ]]; then
|
||||||
|
echo "Skip: empty file: $package_file" >&2
|
||||||
|
fail_count=$((fail_count + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
response_file=$(mktemp)
|
||||||
|
echo "Uploading $(basename -- "$package_file") to $server_url (repository $repository)..."
|
||||||
|
|
||||||
|
http_code=000
|
||||||
|
if http_code=$(curl --silent --show-error \
|
||||||
|
--output "$response_file" \
|
||||||
|
--write-out "%{http_code}" \
|
||||||
|
--request POST \
|
||||||
|
"$server_url$upload_path" \
|
||||||
|
--form "package=@${package_file};type=application/vnd.debian.binary-package" \
|
||||||
|
--form "token=${token}" \
|
||||||
|
--form "repository_name=${repository}"); then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
echo "Failed (transport error): $(basename -- "$package_file")" >&2
|
||||||
|
print_response "$response_file" >&2
|
||||||
|
fail_count=$((fail_count + 1))
|
||||||
|
cleanup
|
||||||
|
response_file=
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$http_code" == 200 || "$http_code" == 201 ]]; then
|
||||||
|
echo "Success ($http_code): $(basename -- "$package_file")"
|
||||||
|
print_response "$response_file"
|
||||||
|
success_count=$((success_count + 1))
|
||||||
|
else
|
||||||
|
echo "Failed ($http_code): $(basename -- "$package_file")" >&2
|
||||||
|
print_response "$response_file" >&2
|
||||||
|
fail_count=$((fail_count + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
cleanup
|
||||||
|
response_file=
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Done. Success: $success_count, Failed: $fail_count"
|
||||||
|
if ((fail_count > 0)); then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# skiff
|
||||||
|
|
||||||
|
`skiff` 用于创建、维护、安装和发布团队自研的 Agent Skill。Skill 的唯一来源位于
|
||||||
|
`~/.skills/skills/<name>/`,安装到各 Agent 时使用软链接。
|
||||||
|
|
||||||
|
## 什么时候使用
|
||||||
|
|
||||||
|
- 想把项目里的重复工作沉淀成一个 skill。
|
||||||
|
- 想完善、校验、转正或发布已有 skill。
|
||||||
|
- 想把自研 skill 安装到当前项目或全局 Agent。
|
||||||
|
- 想检查并修复 skill 软链接。
|
||||||
|
|
||||||
|
## 创建一个 skill
|
||||||
|
|
||||||
|
```bash
|
||||||
|
skiff create my-skill \
|
||||||
|
--idea "描述这个 skill 要解决的重复问题" \
|
||||||
|
--from-project .
|
||||||
|
```
|
||||||
|
|
||||||
|
命令会在 `~/.skills/.drafts/my-skill/` 创建:
|
||||||
|
|
||||||
|
- `SKILL.md`:给 Agent 阅读的工作流与约束。
|
||||||
|
- `README.md`:给人类阅读的用途、准备事项、示例和完成标准。
|
||||||
|
- `brief.yaml`:草稿来源信息,转正时自动移除。
|
||||||
|
|
||||||
|
完善 `SKILL.md` 和 `README.md` 后运行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
skiff check my-skill
|
||||||
|
skiff finalize my-skill
|
||||||
|
```
|
||||||
|
|
||||||
|
## 提交和发布
|
||||||
|
|
||||||
|
只提交:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
skiff publish skills/my-skill -m "add my-skill"
|
||||||
|
```
|
||||||
|
|
||||||
|
提交并推送:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
skiff publish skills/my-skill -m "add my-skill" --push
|
||||||
|
```
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
安装到当前项目的 Codex:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
skiff add my-skill -a codex -y
|
||||||
|
```
|
||||||
|
|
||||||
|
安装到全局 Codex:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
skiff add my-skill -a codex -g -y
|
||||||
|
```
|
||||||
|
|
||||||
|
## 如何判断完成
|
||||||
|
|
||||||
|
- `skiff check <name>` 输出校验通过。
|
||||||
|
- 正式 skill 同时包含 `SKILL.md` 和 `README.md`。
|
||||||
|
- `skiff status` 或 `skiff doctor` 显示目标软链接正常。
|
||||||
|
|
||||||
+13
-12
@@ -2,9 +2,9 @@
|
|||||||
name: skiff
|
name: skiff
|
||||||
description: >-
|
description: >-
|
||||||
创建和维护 ~/.skills 自研 skill:把项目开发中产生的想法提炼为草稿,完善并校验后发布,
|
创建和维护 ~/.skills 自研 skill:把项目开发中产生的想法提炼为草稿,完善并校验后发布,
|
||||||
用 skiff add/remove 在项目及全局挂卸 skill,或用 skiff kit init 初始化项目规范包。
|
用 skiff add/remove 在项目及全局挂卸 skill,或用 skiff init 初始化 skill 项目状态。
|
||||||
触发词:skiff、自研 skill、创建 skill、想做一个 skill、publish skill、安装自研 skill、
|
触发词:skiff、自研 skill、创建 skill、想做一个 skill、publish skill、安装自研 skill、
|
||||||
更新 skill 到项目、初始化 kit。
|
更新 skill 到项目、初始化 skill。
|
||||||
---
|
---
|
||||||
|
|
||||||
# skiff 自研 Skill 工作流
|
# skiff 自研 Skill 工作流
|
||||||
@@ -38,15 +38,16 @@ skiff add <name> -a cursor -a claude -a codex -y
|
|||||||
skiff create <name> --idea "<用户原始想法>" --from-project .
|
skiff create <name> --idea "<用户原始想法>" --from-project .
|
||||||
```
|
```
|
||||||
|
|
||||||
4. 编辑 `~/.skills/.drafts/<name>/SKILL.md`,完善触发条件、不适用场景、步骤、边界与验证方法。仅在确有必要时增加 `references/`、`scripts/` 或 `assets/`。
|
4. 编辑 `~/.skills/.drafts/<name>/SKILL.md`,完善触发条件、不适用场景、步骤、边界与验证方法。
|
||||||
5. 不要把项目专属路径、私有业务规则、一次性命令或密钥复制到通用 skill。
|
5. 编辑同目录的 `README.md`,用面向人类的语言说明用途、准备事项、可直接复制的请求示例、Agent 会做什么以及如何判断完成。README 不应复述 Agent 内部指令。
|
||||||
6. 运行校验并修复所有问题:
|
6. 仅在确有必要时增加 `references/`、`scripts/` 或 `assets/`。不要把项目专属路径、私有业务规则、一次性命令或密钥复制到通用 skill。
|
||||||
|
7. 运行校验并修复所有问题:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
skiff check <name>
|
skiff check <name>
|
||||||
```
|
```
|
||||||
|
|
||||||
7. 向用户展示名称、description、核心步骤和验证方式。获得确认后再转正:
|
8. 向用户展示名称、description、README 的人类使用方式、核心步骤和验证方式。获得确认后再转正:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
skiff finalize <name>
|
skiff finalize <name>
|
||||||
@@ -135,15 +136,15 @@ skiff rm discussion-notes -g -y # rm 别名
|
|||||||
skiff add --list
|
skiff add --list
|
||||||
```
|
```
|
||||||
|
|
||||||
初始化项目 kit:
|
使用 Skill 自带模板初始化项目状态:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
skiff kit init ack # 当前项目,默认软链接到 SSOT
|
skiff init ack
|
||||||
skiff kit init ack --project ~/app # 指定项目
|
skiff init ack --project ~/app
|
||||||
skiff kit init ack --copy # 用户明确要求时整份复制
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`skiff` 只负责可靠地创建 kit 文件。需要分析项目并完善 ACK 配置、检查接入状态或
|
`skiff` 只负责可靠地生成项目状态文件,不复制或链接 Skill。需要分析项目并完善
|
||||||
|
ACK 配置、检查接入状态或
|
||||||
运行三角色闭环时,显式调用全局 `/ack` skill。
|
运行三角色闭环时,显式调用全局 `/ack` skill。
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -172,7 +173,7 @@ skiff kit init ack --copy # 用户明确要求时整份复制
|
|||||||
| `create <name> --idea TEXT [--from-project PATH]` | 创建自研 skill 草稿 |
|
| `create <name> --idea TEXT [--from-project PATH]` | 创建自研 skill 草稿 |
|
||||||
| `check <name>` | 校验草稿或正式 skill |
|
| `check <name>` | 校验草稿或正式 skill |
|
||||||
| `finalize <name>` | 校验草稿并转为正式 skill |
|
| `finalize <name>` | 校验草稿并转为正式 skill |
|
||||||
| `kit init <name> [--project DIR] [--copy]` | 在项目中初始化 kit |
|
| `init <name> [--project DIR]` | 使用 skill 模板初始化项目状态 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -8,15 +8,16 @@ REPO_ROOT = Path(__file__).resolve().parents[1]
|
|||||||
|
|
||||||
|
|
||||||
class AckSkillContentTests(unittest.TestCase):
|
class AckSkillContentTests(unittest.TestCase):
|
||||||
def test_ack_skill_routes_to_kit_without_modifying_agent_instructions(self) -> None:
|
def test_ack_skill_owns_resources_without_modifying_agent_instructions(self) -> None:
|
||||||
content = (REPO_ROOT / "skills" / "ack" / "SKILL.md").read_text(encoding="utf-8")
|
content = (REPO_ROOT / "skills" / "ack" / "SKILL.md").read_text(encoding="utf-8")
|
||||||
|
|
||||||
for expected in (
|
for expected in (
|
||||||
"skiff kit init ack --project <project-root>",
|
"skiff init ack --project <project-root>",
|
||||||
"docs/ack/project.md",
|
"docs/ack/project.md",
|
||||||
"docs/ack/tasks.yaml",
|
"docs/ack/tasks.yaml",
|
||||||
"tasks: []",
|
"tasks: []",
|
||||||
"validate_tasks.py",
|
"validate_tasks.py",
|
||||||
|
"references/kickoff.md",
|
||||||
"不要修改项目的 `AGENTS.md`",
|
"不要修改项目的 `AGENTS.md`",
|
||||||
"当前会话担任 Coordinator",
|
"当前会话担任 Coordinator",
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -28,6 +28,13 @@ class CreateWorkflowTests(unittest.TestCase):
|
|||||||
"---\n\n# Skill 名称\n\n## 步骤\n\n1. 第一步\n",
|
"---\n\n# Skill 名称\n\n## 步骤\n\n1. 第一步\n",
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
|
template.joinpath("README.md").write_text(
|
||||||
|
"# skill-name\n\n"
|
||||||
|
"用一句话告诉使用者这个 skill 能解决什么问题。\n\n"
|
||||||
|
"给出用户可以直接说出的典型请求。\n\n"
|
||||||
|
"给出一条可以直接交给 Agent 的示例请求。\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
project_skill = self.skills_home / "skills" / "skiff"
|
project_skill = self.skills_home / "skills" / "skiff"
|
||||||
project_skill.mkdir()
|
project_skill.mkdir()
|
||||||
project_skill.joinpath("SKILL.md").write_text(
|
project_skill.joinpath("SKILL.md").write_text(
|
||||||
@@ -36,6 +43,10 @@ class CreateWorkflowTests(unittest.TestCase):
|
|||||||
"---\n\n# skiff\n\n## 步骤\n\n1. 维护 skill。\n",
|
"---\n\n# skiff\n\n## 步骤\n\n1. 维护 skill。\n",
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
|
project_skill.joinpath("README.md").write_text(
|
||||||
|
"# skiff\n\n供人类使用的 skiff 说明。\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
def tearDown(self) -> None:
|
def tearDown(self) -> None:
|
||||||
self.temp_dir.cleanup()
|
self.temp_dir.cleanup()
|
||||||
@@ -109,6 +120,10 @@ class CreateWorkflowTests(unittest.TestCase):
|
|||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
draft = self.skills_home / ".drafts" / "migration-review"
|
draft = self.skills_home / ".drafts" / "migration-review"
|
||||||
self.assertTrue((draft / "SKILL.md").is_file())
|
self.assertTrue((draft / "SKILL.md").is_file())
|
||||||
|
self.assertEqual(
|
||||||
|
(draft / "README.md").read_text(encoding="utf-8").splitlines()[0],
|
||||||
|
"# migration-review",
|
||||||
|
)
|
||||||
brief = (draft / "brief.yaml").read_text(encoding="utf-8")
|
brief = (draft / "brief.yaml").read_text(encoding="utf-8")
|
||||||
self.assertIn("idea: 检查数据库迁移", brief)
|
self.assertIn("idea: 检查数据库迁移", brief)
|
||||||
self.assertIn(f"source_project: {project.resolve()}", brief)
|
self.assertIn(f"source_project: {project.resolve()}", brief)
|
||||||
@@ -134,12 +149,19 @@ class CreateWorkflowTests(unittest.TestCase):
|
|||||||
"---\n\n# 数据库迁移检查\n\n## 步骤\n\n1. 检查迁移与回滚路径。\n",
|
"---\n\n# 数据库迁移检查\n\n## 步骤\n\n1. 检查迁移与回滚路径。\n",
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
|
draft.joinpath("README.md").write_text(
|
||||||
|
"# migration-review\n\n"
|
||||||
|
"用于检查数据库迁移安全性。\n\n"
|
||||||
|
"## 使用示例\n\n请检查这次数据库迁移。\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
result = self.run_skiff("finalize", "migration-review")
|
result = self.run_skiff("finalize", "migration-review")
|
||||||
|
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
final = self.skills_home / "skills" / "migration-review"
|
final = self.skills_home / "skills" / "migration-review"
|
||||||
self.assertTrue((final / "SKILL.md").is_file())
|
self.assertTrue((final / "SKILL.md").is_file())
|
||||||
|
self.assertTrue((final / "README.md").is_file())
|
||||||
self.assertFalse((final / "brief.yaml").exists())
|
self.assertFalse((final / "brief.yaml").exists())
|
||||||
self.assertFalse(draft.exists())
|
self.assertFalse(draft.exists())
|
||||||
|
|
||||||
@@ -152,12 +174,31 @@ class CreateWorkflowTests(unittest.TestCase):
|
|||||||
"---\n\n# 参考检查\n\n读取 [规范](references/rules.md)。\n",
|
"---\n\n# 参考检查\n\n读取 [规范](references/rules.md)。\n",
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
|
draft.joinpath("README.md").write_text(
|
||||||
|
"# reference-check\n\n用于检查本地参考资料。\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
result = self.run_skiff("check", "reference-check")
|
result = self.run_skiff("check", "reference-check")
|
||||||
|
|
||||||
self.assertNotEqual(result.returncode, 0)
|
self.assertNotEqual(result.returncode, 0)
|
||||||
self.assertIn("引用文件不存在", result.stderr)
|
self.assertIn("引用文件不存在", result.stderr)
|
||||||
|
|
||||||
|
def test_check_rejects_missing_readme(self) -> None:
|
||||||
|
draft = self.skills_home / ".drafts" / "missing-readme"
|
||||||
|
draft.mkdir(parents=True)
|
||||||
|
draft.joinpath("SKILL.md").write_text(
|
||||||
|
"---\nname: missing-readme\ndescription: >-\n"
|
||||||
|
" 检查 skill 是否包含人类使用说明。用于维护 skill 结构时。\n"
|
||||||
|
"---\n\n# README 检查\n\n1. 检查 README。\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
result = self.run_skiff("check", "missing-readme")
|
||||||
|
|
||||||
|
self.assertNotEqual(result.returncode, 0)
|
||||||
|
self.assertIn("缺少 README.md", result.stderr)
|
||||||
|
|
||||||
def test_finalize_does_not_overwrite_existing_owned_skill(self) -> None:
|
def test_finalize_does_not_overwrite_existing_owned_skill(self) -> None:
|
||||||
created = self.run_skiff("create", "collision-check", "--idea", "检查名称冲突")
|
created = self.run_skiff("create", "collision-check", "--idea", "检查名称冲突")
|
||||||
self.assertEqual(created.returncode, 0, created.stderr)
|
self.assertEqual(created.returncode, 0, created.stderr)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class ProjectSkillContentTests(unittest.TestCase):
|
|||||||
"skiff check <name>",
|
"skiff check <name>",
|
||||||
"~/.skills/skills/<name>/",
|
"~/.skills/skills/<name>/",
|
||||||
"第三方 skill",
|
"第三方 skill",
|
||||||
"skiff kit init ack",
|
"skiff init ack",
|
||||||
"显式调用全局 `/ack` skill",
|
"显式调用全局 `/ack` skill",
|
||||||
):
|
):
|
||||||
self.assertIn(expected, content)
|
self.assertIn(expected, content)
|
||||||
|
|||||||
@@ -11,22 +11,23 @@ from pathlib import Path
|
|||||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
|
||||||
|
|
||||||
class KitInitTests(unittest.TestCase):
|
class SkillInitTests(unittest.TestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
self.temp_dir = tempfile.TemporaryDirectory()
|
self.temp_dir = tempfile.TemporaryDirectory()
|
||||||
self.home = Path(self.temp_dir.name)
|
self.home = Path(self.temp_dir.name)
|
||||||
self.skills_home = self.home / ".skills"
|
self.skills_home = self.home / ".skills"
|
||||||
kit = self.skills_home / "kits" / "ack"
|
skill = self.skills_home / "skills" / "ack"
|
||||||
(kit / "templates").mkdir(parents=True)
|
(skill / "templates").mkdir(parents=True)
|
||||||
(kit / "scripts").mkdir()
|
(skill / "scripts").mkdir()
|
||||||
(kit / "VERSION").write_text("1.2.3\n", encoding="utf-8")
|
(skill / "SKILL.md").write_text("---\nname: ack\n---\n", encoding="utf-8")
|
||||||
(kit / "templates" / "project.template.md").write_text(
|
(skill / "VERSION").write_text("1.2.3\n", encoding="utf-8")
|
||||||
"# <project_name>\nversion=<kit_version>\npath=<overlay_file_path>\n",
|
(skill / "templates" / "project.template.md").write_text(
|
||||||
|
"# <project_name>\nversion=<ack_version>\npath=<overlay_file_path>\n",
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
(kit / "templates" / "tasks.template.yaml").write_text(
|
(skill / "templates" / "tasks.template.yaml").write_text(
|
||||||
'updatedAt: "<YYYY-MM-DDTHH:mm:ss+TZ>"\n'
|
'updatedAt: "<YYYY-MM-DDTHH:mm:ss+TZ>"\n'
|
||||||
'kitVersion: "<接入时的 ack 版本,见 kit 根 VERSION>"\n'
|
'ackVersion: "<接入时的 ack skill 版本>"\n'
|
||||||
'project:\n'
|
'project:\n'
|
||||||
' name: "<project_name>"\n'
|
' name: "<project_name>"\n'
|
||||||
' repoPath: "<repo_path>"\n'
|
' repoPath: "<repo_path>"\n'
|
||||||
@@ -50,16 +51,16 @@ class KitInitTests(unittest.TestCase):
|
|||||||
check=False,
|
check=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_init_creates_symlink_and_rendered_project_files(self) -> None:
|
def test_init_creates_only_rendered_project_files(self) -> None:
|
||||||
project = self.home / "sample-app"
|
project = self.home / "sample-app"
|
||||||
project.mkdir()
|
project.mkdir()
|
||||||
|
|
||||||
result = self.run_skiff("kit", "init", "ack", "--project", str(project))
|
result = self.run_skiff("init", "ack", "--project", str(project))
|
||||||
|
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
target = project / "docs" / "ack"
|
target = project / "docs" / "ack"
|
||||||
self.assertTrue((target / "kit").is_symlink())
|
self.assertFalse((target / "kit").exists())
|
||||||
self.assertEqual((target / "kit").resolve(), self.skills_home / "kits" / "ack")
|
self.assertFalse((target / "framework").exists())
|
||||||
project_content = (target / "project.md").read_text(encoding="utf-8")
|
project_content = (target / "project.md").read_text(encoding="utf-8")
|
||||||
tasks_content = (target / "tasks.yaml").read_text(encoding="utf-8")
|
tasks_content = (target / "tasks.yaml").read_text(encoding="utf-8")
|
||||||
self.assertIn("# sample-app", project_content)
|
self.assertIn("# sample-app", project_content)
|
||||||
@@ -67,18 +68,6 @@ class KitInitTests(unittest.TestCase):
|
|||||||
self.assertIn(f'repoPath: "{project}"', tasks_content)
|
self.assertIn(f'repoPath: "{project}"', tasks_content)
|
||||||
self.assertNotIn("<project_name>", tasks_content)
|
self.assertNotIn("<project_name>", tasks_content)
|
||||||
|
|
||||||
def test_init_copy_mode_copies_kit(self) -> None:
|
|
||||||
project = self.home / "copied-app"
|
|
||||||
project.mkdir()
|
|
||||||
|
|
||||||
result = self.run_skiff("kit", "init", "ack", "--copy", "--project", str(project))
|
|
||||||
|
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
|
||||||
target = project / "docs" / "ack" / "kit"
|
|
||||||
self.assertTrue(target.is_dir())
|
|
||||||
self.assertFalse(target.is_symlink())
|
|
||||||
self.assertEqual((target / "VERSION").read_text(encoding="utf-8"), "1.2.3\n")
|
|
||||||
|
|
||||||
def test_init_refuses_to_overwrite_existing_files(self) -> None:
|
def test_init_refuses_to_overwrite_existing_files(self) -> None:
|
||||||
project = self.home / "existing-app"
|
project = self.home / "existing-app"
|
||||||
target = project / "docs" / "ack"
|
target = project / "docs" / "ack"
|
||||||
@@ -86,22 +75,28 @@ class KitInitTests(unittest.TestCase):
|
|||||||
existing = target / "project.md"
|
existing = target / "project.md"
|
||||||
existing.write_text("keep me", encoding="utf-8")
|
existing.write_text("keep me", encoding="utf-8")
|
||||||
|
|
||||||
result = self.run_skiff("kit", "init", "ack", "--project", str(project))
|
result = self.run_skiff("init", "ack", "--project", str(project))
|
||||||
|
|
||||||
self.assertNotEqual(result.returncode, 0)
|
self.assertNotEqual(result.returncode, 0)
|
||||||
self.assertIn("拒绝覆盖已有路径", result.stderr)
|
self.assertIn("拒绝覆盖已有路径", result.stderr)
|
||||||
self.assertEqual(existing.read_text(encoding="utf-8"), "keep me")
|
self.assertEqual(existing.read_text(encoding="utf-8"), "keep me")
|
||||||
self.assertFalse((target / "kit").exists())
|
self.assertFalse((target / "tasks.yaml").exists())
|
||||||
|
|
||||||
def test_init_rejects_missing_project_directory(self) -> None:
|
def test_init_rejects_missing_project_directory(self) -> None:
|
||||||
project = self.home / "missing-app"
|
project = self.home / "missing-app"
|
||||||
|
|
||||||
result = self.run_skiff("kit", "init", "ack", "--project", str(project))
|
result = self.run_skiff("init", "ack", "--project", str(project))
|
||||||
|
|
||||||
self.assertNotEqual(result.returncode, 0)
|
self.assertNotEqual(result.returncode, 0)
|
||||||
self.assertIn("项目目录不存在", result.stderr)
|
self.assertIn("项目目录不存在", result.stderr)
|
||||||
self.assertFalse(project.exists())
|
self.assertFalse(project.exists())
|
||||||
|
|
||||||
|
def test_legacy_kit_command_is_not_exposed(self) -> None:
|
||||||
|
result = self.run_skiff("kit", "init", "ack")
|
||||||
|
|
||||||
|
self.assertNotEqual(result.returncode, 0)
|
||||||
|
self.assertIn("invalid choice", result.stderr)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
Reference in New Issue
Block a user