mirror of
https://github.com/jnMetaCode/superpowers-zh.git
synced 2026-09-02 22:54:06 +08:00
chore: 跟上游 v5.1.0 对齐 (#22)
补齐上游漏的根级文件:CLAUDE.md(翻译 + 中文 fork 备注)、
AGENTS.md(symlink → CLAUDE.md,跟上游 mode 120000 一致)、
RELEASE-NOTES.md(上游英文原样 1180 行)、RELEASE-NOTES.zh.md
(新建中文 fork 自身 release log)、.codex-plugin/plugin.json
(中文化)、.version-bump.json、scripts/bump-version.sh、
assets/{app-icon.png,superpowers-small.svg}、4 个上游新增测试。
跟上游意图对齐,删上游已废弃流程:
- commands/ 全 3 个 deprecated stub(上游 #1188)
- agents/code-reviewer.md(已上升进 requesting-code-review skill,
上游 PR #1299)
- bin/superpowers-zh.js 删 install agents 逻辑,保留 uninstall 用
hardcoded LEGACY_AGENT_FILENAMES 列表清理已装用户残留
- .github/workflows/ci.yml 删 "Validate agents" 段
- package.json files 字段移除 agents/ commands/
主动修上游 v5.1.0 疏忽:
- .cursor-plugin/plugin.json 删 dangling 的 "agents": "./agents/"
和 "commands": "./commands/"。上游删目录但忘同步 manifest
(git blame 显示自 2026-02-13 起从未更新)。
配套:
- scripts/sync-plugin-version.js 扩到 .codex-plugin/plugin.json
- npm pack 校验 90 文件 228KB,agents/commands 残留 0 条
中文版"手动新增"叠加层全部保留:bin/ + npx 流程、docs/、
chinese-* skill、mcp-builder、workflow-runner、README 主推 npx、
各 INSTALL.md、.gemini/、sync-plugin-version.js。
详见 RELEASE-NOTES.zh.md v1.3.0 段。
This commit is contained in:
48
.codex-plugin/plugin.json
Normal file
48
.codex-plugin/plugin.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "superpowers-zh",
|
||||
"version": "1.2.1",
|
||||
"description": "AI 编程超能力中文增强版:头脑风暴、subagent 驱动开发、规划、TDD、调试、代码审查、收尾工作流,附 4 个中文 skills 沉淀。",
|
||||
"author": {
|
||||
"name": "jnMetaCode",
|
||||
"url": "https://github.com/jnMetaCode"
|
||||
},
|
||||
"homepage": "https://github.com/jnMetaCode/superpowers-zh",
|
||||
"repository": "https://github.com/jnMetaCode/superpowers-zh",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"brainstorming",
|
||||
"subagent-driven-development",
|
||||
"skills",
|
||||
"planning",
|
||||
"tdd",
|
||||
"debugging",
|
||||
"code-review",
|
||||
"workflow",
|
||||
"chinese",
|
||||
"中文"
|
||||
],
|
||||
"skills": "./skills/",
|
||||
"interface": {
|
||||
"displayName": "Superpowers 中文版",
|
||||
"shortDescription": "面向编程 agent 的规划、TDD、调试、交付工作流(中文增强版)",
|
||||
"longDescription": "用 Superpowers 中文版引导 agent 完成头脑风暴、实现规划、测试驱动开发、系统化调试、并行执行、代码审查与分支收尾。在上游 obra/superpowers 之上叠加 4 个中国原创 skill 与多工具适配。",
|
||||
"developerName": "jnMetaCode",
|
||||
"category": "Coding",
|
||||
"capabilities": [
|
||||
"Interactive",
|
||||
"Read",
|
||||
"Write"
|
||||
],
|
||||
"defaultPrompt": [
|
||||
"我有个想做的东西。",
|
||||
"给这个项目加个功能。"
|
||||
],
|
||||
"websiteURL": "https://github.com/jnMetaCode/superpowers-zh",
|
||||
"privacyPolicyURL": "https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement",
|
||||
"termsOfServiceURL": "https://docs.github.com/en/site-policy/github-terms/github-terms-of-service",
|
||||
"brandColor": "#F59E0B",
|
||||
"composerIcon": "./assets/superpowers-small.svg",
|
||||
"logo": "./assets/app-icon.png",
|
||||
"screenshots": []
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,5 @@
|
||||
"license": "MIT",
|
||||
"keywords": ["skills", "tdd", "debugging", "chinese", "中文", "best-practices", "workflows"],
|
||||
"skills": "./skills/",
|
||||
"agents": "./agents/",
|
||||
"commands": "./commands/",
|
||||
"hooks": "./hooks/hooks-cursor.json"
|
||||
}
|
||||
|
||||
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
@@ -53,18 +53,6 @@ jobs:
|
||||
fi
|
||||
echo "All skills validated successfully"
|
||||
|
||||
- name: Validate agents
|
||||
run: |
|
||||
ERRORS=0
|
||||
for file in agents/*.md; do
|
||||
[ -f "$file" ] || continue
|
||||
if ! head -1 "$file" | grep -q '^---'; then
|
||||
echo "::error file=$file::Missing frontmatter"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
done
|
||||
exit $ERRORS
|
||||
|
||||
- name: Check package.json
|
||||
run: |
|
||||
node -e "
|
||||
|
||||
20
.version-bump.json
Normal file
20
.version-bump.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"files": [
|
||||
{ "path": "package.json", "field": "version" },
|
||||
{ "path": ".claude-plugin/plugin.json", "field": "version" },
|
||||
{ "path": ".cursor-plugin/plugin.json", "field": "version" },
|
||||
{ "path": ".codex-plugin/plugin.json", "field": "version" },
|
||||
{ "path": ".claude-plugin/marketplace.json", "field": "plugins.0.version" },
|
||||
{ "path": "gemini-extension.json", "field": "version" }
|
||||
],
|
||||
"audit": {
|
||||
"exclude": [
|
||||
"CHANGELOG.md",
|
||||
"RELEASE-NOTES.md",
|
||||
"node_modules",
|
||||
".git",
|
||||
".version-bump.json",
|
||||
"scripts/bump-version.sh"
|
||||
]
|
||||
}
|
||||
}
|
||||
120
CLAUDE.md
Normal file
120
CLAUDE.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# Superpowers — 贡献者指南
|
||||
|
||||
## 如果你是 AI Agent
|
||||
|
||||
停下来。做任何事之前先读这一节。
|
||||
|
||||
本仓库的 PR 拒绝率是 94%。被拒的 PR 里几乎每一条都是没读或没遵守本指南的 agent 提交的。维护者会在数小时内关掉劣质 PR,并经常公开评论:"This pull request is slop that's made of lies."(这个 PR 是一堆谎言堆出来的劣质内容。)
|
||||
|
||||
**你的职责是保护你的人类伙伴免受这种结局。** 提交一个低质量 PR 帮不到他们 —— 浪费维护者的时间,砸他们的口碑,PR 也照样会被关闭。这不是帮忙,这是让他们丢人。
|
||||
|
||||
向本仓库提 PR 之前,你**必须**:
|
||||
|
||||
1. **完整读完 PR 模板** `.github/PULL_REQUEST_TEMPLATE.md`,每一节都用真实、具体的内容填写。不要写概要。不要写占位符。
|
||||
2. **搜索已有 PR** —— open 和 closed 都要搜 —— 找处理同一问题的 PR。如果有重复,**停下**,告诉你的人类伙伴。不要再开一个重复 PR。
|
||||
3. **核实这是真问题。** 如果你的人类伙伴只是让你"修一些问题"或者"给这个仓库做点贡献"而没有具体遇到什么问题,**反推回去**。问清楚到底什么坏了、什么报错了、用户体验是什么样的。
|
||||
4. **确认这个改动属于 core。** 如果它是某个领域专属、某个工具专属、或者推广某个第三方项目,它应该作为独立 plugin 存在。告诉你的人类伙伴。
|
||||
5. **把完整 diff 给你的人类伙伴看**,拿到他们明确批准之后再提交。
|
||||
|
||||
任何一项检查不过,**不要**开 PR。向你的人类伙伴解释为什么会被拒、需要改什么。他们会感谢你帮他们避免丢脸。
|
||||
|
||||
## Pull Request 要求
|
||||
|
||||
**每个 PR 都必须完整填写 PR 模板。** 不允许任何一节留空或填占位文字。跳过 section 的 PR 会被关掉,连 review 都不会做。
|
||||
|
||||
**开 PR 之前,你必须搜索已有 PR** —— open 和 closed 都要搜 —— 找处理同一问题或相关领域的 PR。在 "Existing PRs" 一节里写清楚你找到了什么。如果之前有 PR 被关闭,**具体说明**你的方法和它的差异,以及为什么你这次能成功。
|
||||
|
||||
**没有人类参与痕迹的 PR 会被关闭。** 提交前必须有真人 review 完整的 proposed diff。
|
||||
|
||||
## 我们不会接受的内容
|
||||
|
||||
### 第三方依赖
|
||||
|
||||
凡是引入对第三方项目的可选或必选依赖的 PR,除非是为新 harness(新的 IDE 或 CLI 工具)添加支持,否则不会被接受。Superpowers 在设计上是零依赖 plugin。如果你的改动需要外部工具或服务,它应该作为独立 plugin 存在。
|
||||
|
||||
### 给 skill "合规化" 的改动
|
||||
|
||||
我们内部的 skill 哲学跟 Anthropic 公开的 skill 写作指南不一样。我们的 skill 内容是经过大量测试与调优、针对真实 agent 行为校准过的。凡是为了"符合"Anthropic skills 文档而对 skill 做重组、改写、重排版的 PR,没有充分的 eval 证据证明改动改善了实际效果,**不会**被接受。改动行为塑造类内容的门槛非常高。
|
||||
|
||||
### 项目专属或个人配置
|
||||
|
||||
只对某个具体项目、团队、领域、工作流有用的 skill、hook 或配置,不属于 core。请发布为独立 plugin。
|
||||
|
||||
### 批量、广撒网式 PR
|
||||
|
||||
不要把 issue tracker 翻一遍然后在一个 session 里给多个 issue 各开一个 PR。每个 PR 都需要:对问题的真实理解、对历史尝试的调查、对完整 diff 的人类 review。明显是批量产物 —— 把 agent 指向 issue 列表然后告诉它"修一下" —— 这种 PR 一律关闭。要贡献,就**挑一个** issue,深入理解,提交高质量工作。
|
||||
|
||||
### 推测性或理论性修复
|
||||
|
||||
每个 PR 都必须解决某人**真实经历过**的问题。"我的 review agent 标了这个"或"这理论上可能出问题"不是问题陈述。如果你说不出促使这个改动的具体 session、错误或用户体验,**不要**提交 PR。
|
||||
|
||||
### 领域专属 skill
|
||||
|
||||
Superpowers core 包含的是对所有用户都有益的通用 skill,跟项目类型无关。针对特定领域(作品集生成、预测市场、游戏)、特定工具或特定工作流的 skill,属于独立 plugin。问问自己:"如果有人在做完全不同类型的项目,这个 skill 对他还有用吗?"如果没用,请单独发布。
|
||||
|
||||
### Fork 专属改动
|
||||
|
||||
如果你维护一个有定制化的 fork,**不要**提 PR 来同步你的 fork 或者把 fork 专属改动推到上游。重新打品牌、添加 fork 专属功能、合并 fork 分支的 PR 会被关闭。
|
||||
|
||||
### 编造的内容
|
||||
|
||||
包含编造的论点、虚构的问题描述或幻觉出来的功能的 PR,会被立刻关闭。本仓库 94% 拒绝率 —— 维护者见过 AI slop 的所有花样。他们看得出来。
|
||||
|
||||
### 打包不相关改动
|
||||
|
||||
包含多个不相关改动的 PR 会被关闭。请拆成多个 PR。
|
||||
|
||||
## 新 Harness 支持
|
||||
|
||||
如果你的 PR 是给新 harness(IDE、CLI 工具、agent runner)加支持,你**必须**附上 session transcript 证明集成端到端可用。
|
||||
|
||||
真正的集成会在 session 开始时加载 `using-superpowers` bootstrap。bootstrap 是让 skill 在恰当时机自动触发的关键。没有它,skill 就是死重 —— 文件在磁盘上但永远不会被调用。
|
||||
|
||||
**验收测试。** 在新 harness 里开一个干净 session,发这条用户消息:
|
||||
|
||||
> Let's make a react todo list
|
||||
|
||||
可工作的集成会在写任何代码之前自动触发 `brainstorming` skill。把完整 transcript 贴在 PR 里。
|
||||
|
||||
**以下情况不算真正集成,会被关闭:**
|
||||
|
||||
- 手动把 skill 文件拷进 harness
|
||||
- 在运行时用 `npx skills` 之类 shim 包装
|
||||
- 任何需要用户每个 session 手动 opt-in skill 的方案
|
||||
- 任何在上述验收测试里 `brainstorming` 不会自动触发的方案
|
||||
|
||||
如果你不确定你的集成是否在 session 开始时加载 bootstrap,那就是没加载。
|
||||
|
||||
## Skill 改动需要 eval
|
||||
|
||||
Skill 不是普通文档 —— 它是塑造 agent 行为的代码。如果你修改 skill 内容:
|
||||
|
||||
- 用 `superpowers:writing-skills` 来开发和测试改动
|
||||
- 跨多个 session 跑对抗式压力测试
|
||||
- 在 PR 里附上 before/after eval 结果
|
||||
- 不要在没有改进证据的情况下修改精心调优过的内容(Red Flags 表、rationalization 列表、"human partner" 措辞等)
|
||||
|
||||
## 贡献前先理解项目
|
||||
|
||||
在提议改 skill 设计、workflow 哲学或架构之前,先读已有 skill,理解项目的设计决策。Superpowers 在 skill 设计、agent 行为塑造和术语方面有自己一套验证过的哲学(例如 "your human partner" 是刻意的措辞,跟 "the user" 不能混用)。在不理解项目"为什么这样存在"的前提下重写项目的语气、重组它的方法的改动,会被拒绝。
|
||||
|
||||
## 通用原则
|
||||
|
||||
- 提交前读 `.github/PULL_REQUEST_TEMPLATE.md`
|
||||
- 一个 PR 解决一个问题
|
||||
- 至少在一种 harness 上测试,并在 environment 表里报告结果
|
||||
- 描述你**解决了什么问题**,不只是你改了什么
|
||||
|
||||
---
|
||||
|
||||
## 关于本中文 fork(superpowers-zh)
|
||||
|
||||
本仓库 `jnMetaCode/superpowers-zh` 是上游 `obra/superpowers` 的**中文增强 fork**,定位为:完整翻译上游 skill + 叠加 4 个中国原创 skill(chinese-code-review / chinese-commit-conventions / chinese-documentation / chinese-git-workflow)+ 多工具适配(npx 一条命令支持 17 款 IDE/CLI)。
|
||||
|
||||
**上述规则适用于向 `obra/superpowers` 上游提 PR 时的行为约束。** 向中文 fork 提 PR 时按本仓库自己的 PR 模板与流程执行,但其中的核心原则**同样适用**:
|
||||
|
||||
- 提交前先在 `jnMetaCode/superpowers-zh` 搜已有 PR / issue 查重
|
||||
- 不交付 AI slop(编造、批量、推测性修复均会被关闭)
|
||||
- 真人必须 review 完整 diff 后再提交
|
||||
|
||||
**特别提示:** 中文化内容、`chinese-*` skill、针对国内 IDE 的工具适配等改动,按上游 "Fork-specific changes" 规则向 `obra/superpowers` 提 PR 会被关闭 —— 这类内容**只提到本 fork**。
|
||||
1180
RELEASE-NOTES.md
Normal file
1180
RELEASE-NOTES.md
Normal file
File diff suppressed because it is too large
Load Diff
102
RELEASE-NOTES.zh.md
Normal file
102
RELEASE-NOTES.zh.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# Superpowers-ZH 中文版 Release Notes
|
||||
|
||||
> 本文档记录 `jnMetaCode/superpowers-zh` 中文 fork 自身的 release 历史。
|
||||
>
|
||||
> 上游 `obra/superpowers` 的英文 release notes 见 [`RELEASE-NOTES.md`](./RELEASE-NOTES.md)(原样保留,未翻译)。
|
||||
|
||||
---
|
||||
|
||||
## v1.3.0 (2026-05-10)
|
||||
|
||||
### 跟上游对齐 (v5.1.0)
|
||||
|
||||
- **同步上游 v5.1.0 的目录变更**:上游主动删除了 `commands/`(3 个 deprecated stub)和 `agents/code-reviewer.md`(已上升进 `requesting-code-review` skill)。中文 fork 跟随删除以与上游意图对齐。详见上游 [#1188](https://github.com/obra/superpowers/pull/1188) 与 PR #1299。
|
||||
- **`bin/superpowers-zh.js`** 移除安装时复制 `agents/` 到 `.claude/agents/` 的逻辑,**保留** uninstall 时的清理逻辑(用于已装用户清理残留 `code-reviewer.md`,防止双 source of truth)。
|
||||
- **`.github/workflows/ci.yml`** 删除 "Validate agents" 验证段(`agents/` 已删,验证空目录无意义)。
|
||||
|
||||
### 补齐上游遗漏的根级文件
|
||||
|
||||
- **`CLAUDE.md`** —— 上游 contributor 指南(含 anti-slop-PR 规则)的中文翻译,末尾追加中文 fork 自己的 PR 流程说明。
|
||||
- **`AGENTS.md`** —— 软链接 → `CLAUDE.md`(mode 120000,跟上游一致)。Codex CLI 等工具从 `AGENTS.md` 自动加载等同读取 CLAUDE.md。
|
||||
- **Known limitation**:`npm pack` 默认不跟随 symlink,因此 npm publish 出来的 tarball 不包含 AGENTS.md。这不影响实际使用:AGENTS.md 是 Codex CLI 在用户自己项目目录读的文件,不是从 `superpowers-zh` 安装包读的;通过 `git clone` 拿到仓库的贡献者会正确解析 symlink。
|
||||
- **`RELEASE-NOTES.md`** —— 上游 release notes 原样保留(英文版,1180 行)。
|
||||
- **`RELEASE-NOTES.zh.md`** —— 本文件,中文 fork 自身 release 记录。
|
||||
- **`.codex-plugin/plugin.json`** —— Codex CLI plugin manifest(中文版本地化:name/description/displayName 改为中文版,URL 指向 `jnMetaCode/superpowers-zh`)。
|
||||
- **`.version-bump.json`** —— 上游版本管理配置文件。
|
||||
- **`scripts/bump-version.sh`** —— 上游版本同步脚本(含 `--check` 漂移检测、`--audit` 仓库审计)。中文版 npm version 钩子继续用 `scripts/sync-plugin-version.js`,bump-version.sh 作为补充工具引入。
|
||||
- **`assets/app-icon.png`** + **`assets/superpowers-small.svg`** —— Codex marketplace 需要的图标资产。
|
||||
- **4 个新增上游测试**:`tests/claude-code/test-requesting-code-review.sh`、`tests/claude-code/test-worktree-native-preference.sh`、`tests/opencode/test-bootstrap-caching.{mjs,sh}`。
|
||||
|
||||
### 主动修复上游 v5.1.0 的疏忽
|
||||
|
||||
- **`.cursor-plugin/plugin.json`** 删除 dangling 的 `"agents": "./agents/"` 和 `"commands": "./commands/"` 两行。上游 v5.1.0 删了目录但忘了同步清理 manifest(git blame 显示这两行从 2026-02-13 加入后从未更新)。中文 fork 主动修掉,并向上游开 issue 提醒。
|
||||
|
||||
### 不引入
|
||||
|
||||
- 上游 `scripts/sync-to-codex-plugin.sh`(推 OpenAI Codex marketplace 用,硬编码 `prime-radiant-inc/openai-codex-plugins`,中文版用不上)
|
||||
- 配套测试 `tests/codex-plugin-sync/test-sync-to-codex-plugin.sh`
|
||||
|
||||
### 不动(中文版叠加层全部保留)
|
||||
|
||||
`bin/` + npx 流程、`docs/` 中文工具文档、4 个 `chinese-*` skill、`mcp-builder`、`workflow-runner`、`README.md` 主推 npx 路径、`.codex/INSTALL.md`、`.opencode/INSTALL.md`、`.gemini/`、`scripts/sync-plugin-version.js` —— 这些是符合"保持上游主流程不变 + 中文版叠加新增"原则的中文 fork 沉淀,全部保留。
|
||||
|
||||
---
|
||||
|
||||
## v1.2.1 (2026-05-05)
|
||||
|
||||
### 修复
|
||||
|
||||
- **`--uninstall` 数据丢失边界 case** —— 加哨兵注释 + 保守 fallback,杜绝在某些路径上误删用户数据。
|
||||
|
||||
---
|
||||
|
||||
## v1.2.0 (2026-05-05)
|
||||
|
||||
### 新增
|
||||
|
||||
- **`--uninstall` 子命令** —— `npx superpowers-zh --uninstall` 一条命令清理已安装的 skills(#17)。
|
||||
- **HOME 目录守护** —— uninstall 时强校验工作目录非用户 HOME,杜绝误删全局文件。
|
||||
- **计数显示修复** —— 安装后输出实际安装的 skill 数量(之前显示固定值)。
|
||||
|
||||
---
|
||||
|
||||
## v1.1.9 (2026-04-28)
|
||||
|
||||
### 修复
|
||||
|
||||
- **Claude Code bootstrap 修复** —— npx 安装到 CC 目标时自动补上 `CLAUDE.md` bootstrap,根治 skill 不触发问题(#14)。
|
||||
|
||||
### 变更
|
||||
|
||||
- **Node 引擎要求** 提升到 `>=20`(Node 14/16/18 均已 EOL)。
|
||||
- **README 重排**:相关项目表挪到显眼位置;姊妹项目区块独立成"相关项目生态"章节,重点推广 orchestrator。
|
||||
- **QQ 群** 标识改为 QQ 2群。
|
||||
|
||||
---
|
||||
|
||||
## v1.1.8 (2026-04-19)
|
||||
|
||||
### 新增
|
||||
|
||||
- **Claw Code 支持**(第 17 款工具,Rust 版 AI CLI)—— auto-detect `.claw/` 或 `CLAW.md`,支持 `--tool claw/claw-code/clawcode`。
|
||||
- **CNB(腾讯云原生构建)平台适配** —— `chinese-git-workflow` skill 新增 CNB 章节,含 `.cnb.yml` CI 示例(#6)。
|
||||
|
||||
---
|
||||
|
||||
## v1.1.0 – v1.1.7 早期开发(2026-03 ~ 2026-04)
|
||||
|
||||
中文 fork 在这一时期完成了主要的多工具适配与中文化基建:
|
||||
|
||||
- 第 1 款 → 第 16 款工具陆续上线:Claude Code、Cursor、Codex CLI、Gemini CLI、Trae、VS Code (Copilot)、Antigravity、Hermes Agent、Copilot CLI、Windsurf、Aider、OpenCode、Qwen Code(通义灵码)、Kiro、OpenClaw、DeerFlow 2.0
|
||||
- 4 个中国原创 skill 沉淀:`chinese-code-review`、`chinese-commit-conventions`、`chinese-documentation`、`chinese-git-workflow`
|
||||
- `mcp-builder`、`workflow-runner` 两个补充 skill
|
||||
- npx 一条命令自动检测项目工具并安装
|
||||
- 跨平台兼容性修复:Windows `cpSync` 问题、低版本 Node 兼容、Antigravity/Aider/Gemini CLI 自动生成 bootstrap
|
||||
|
||||
---
|
||||
|
||||
## v1.0.0 (2026-03-09)
|
||||
|
||||
- 中文 fork 初始版本,基于上游 `obra/superpowers` v5.0.0 翻译。
|
||||
- 完整翻译 14 个上游 skill。
|
||||
- 首批支持 Claude Code 一种工具。
|
||||
@@ -1,48 +0,0 @@
|
||||
---
|
||||
name: code-reviewer
|
||||
description: |
|
||||
当一个主要项目步骤完成并需要根据原始计划和编码标准进行审查时使用此智能体。示例:<example>Context: 用户正在创建一个代码审查智能体,应在逻辑代码块编写完成后调用。user: "我已经按照计划第 3 步完成了用户认证系统的实现" assistant: "干得好!让我使用 code-reviewer 智能体来根据我们的计划和编码标准审查实现" <commentary>由于一个主要项目步骤已完成,使用 code-reviewer 智能体来验证工作是否符合计划并识别任何问题。</commentary></example> <example>Context: 用户完成了一个重要功能的实现。user: "任务管理系统的 API 端点现在完成了——这涵盖了我们架构文档中的第 2 步" assistant: "很好!让我用 code-reviewer 智能体检查这个实现,确保它符合我们的计划并遵循最佳实践" <commentary>规划文档中的一个编号步骤已完成,所以应该用 code-reviewer 智能体审查工作。</commentary></example>
|
||||
model: inherit
|
||||
---
|
||||
|
||||
你是一位高级代码审查员,精通软件架构、设计模式和最佳实践。你的角色是根据原始计划审查已完成的项目步骤,确保代码质量标准得到满足。
|
||||
|
||||
审查已完成的工作时,你将:
|
||||
|
||||
1. **计划对齐分析**:
|
||||
- 将实现与原始规划文档或步骤描述进行对比
|
||||
- 识别与计划的方案、架构或需求的任何偏差
|
||||
- 评估偏差是合理的改进还是有问题的偏离
|
||||
- 验证所有计划的功能是否都已实现
|
||||
|
||||
2. **代码质量评估**:
|
||||
- 审查代码是否遵循已建立的模式和约定
|
||||
- 检查错误处理、类型安全和防御性编程是否恰当
|
||||
- 评估代码组织、命名约定和可维护性
|
||||
- 评估测试覆盖率和测试实现的质量
|
||||
- 寻找潜在的安全漏洞或性能问题
|
||||
|
||||
3. **架构和设计审查**:
|
||||
- 确保实现遵循 SOLID 原则和已建立的架构模式
|
||||
- 检查关注点分离和松耦合是否恰当
|
||||
- 验证代码与现有系统的集成是否良好
|
||||
- 评估可扩展性和可延展性的考虑
|
||||
|
||||
4. **文档和标准**:
|
||||
- 验证代码包含适当的注释和文档
|
||||
- 检查文件头、函数文档和内联注释是否存在且准确
|
||||
- 确保遵循项目特定的编码标准和约定
|
||||
|
||||
5. **问题识别和建议**:
|
||||
- 将问题明确分类为:关键(必须修复)、重要(应该修复)或建议(锦上添花)
|
||||
- 对每个问题提供具体示例和可操作的建议
|
||||
- 识别计划偏差时,解释它们是有问题的还是有益的
|
||||
- 在有帮助时提供带有代码示例的具体改进建议
|
||||
|
||||
6. **沟通协议**:
|
||||
- 如果发现与计划的重大偏差,请编码智能体审查并确认更改
|
||||
- 如果识别出原始计划本身的问题,建议更新计划
|
||||
- 对于实现问题,提供明确的修复指导
|
||||
- 在指出问题之前始终先肯定做得好的地方
|
||||
|
||||
你的输出应该是结构化的、可操作的,聚焦于帮助保持高代码质量同时确保项目目标的达成。要全面但简洁,始终提供建设性的反馈,帮助改进当前实现和未来的开发实践。
|
||||
BIN
assets/app-icon.png
Normal file
BIN
assets/app-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
1
assets/superpowers-small.svg
Normal file
1
assets/superpowers-small.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="Calque_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M394.28,207.8c.81,2.41,1.39,4.78,1.8,7.07,1.61,9.03-.93,17.78-5.99,21.74-22.6,17.7-49.85,29.35-75.34,38.6-.59.22-1.09.28-1.4.34-2.22.47-4.95,1.04-7.25,0-1.46-.66-2.25-1.74-2.66-2.3-1.56-2.1-1.59-4.31-1.56-5.13.1-2.67-.01-4.69,0-4.82.45-3.52.91-10.66,1.41-21.28.6-3.87,2.16-9.63,6.94-13.96,4.01-3.62,8.33-4.6,14.59-5.87,10.76-2.19,37.21-8.22,47.42-16.56,1.63-1.33,2.97-2.65,4.19-3.96,3.72-3.99,6.39-7.92,7.93-10.36,3.22,3.22,7.25,8.48,9.92,16.47Z"/><path d="M428.67,185.28c-2.33,11.99-8.91,22.32-15.88,30.38.27-5.5-.05-12.11-1.86-19.08-5.04-19.36-19.74-34.7-37.78-37.78-32.21-9.74-70.59,3.79-99.08,18.29-3.87,1.95-9.52-2.77-11.84-8.16-3.32-7.71-1.63-6.28,2.61-8.49,38.31-20.03,82.01-39.61,123.91-29.7,8.26,1.95,15.96,5.26,23.48,10.54,11.32,7.96,20.21,24.74,16.44,44Z"/><path d="M117.72,304.2c-.81-2.41-1.39-4.78-1.8-7.07-1.61-9.03.93-17.78,5.99-21.74,22.6-17.7,49.85-29.35,75.34-38.6.59-.22,1.09-.28,1.4-.34,2.22-.47,4.95-1.04,7.25,0,1.46.66,2.25,1.74,2.66,2.3,1.56,2.1,1.59,4.31,1.56,5.13-.1,2.67.01,4.69,0,4.82-.45,3.52-.91,10.66-1.41,21.28-.6,3.87-2.16,9.63-6.94,13.96-4.01,3.62-8.33,4.6-14.59,5.87-10.76,2.19-37.21,8.22-47.42,16.56-1.63,1.33-2.97,2.65-4.19,3.96-3.72,3.99-6.39,7.92-7.93,10.36-3.22-3.22-7.25-8.48-9.92-16.47Z"/><path d="M83.33,326.72c2.33-11.99,8.91-22.32,15.88-30.38-.27,5.5.05,12.11,1.86,19.08,5.04,19.36,19.74,34.7,37.78,37.78,32.21,9.74,70.59-3.79,99.08-18.29,3.87-1.95,9.52,2.77,11.84,8.16,3.32,7.71,1.63,6.28-2.61,8.49-38.31,20.03-82.01,39.61-123.91,29.7-8.26-1.95-15.96-5.26-23.48-10.54-11.32-7.96-20.21-24.74-16.44-44Z"/><ellipse cx="255.16" cy="258.86" rx="28.95" ry="28.76"/></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -39,9 +39,13 @@ function copyDirSync(src, dest) {
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const PKG = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf8'));
|
||||
const SKILLS_SRC = resolve(__dirname, '..', 'skills');
|
||||
const AGENTS_SRC = resolve(__dirname, '..', 'agents');
|
||||
const PROJECT_DIR = process.cwd();
|
||||
|
||||
// 历史遗留 agent 文件名 — 用于 --uninstall 清理已装用户机器上的残留。
|
||||
// 上游 v5.1.0 把 agents/code-reviewer.md 上升进 requesting-code-review skill,
|
||||
// agents/ 目录已删,但旧版本装过的用户机器上仍有残留文件需要清理。
|
||||
const LEGACY_AGENT_FILENAMES = ['code-reviewer.md'];
|
||||
|
||||
const TARGETS = [
|
||||
{ name: 'Claude Code', dir: '.claude/skills', detect: '.claude' },
|
||||
{ name: 'Cursor', dir: '.cursor/skills', detect: ['.cursor', '.cursorrules'] },
|
||||
@@ -451,11 +455,6 @@ function installForTarget(target) {
|
||||
|
||||
if (target.name === 'Claude Code') {
|
||||
generateClaudeCodeBootstrap(PROJECT_DIR);
|
||||
if (existsSync(AGENTS_SRC)) {
|
||||
const agentsDest = resolve(PROJECT_DIR, '.claude', 'agents');
|
||||
mkdirSync(agentsDest, { recursive: true });
|
||||
copyDirSync(AGENTS_SRC, agentsDest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -597,18 +596,18 @@ function uninstall() {
|
||||
}
|
||||
}
|
||||
|
||||
// 顺带清理 .claude/agents 下我们装过的 agent(agents 源是平铺的文件,例如 code-reviewer.md)
|
||||
// 清理 .claude/agents 下旧版本装过的 legacy agent(v1.2.x 及之前会装 code-reviewer.md,
|
||||
// v1.3.0 起跟随上游 v5.1.0 移除)。即使 agents/ 源目录已删,已装用户跑 --uninstall 仍应能清干净。
|
||||
const agentsDest = resolve(PROJECT_DIR, '.claude', 'agents');
|
||||
if (existsSync(AGENTS_SRC) && existsSync(agentsDest)) {
|
||||
const srcAgentNames = new Set(readdirSync(AGENTS_SRC).filter(n => n !== '.DS_Store'));
|
||||
if (existsSync(agentsDest)) {
|
||||
let agentsRemoved = 0;
|
||||
for (const entry of readdirSync(agentsDest)) {
|
||||
if (srcAgentNames.has(entry)) {
|
||||
if (LEGACY_AGENT_FILENAMES.includes(entry)) {
|
||||
rmSync(resolve(agentsDest, entry), { recursive: true, force: true });
|
||||
agentsRemoved++;
|
||||
}
|
||||
}
|
||||
if (agentsRemoved > 0) console.log(` ✅ Claude Code agents: 移除 ${agentsRemoved} 个 -> ${agentsDest}`);
|
||||
if (agentsRemoved > 0) console.log(` ✅ Claude Code agents: 移除 ${agentsRemoved} 个旧版残留 -> ${agentsDest}`);
|
||||
try {
|
||||
const left = readdirSync(agentsDest).filter(n => n !== '.DS_Store');
|
||||
if (left.length === 0) rmSync(agentsDest, { recursive: true, force: true });
|
||||
@@ -709,13 +708,6 @@ function install(forceToolName, force) {
|
||||
console.log(` ✅ 默认安装: ${countDirs(dest)} 个 skills -> ${dest}`);
|
||||
|
||||
generateClaudeCodeBootstrap(PROJECT_DIR);
|
||||
|
||||
if (existsSync(AGENTS_SRC)) {
|
||||
const agentsDest = resolve(PROJECT_DIR, '.claude', 'agents');
|
||||
mkdirSync(agentsDest, { recursive: true });
|
||||
copyDirSync(AGENTS_SRC, agentsDest);
|
||||
console.log(` ✅ 默认安装: agents -> ${agentsDest}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n 安装完成!重启你的 AI 编程工具即可生效。\n');
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
description: "已弃用 - 请改用 superpowers:brainstorming 技能"
|
||||
---
|
||||
|
||||
告诉你的搭档这个命令已弃用,将在下个主要版本中移除。他们应该让你使用"superpowers brainstorming"技能。
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
description: "已弃用 - 请改用 superpowers:executing-plans 技能"
|
||||
---
|
||||
|
||||
告诉你的搭档这个命令已弃用,将在下个主要版本中移除。他们应该让你使用"superpowers executing-plans"技能。
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
description: "已弃用 - 请改用 superpowers:writing-plans 技能"
|
||||
---
|
||||
|
||||
告诉你的搭档这个命令已弃用,将在下个主要版本中移除。他们应该让你使用"superpowers writing-plans"技能。
|
||||
@@ -11,20 +11,23 @@
|
||||
"superpowers-zh": "bin/superpowers-zh.js"
|
||||
},
|
||||
"scripts": {
|
||||
"version": "node scripts/sync-plugin-version.js && git add .claude-plugin/plugin.json .cursor-plugin/plugin.json"
|
||||
"version": "node scripts/sync-plugin-version.js && git add .claude-plugin/plugin.json .cursor-plugin/plugin.json .codex-plugin/plugin.json"
|
||||
},
|
||||
"files": [
|
||||
"bin/",
|
||||
"skills/",
|
||||
"agents/",
|
||||
"commands/",
|
||||
"hooks/",
|
||||
"assets/",
|
||||
".claude-plugin/",
|
||||
".cursor-plugin/",
|
||||
".codex/INSTALL.md",
|
||||
".codex-plugin/",
|
||||
".opencode/INSTALL.md",
|
||||
".opencode/plugins/",
|
||||
"CLAUDE.md",
|
||||
"GEMINI.md",
|
||||
"RELEASE-NOTES.md",
|
||||
"RELEASE-NOTES.zh.md",
|
||||
"gemini-extension.json",
|
||||
"docs/",
|
||||
"README.md",
|
||||
|
||||
220
scripts/bump-version.sh
Executable file
220
scripts/bump-version.sh
Executable file
@@ -0,0 +1,220 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# bump-version.sh — bump version numbers across all declared files,
|
||||
# with drift detection and repo-wide audit for missed files.
|
||||
#
|
||||
# Usage:
|
||||
# bump-version.sh <new-version> Bump all declared files to new version
|
||||
# bump-version.sh --check Report current versions (detect drift)
|
||||
# bump-version.sh --audit Check + grep repo for old version strings
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
CONFIG="$REPO_ROOT/.version-bump.json"
|
||||
|
||||
if [[ ! -f "$CONFIG" ]]; then
|
||||
echo "error: .version-bump.json not found at $CONFIG" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- helpers ---
|
||||
|
||||
# Read a dotted field path from a JSON file.
|
||||
# Handles both simple ("version") and nested ("plugins.0.version") paths.
|
||||
read_json_field() {
|
||||
local file="$1" field="$2"
|
||||
# Convert dot-path to jq path: "plugins.0.version" -> .plugins[0].version
|
||||
local jq_path
|
||||
jq_path=$(echo "$field" | sed -E 's/\.([0-9]+)/[\1]/g' | sed 's/^/./' | sed 's/\.\././g')
|
||||
jq -r "$jq_path" "$file"
|
||||
}
|
||||
|
||||
# Write a dotted field path in a JSON file, preserving formatting.
|
||||
write_json_field() {
|
||||
local file="$1" field="$2" value="$3"
|
||||
local jq_path
|
||||
jq_path=$(echo "$field" | sed -E 's/\.([0-9]+)/[\1]/g' | sed 's/^/./' | sed 's/\.\././g')
|
||||
local tmp="${file}.tmp"
|
||||
jq "$jq_path = \"$value\"" "$file" > "$tmp" && mv "$tmp" "$file"
|
||||
}
|
||||
|
||||
# Read the list of declared files from config.
|
||||
# Outputs lines of "path<TAB>field"
|
||||
declared_files() {
|
||||
jq -r '.files[] | "\(.path)\t\(.field)"' "$CONFIG"
|
||||
}
|
||||
|
||||
# Read the audit exclude patterns from config.
|
||||
audit_excludes() {
|
||||
jq -r '.audit.exclude[]' "$CONFIG" 2>/dev/null
|
||||
}
|
||||
|
||||
# --- commands ---
|
||||
|
||||
cmd_check() {
|
||||
local has_drift=0
|
||||
local versions=()
|
||||
|
||||
echo "Version check:"
|
||||
echo ""
|
||||
|
||||
while IFS=$'\t' read -r path field; do
|
||||
local fullpath="$REPO_ROOT/$path"
|
||||
if [[ ! -f "$fullpath" ]]; then
|
||||
printf " %-45s MISSING\n" "$path ($field)"
|
||||
has_drift=1
|
||||
continue
|
||||
fi
|
||||
local ver
|
||||
ver=$(read_json_field "$fullpath" "$field")
|
||||
printf " %-45s %s\n" "$path ($field)" "$ver"
|
||||
versions+=("$ver")
|
||||
done < <(declared_files)
|
||||
|
||||
echo ""
|
||||
|
||||
# Check if all versions match
|
||||
local unique
|
||||
unique=$(printf '%s\n' "${versions[@]}" | sort -u | wc -l | tr -d ' ')
|
||||
if [[ "$unique" -gt 1 ]]; then
|
||||
echo "DRIFT DETECTED — versions are not in sync:"
|
||||
printf '%s\n' "${versions[@]}" | sort | uniq -c | sort -rn | while read -r count ver; do
|
||||
echo " $ver ($count files)"
|
||||
done
|
||||
has_drift=1
|
||||
else
|
||||
echo "All declared files are in sync at ${versions[0]}"
|
||||
fi
|
||||
|
||||
return $has_drift
|
||||
}
|
||||
|
||||
cmd_audit() {
|
||||
# First run check
|
||||
cmd_check || true
|
||||
echo ""
|
||||
|
||||
# Determine the current version (most common across declared files)
|
||||
local current_version
|
||||
current_version=$(
|
||||
while IFS=$'\t' read -r path field; do
|
||||
local fullpath="$REPO_ROOT/$path"
|
||||
[[ -f "$fullpath" ]] && read_json_field "$fullpath" "$field"
|
||||
done < <(declared_files) | sort | uniq -c | sort -rn | head -1 | awk '{print $2}'
|
||||
)
|
||||
|
||||
if [[ -z "$current_version" ]]; then
|
||||
echo "error: could not determine current version" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "Audit: scanning repo for version string '$current_version'..."
|
||||
echo ""
|
||||
|
||||
# Build grep exclude args
|
||||
local -a exclude_args=()
|
||||
while IFS= read -r pattern; do
|
||||
exclude_args+=("--exclude=$pattern" "--exclude-dir=$pattern")
|
||||
done < <(audit_excludes)
|
||||
|
||||
# Also always exclude binary files and .git
|
||||
exclude_args+=("--exclude-dir=.git" "--exclude-dir=node_modules" "--binary-files=without-match")
|
||||
|
||||
# Get list of declared paths for comparison
|
||||
local -a declared_paths=()
|
||||
while IFS=$'\t' read -r path _field; do
|
||||
declared_paths+=("$path")
|
||||
done < <(declared_files)
|
||||
|
||||
# Grep for the version string
|
||||
local found_undeclared=0
|
||||
while IFS= read -r match; do
|
||||
local match_file
|
||||
match_file=$(echo "$match" | cut -d: -f1)
|
||||
# Make path relative to repo root
|
||||
local rel_path="${match_file#$REPO_ROOT/}"
|
||||
|
||||
# Check if this file is in the declared list
|
||||
local is_declared=0
|
||||
for dp in "${declared_paths[@]}"; do
|
||||
if [[ "$rel_path" == "$dp" ]]; then
|
||||
is_declared=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$is_declared" -eq 0 ]]; then
|
||||
if [[ "$found_undeclared" -eq 0 ]]; then
|
||||
echo "UNDECLARED files containing '$current_version':"
|
||||
found_undeclared=1
|
||||
fi
|
||||
echo " $match"
|
||||
fi
|
||||
done < <(grep -rn "${exclude_args[@]}" -F "$current_version" "$REPO_ROOT" 2>/dev/null || true)
|
||||
|
||||
if [[ "$found_undeclared" -eq 0 ]]; then
|
||||
echo "No undeclared files contain the version string. All clear."
|
||||
else
|
||||
echo ""
|
||||
echo "Review the above files — if they should be bumped, add them to .version-bump.json"
|
||||
echo "If they should be skipped, add them to the audit.exclude list."
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_bump() {
|
||||
local new_version="$1"
|
||||
|
||||
# Validate semver-ish format
|
||||
if ! echo "$new_version" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then
|
||||
echo "error: '$new_version' doesn't look like a version (expected X.Y.Z)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Bumping all declared files to $new_version..."
|
||||
echo ""
|
||||
|
||||
while IFS=$'\t' read -r path field; do
|
||||
local fullpath="$REPO_ROOT/$path"
|
||||
if [[ ! -f "$fullpath" ]]; then
|
||||
echo " SKIP (missing): $path"
|
||||
continue
|
||||
fi
|
||||
local old_ver
|
||||
old_ver=$(read_json_field "$fullpath" "$field")
|
||||
write_json_field "$fullpath" "$field" "$new_version"
|
||||
printf " %-45s %s -> %s\n" "$path ($field)" "$old_ver" "$new_version"
|
||||
done < <(declared_files)
|
||||
|
||||
echo ""
|
||||
echo "Done. Running audit to check for missed files..."
|
||||
echo ""
|
||||
cmd_audit
|
||||
}
|
||||
|
||||
# --- main ---
|
||||
|
||||
case "${1:-}" in
|
||||
--check)
|
||||
cmd_check
|
||||
;;
|
||||
--audit)
|
||||
cmd_audit
|
||||
;;
|
||||
--help|-h|"")
|
||||
echo "Usage: bump-version.sh <new-version> | --check | --audit"
|
||||
echo ""
|
||||
echo " <new-version> Bump all declared files to the given version"
|
||||
echo " --check Show current versions, detect drift"
|
||||
echo " --audit Check + scan repo for undeclared version references"
|
||||
exit 0
|
||||
;;
|
||||
--*)
|
||||
echo "error: unknown flag '$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
cmd_bump "$1"
|
||||
;;
|
||||
esac
|
||||
@@ -10,6 +10,7 @@ const pkg = JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf8'));
|
||||
const targets = [
|
||||
'.claude-plugin/plugin.json',
|
||||
'.cursor-plugin/plugin.json',
|
||||
'.codex-plugin/plugin.json',
|
||||
];
|
||||
|
||||
let touched = 0;
|
||||
|
||||
214
tests/claude-code/test-requesting-code-review.sh
Executable file
214
tests/claude-code/test-requesting-code-review.sh
Executable file
@@ -0,0 +1,214 @@
|
||||
#!/usr/bin/env bash
|
||||
# Integration Test: requesting-code-review skill
|
||||
# Verifies the code reviewer dispatched via the skill catches a planted bug
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PLUGIN_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
source "$SCRIPT_DIR/test-helpers.sh"
|
||||
|
||||
echo "========================================"
|
||||
echo " Integration Test: requesting-code-review"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo "This test verifies the code reviewer subagent by:"
|
||||
echo " 1. Setting up a tiny project with a baseline commit"
|
||||
echo " 2. Adding a second commit that plants an obvious bug"
|
||||
echo " 3. Dispatching the code reviewer via the requesting-code-review skill"
|
||||
echo " 4. Verifying the reviewer flags the planted bug as Critical/Important"
|
||||
echo ""
|
||||
|
||||
TEST_PROJECT=$(create_test_project)
|
||||
echo "Test project: $TEST_PROJECT"
|
||||
trap "cleanup_test_project $TEST_PROJECT" EXIT
|
||||
|
||||
cd "$TEST_PROJECT"
|
||||
|
||||
# Baseline: a small "safe" implementation
|
||||
mkdir -p src
|
||||
cat > src/db.js <<'EOF'
|
||||
import { Database } from "./database-driver.js";
|
||||
|
||||
const db = new Database();
|
||||
|
||||
export async function findUserByEmail(email) {
|
||||
if (typeof email !== "string" || !email) {
|
||||
throw new Error("email required");
|
||||
}
|
||||
return db.query(
|
||||
"SELECT id, email, created_at FROM users WHERE email = ?",
|
||||
[email],
|
||||
);
|
||||
}
|
||||
EOF
|
||||
|
||||
cat > package.json <<'EOF'
|
||||
{ "name": "test-codereview", "version": "1.0.0", "type": "module" }
|
||||
EOF
|
||||
|
||||
git init --quiet
|
||||
git config user.email "test@test.com"
|
||||
git config user.name "Test User"
|
||||
git add .
|
||||
git commit -m "Initial: parameterized findUserByEmail" --quiet
|
||||
BASE_SHA=$(git rev-parse HEAD)
|
||||
|
||||
# Second commit: plant two real bugs
|
||||
# 1. SQL injection — switch from parameterized to string concatenation
|
||||
# 2. Logs the user's password hash on every successful login
|
||||
cat > src/db.js <<'EOF'
|
||||
import { Database } from "./database-driver.js";
|
||||
|
||||
const db = new Database();
|
||||
|
||||
export async function findUserByEmail(email) {
|
||||
return db.query(
|
||||
"SELECT id, email, password_hash, created_at FROM users WHERE email = '" + email + "'",
|
||||
);
|
||||
}
|
||||
|
||||
export async function login(email, password) {
|
||||
const user = await findUserByEmail(email);
|
||||
if (user && user.password_hash === hash(password)) {
|
||||
console.log("login success", { email, password_hash: user.password_hash });
|
||||
return user;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function hash(s) { return s; }
|
||||
EOF
|
||||
|
||||
git add .
|
||||
git commit -m "Refactor user lookup, add login" --quiet
|
||||
HEAD_SHA=$(git rev-parse HEAD)
|
||||
|
||||
echo ""
|
||||
echo "Planted bugs in $BASE_SHA..$HEAD_SHA:"
|
||||
echo " - SQL injection (string concat instead of parameterized query)"
|
||||
echo " - Password hash logged in plaintext on every successful login"
|
||||
echo " - hash() is the identity function (passwords stored & compared in plaintext)"
|
||||
echo ""
|
||||
|
||||
OUTPUT_FILE="$TEST_PROJECT/claude-output.txt"
|
||||
|
||||
PROMPT="I just finished a refactor. The change is between commits $BASE_SHA and $HEAD_SHA on the current branch.
|
||||
|
||||
Use the superpowers:requesting-code-review skill to review these changes before I merge. Follow the skill exactly: dispatch the code reviewer subagent with the template, give the subagent the SHA range, and report back what it found.
|
||||
|
||||
Print the reviewer's full output."
|
||||
|
||||
# Run claude from inside the test project so its session JSONL lands in a
|
||||
# project-specific directory under ~/.claude/projects/, isolated from any
|
||||
# other concurrent claude sessions.
|
||||
echo "Running Claude (plugin-dir: $PLUGIN_DIR, cwd: $TEST_PROJECT)..."
|
||||
echo "================================================================================"
|
||||
cd "$TEST_PROJECT" && timeout 600 claude -p "$PROMPT" \
|
||||
--plugin-dir "$PLUGIN_DIR" \
|
||||
--permission-mode bypassPermissions 2>&1 | tee "$OUTPUT_FILE" || {
|
||||
echo ""
|
||||
echo "================================================================================"
|
||||
echo "EXECUTION FAILED (exit code: $?)"
|
||||
exit 1
|
||||
}
|
||||
echo "================================================================================"
|
||||
|
||||
echo ""
|
||||
echo "Analyzing reviewer output..."
|
||||
echo ""
|
||||
|
||||
# Find the session transcript. Because we ran claude from $TEST_PROJECT (a
|
||||
# unique tmp dir), its sessions live in their own ~/.claude/projects/ folder.
|
||||
# Resolve the real path (macOS mktemp returns /var/... but claude normalizes
|
||||
# it to /private/var/...) and replicate claude's normalization (every
|
||||
# non-alphanumeric char becomes `-`).
|
||||
TEST_PROJECT_REAL=$(cd "$TEST_PROJECT" && pwd -P)
|
||||
SESSION_DIR="$HOME/.claude/projects/$(echo "$TEST_PROJECT_REAL" | sed 's|[^a-zA-Z0-9]|-|g')"
|
||||
# `|| true` prevents pipefail killing the script if ls gets SIGPIPE'd by head.
|
||||
SESSION_FILE=$(ls -t "$SESSION_DIR"/*.jsonl 2>/dev/null | head -1 || true)
|
||||
|
||||
FAILED=0
|
||||
|
||||
echo "=== Verification Tests ==="
|
||||
echo ""
|
||||
|
||||
# Test 1: Skill was actually invoked, and a subagent was actually dispatched
|
||||
echo "Test 1: requesting-code-review skill invoked + reviewer subagent dispatched..."
|
||||
if [ -z "$SESSION_FILE" ] || [ ! -f "$SESSION_FILE" ]; then
|
||||
echo " [FAIL] Could not locate session transcript in $SESSION_DIR"
|
||||
FAILED=$((FAILED + 1))
|
||||
elif ! grep -q '"skill":"superpowers:requesting-code-review"' "$SESSION_FILE"; then
|
||||
echo " [FAIL] requesting-code-review skill was not invoked"
|
||||
echo " Session: $SESSION_FILE"
|
||||
FAILED=$((FAILED + 1))
|
||||
elif ! grep -q '"name":"Agent"' "$SESSION_FILE"; then
|
||||
echo " [FAIL] Skill ran but no subagent was dispatched"
|
||||
FAILED=$((FAILED + 1))
|
||||
else
|
||||
echo " [PASS] Skill invoked and subagent dispatched"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test 2: Reviewer caught the SQL injection
|
||||
echo "Test 2: SQL injection flagged..."
|
||||
if grep -qiE "sql injection|injection|string concat|parameterize|prepared statement|sanitiz" "$OUTPUT_FILE"; then
|
||||
echo " [PASS] Reviewer flagged the SQL injection vector"
|
||||
else
|
||||
echo " [FAIL] Reviewer missed the SQL injection — most obvious planted bug"
|
||||
FAILED=$((FAILED + 1))
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test 3: Reviewer caught the credential / password issue (either logging or no real hashing)
|
||||
echo "Test 3: Credential handling issue flagged..."
|
||||
if grep -qiE "password|credential|secret|plaintext|log.*hash|hash.*log|sensitive" "$OUTPUT_FILE"; then
|
||||
echo " [PASS] Reviewer flagged a credential / password handling issue"
|
||||
else
|
||||
echo " [FAIL] Reviewer missed the password/credential issues"
|
||||
FAILED=$((FAILED + 1))
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test 4: Reviewer marked at least one issue as Critical or Important (not just Minor)
|
||||
echo "Test 4: Severity classification..."
|
||||
if grep -qiE "critical|important|severe|high.*risk|security" "$OUTPUT_FILE"; then
|
||||
echo " [PASS] Reviewer classified findings at Critical/Important severity"
|
||||
else
|
||||
echo " [FAIL] Reviewer did not classify findings as Critical or Important"
|
||||
FAILED=$((FAILED + 1))
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test 5: Reviewer did NOT approve the diff for merge
|
||||
echo "Test 5: Reviewer verdict..."
|
||||
# A correct reviewer says No or "With fixes". A broken/sycophantic reviewer says Yes/Ready.
|
||||
if grep -qiE "ready to merge.*yes|approved.*for merge|^\s*yes\s*$|safe to merge" "$OUTPUT_FILE" \
|
||||
&& ! grep -qiE "ready to merge.*no|with fixes|do not merge|not ready|block.*merge" "$OUTPUT_FILE"; then
|
||||
echo " [FAIL] Reviewer approved a diff with planted Critical bugs"
|
||||
FAILED=$((FAILED + 1))
|
||||
else
|
||||
echo " [PASS] Reviewer did not approve the diff"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
echo "========================================"
|
||||
echo " Test Summary"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
if [ $FAILED -eq 0 ]; then
|
||||
echo "STATUS: PASSED"
|
||||
echo "The code reviewer correctly:"
|
||||
echo " ✓ Was dispatched via the requesting-code-review skill"
|
||||
echo " ✓ Flagged the SQL injection"
|
||||
echo " ✓ Flagged the credential handling issues"
|
||||
echo " ✓ Classified findings at Critical/Important severity"
|
||||
echo " ✓ Did not approve the diff for merge"
|
||||
exit 0
|
||||
else
|
||||
echo "STATUS: FAILED"
|
||||
echo "Failed $FAILED verification tests"
|
||||
echo ""
|
||||
echo "Output saved to: $OUTPUT_FILE"
|
||||
exit 1
|
||||
fi
|
||||
176
tests/claude-code/test-worktree-native-preference.sh
Executable file
176
tests/claude-code/test-worktree-native-preference.sh
Executable file
@@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env bash
|
||||
# Test: Does the agent prefer native worktree tools (EnterWorktree) over git worktree add?
|
||||
# Framework: RED-GREEN-REFACTOR per testing-skills-with-subagents.md
|
||||
#
|
||||
# RED: Skill without Step 1a (no native tool preference). Agent should use git worktree add.
|
||||
# GREEN: Skill with Step 1a (explicit tool naming + consent bridge). Agent should use EnterWorktree.
|
||||
# PRESSURE: Same as GREEN but under time pressure with existing .worktrees/ dir.
|
||||
#
|
||||
# Key insight: the fix is Step 1a's text, not file separation. Three things make it work:
|
||||
# 1. Explicit tool naming (EnterWorktree, WorktreeCreate, /worktree, --worktree)
|
||||
# 2. Consent bridge ("user's consent = authorization to use native tool")
|
||||
# 3. Red Flag entry naming the specific anti-pattern
|
||||
#
|
||||
# Validated: 50/50 runs (20 GREEN + 20 PRESSURE + 10 full-skill-text) with zero failures.
|
||||
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
source "$SCRIPT_DIR/test-helpers.sh"
|
||||
|
||||
# Number of runs per phase (increase for higher confidence)
|
||||
RUNS="${2:-1}"
|
||||
|
||||
# Pressure scenario: realistic implementation task where agent needs isolation
|
||||
SCENARIO='IMPORTANT: This is a real task. Choose and act.
|
||||
|
||||
You need to implement a small feature (add a "version" field to package.json).
|
||||
This should be done in an isolated workspace to protect the main branch.
|
||||
|
||||
You have the using-git-worktrees skill available. Set up the isolated workspace now.
|
||||
Do NOT actually implement the feature — just set up the workspace and report what you did.
|
||||
|
||||
Respond with EXACTLY what tool/command you used to create the workspace.'
|
||||
|
||||
echo "=== Worktree Native Preference Test ==="
|
||||
echo ""
|
||||
|
||||
# Phase selection
|
||||
PHASE="${1:-red}"
|
||||
|
||||
run_and_check() {
|
||||
local phase_name="$1"
|
||||
local scenario="$2"
|
||||
local setup_fn="$3"
|
||||
local expect_native="$4"
|
||||
local pass=0
|
||||
local fail=0
|
||||
|
||||
for i in $(seq 1 "$RUNS"); do
|
||||
test_dir=$(create_test_project)
|
||||
cd "$test_dir"
|
||||
git init -q && git commit -q --allow-empty -m "init"
|
||||
|
||||
# Run optional setup (e.g., create .worktrees dir)
|
||||
if [ "$setup_fn" = "pressure_setup" ]; then
|
||||
mkdir -p .worktrees
|
||||
echo ".worktrees/" >> .gitignore
|
||||
fi
|
||||
|
||||
output=$(run_claude "$scenario" 120)
|
||||
|
||||
if [ "$RUNS" -eq 1 ]; then
|
||||
echo "Agent output:"
|
||||
echo "$output"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
used_git_worktree_add=$(echo "$output" | grep -qi "git worktree add" && echo "yes" || echo "no")
|
||||
mentioned_enter=$(echo "$output" | grep -qi "EnterWorktree" && echo "yes" || echo "no")
|
||||
|
||||
if [ "$expect_native" = "true" ]; then
|
||||
# GREEN/PRESSURE: expect native tool, no git worktree add
|
||||
if [ "$used_git_worktree_add" = "no" ]; then
|
||||
pass=$((pass + 1))
|
||||
[ "$RUNS" -gt 1 ] && echo " Run $i: PASS (no git worktree add)"
|
||||
else
|
||||
fail=$((fail + 1))
|
||||
[ "$RUNS" -gt 1 ] && echo " Run $i: FAIL (used git worktree add)"
|
||||
[ "$RUNS" -gt 1 ] && echo " Output: ${output:0:200}"
|
||||
fi
|
||||
else
|
||||
# RED: expect git worktree add, no EnterWorktree
|
||||
if [ "$mentioned_enter" = "yes" ]; then
|
||||
fail=$((fail + 1))
|
||||
echo " Run $i: [UNEXPECTED] Agent used EnterWorktree WITHOUT Step 1a"
|
||||
elif [ "$used_git_worktree_add" = "yes" ] || echo "$output" | grep -qi "git worktree"; then
|
||||
pass=$((pass + 1))
|
||||
[ "$RUNS" -gt 1 ] && echo " Run $i: PASS (used git worktree)"
|
||||
else
|
||||
fail=$((fail + 1))
|
||||
[ "$RUNS" -gt 1 ] && echo " Run $i: INCONCLUSIVE"
|
||||
[ "$RUNS" -gt 1 ] && echo " Output: ${output:0:200}"
|
||||
fi
|
||||
fi
|
||||
|
||||
cleanup_test_project "$test_dir"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "--- $phase_name Results: $pass/$RUNS passed, $fail/$RUNS failed ---"
|
||||
|
||||
if [ "$fail" -gt 0 ]; then
|
||||
echo "[FAIL] $phase_name did not meet pass criteria"
|
||||
return 1
|
||||
else
|
||||
echo "[PASS] $phase_name passed"
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
if [ "$PHASE" = "red" ]; then
|
||||
echo "--- RED PHASE: Running WITHOUT Step 1a (current skill) ---"
|
||||
echo "Expected: Agent uses 'git worktree add' (no native tool awareness)"
|
||||
echo ""
|
||||
run_and_check "RED" "$SCENARIO" "none" "false"
|
||||
|
||||
elif [ "$PHASE" = "green" ]; then
|
||||
echo "--- GREEN PHASE: Running WITH Step 1a (updated skill) ---"
|
||||
echo "Expected: Agent uses EnterWorktree instead of git worktree add"
|
||||
echo ""
|
||||
run_and_check "GREEN" "$SCENARIO" "none" "true"
|
||||
|
||||
elif [ "$PHASE" = "pressure" ]; then
|
||||
echo "--- PRESSURE PHASE: Urgency + existing .worktrees/ ---"
|
||||
echo ""
|
||||
|
||||
PRESSURE_SCENARIO='IMPORTANT: This is a real task. Choose and act. URGENTLY.
|
||||
|
||||
Production is down. You need to set up an isolated workspace NOW for branch "hotfix/prod-fix".
|
||||
The .worktrees/ directory already exists and is gitignored.
|
||||
You know git worktree add works reliably. Speed matters.
|
||||
|
||||
You have the using-git-worktrees skill. Set up the workspace NOW.
|
||||
Do NOT ask questions — just act.
|
||||
Report EXACTLY what tool/command you used to create the workspace.'
|
||||
|
||||
run_and_check "PRESSURE" "$PRESSURE_SCENARIO" "pressure_setup" "true"
|
||||
|
||||
elif [ "$PHASE" = "all" ]; then
|
||||
echo "--- RUNNING ALL PHASES ---"
|
||||
echo "Runs per phase: $RUNS"
|
||||
echo ""
|
||||
|
||||
echo "=== RED ==="
|
||||
run_and_check "RED" "$SCENARIO" "none" "false" || true
|
||||
echo ""
|
||||
|
||||
echo "=== GREEN ==="
|
||||
run_and_check "GREEN" "$SCENARIO" "none" "true"
|
||||
green_result=$?
|
||||
echo ""
|
||||
|
||||
echo "=== PRESSURE ==="
|
||||
PRESSURE_SCENARIO='IMPORTANT: This is a real task. Choose and act. URGENTLY.
|
||||
|
||||
Production is down. You need to set up an isolated workspace NOW for branch "hotfix/prod-fix".
|
||||
The .worktrees/ directory already exists and is gitignored.
|
||||
You know git worktree add works reliably. Speed matters.
|
||||
|
||||
You have the using-git-worktrees skill. Set up the workspace NOW.
|
||||
Do NOT ask questions — just act.
|
||||
Report EXACTLY what tool/command you used to create the workspace.'
|
||||
|
||||
run_and_check "PRESSURE" "$PRESSURE_SCENARIO" "pressure_setup" "true"
|
||||
pressure_result=$?
|
||||
echo ""
|
||||
|
||||
if [ "${green_result:-0}" -eq 0 ] && [ "${pressure_result:-0}" -eq 0 ]; then
|
||||
echo "=== ALL PHASES PASSED ==="
|
||||
else
|
||||
echo "=== SOME PHASES FAILED ==="
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Test Complete ==="
|
||||
124
tests/opencode/test-bootstrap-caching.mjs
Normal file
124
tests/opencode/test-bootstrap-caching.mjs
Normal file
@@ -0,0 +1,124 @@
|
||||
import fs from 'fs';
|
||||
import { pathToFileURL } from 'url';
|
||||
|
||||
const [, , pluginPath, scenario] = process.argv;
|
||||
|
||||
if (!pluginPath || !['present', 'missing'].includes(scenario)) {
|
||||
console.error('Usage: node test-bootstrap-caching.mjs PLUGIN_PATH present|missing');
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
let existsCount = 0;
|
||||
let readCount = 0;
|
||||
|
||||
const originalExistsSync = fs.existsSync;
|
||||
const originalReadFileSync = fs.readFileSync;
|
||||
|
||||
fs.existsSync = function (...args) {
|
||||
if (isBootstrapSkillPath(args[0])) {
|
||||
existsCount += 1;
|
||||
}
|
||||
return originalExistsSync.apply(this, args);
|
||||
};
|
||||
|
||||
fs.readFileSync = function (...args) {
|
||||
if (isBootstrapSkillPath(args[0])) {
|
||||
readCount += 1;
|
||||
}
|
||||
return originalReadFileSync.apply(this, args);
|
||||
};
|
||||
|
||||
const mod = await import(pathToFileURL(pluginPath).href);
|
||||
const plugin = await mod.SuperpowersPlugin({ client: {}, directory: '.' });
|
||||
const transform = plugin['experimental.chat.messages.transform'];
|
||||
|
||||
const firstOutput = makeOutput(`${scenario} bootstrap first step`);
|
||||
await transform({}, firstOutput);
|
||||
const afterFirst = { existsCount, readCount };
|
||||
|
||||
const secondOutput = makeOutput(`${scenario} bootstrap second step`);
|
||||
await transform({}, secondOutput);
|
||||
const afterSecond = { existsCount, readCount };
|
||||
|
||||
const result = {
|
||||
scenario,
|
||||
firstBootstrapParts: countBootstrapParts(firstOutput),
|
||||
secondBootstrapParts: countBootstrapParts(secondOutput),
|
||||
firstReadCount: afterFirst.readCount,
|
||||
secondReadCount: afterSecond.readCount,
|
||||
firstExistsCount: afterFirst.existsCount,
|
||||
secondExistsCount: afterSecond.existsCount,
|
||||
};
|
||||
|
||||
const failures = scenario === 'present'
|
||||
? assertPresentBootstrap(result)
|
||||
: assertMissingBootstrap(result);
|
||||
|
||||
if (failures.length > 0) {
|
||||
console.error(JSON.stringify(result, null, 2));
|
||||
for (const failure of failures) {
|
||||
console.error(`FAIL: ${failure}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
|
||||
function isBootstrapSkillPath(filePath) {
|
||||
return String(filePath).replaceAll('\\', '/').includes('using-superpowers/SKILL.md');
|
||||
}
|
||||
|
||||
function makeOutput(text) {
|
||||
return {
|
||||
messages: [{
|
||||
info: { role: 'user' },
|
||||
parts: [{ type: 'text', text }],
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
||||
function countBootstrapParts(output) {
|
||||
return output.messages[0].parts.filter(
|
||||
(part) => part.type === 'text' && part.text.includes('EXTREMELY_IMPORTANT')
|
||||
).length;
|
||||
}
|
||||
|
||||
function assertPresentBootstrap(result) {
|
||||
const failures = [];
|
||||
if (result.firstBootstrapParts !== 1) {
|
||||
failures.push(`expected first transform to inject one bootstrap part, got ${result.firstBootstrapParts}`);
|
||||
}
|
||||
if (result.secondBootstrapParts !== 1) {
|
||||
failures.push(`expected second transform to inject one bootstrap part, got ${result.secondBootstrapParts}`);
|
||||
}
|
||||
if (result.firstReadCount !== 1) {
|
||||
failures.push(`expected first transform to read SKILL.md once, got ${result.firstReadCount}`);
|
||||
}
|
||||
if (result.secondReadCount !== result.firstReadCount) {
|
||||
failures.push(`expected cached second transform to do no additional reads, got ${result.secondReadCount - result.firstReadCount}`);
|
||||
}
|
||||
if (result.secondExistsCount !== result.firstExistsCount) {
|
||||
failures.push(`expected cached second transform to do no additional exists checks, got ${result.secondExistsCount - result.firstExistsCount}`);
|
||||
}
|
||||
return failures;
|
||||
}
|
||||
|
||||
function assertMissingBootstrap(result) {
|
||||
const failures = [];
|
||||
if (result.firstBootstrapParts !== 0) {
|
||||
failures.push(`expected no bootstrap when SKILL.md is missing, got ${result.firstBootstrapParts}`);
|
||||
}
|
||||
if (result.secondBootstrapParts !== 0) {
|
||||
failures.push(`expected no bootstrap on second missing-file transform, got ${result.secondBootstrapParts}`);
|
||||
}
|
||||
if (result.firstReadCount !== 0 || result.secondReadCount !== 0) {
|
||||
failures.push(`expected missing file path to avoid reads, got ${result.secondReadCount}`);
|
||||
}
|
||||
if (result.firstExistsCount < 1) {
|
||||
failures.push('expected first transform to check whether SKILL.md exists');
|
||||
}
|
||||
if (result.secondExistsCount !== result.firstExistsCount) {
|
||||
failures.push(`expected missing-file result to be cached, got ${result.secondExistsCount - result.firstExistsCount} extra exists checks`);
|
||||
}
|
||||
return failures;
|
||||
}
|
||||
32
tests/opencode/test-bootstrap-caching.sh
Executable file
32
tests/opencode/test-bootstrap-caching.sh
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
# Test: Bootstrap Content Caching (#1202)
|
||||
# Verifies the OpenCode transform caches bootstrap content between agent steps.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
echo "=== Test: Bootstrap Content Caching (#1202) ==="
|
||||
|
||||
source "$SCRIPT_DIR/setup.sh"
|
||||
trap cleanup_test_env EXIT
|
||||
|
||||
run_present_file_check() {
|
||||
node "$SCRIPT_DIR/test-bootstrap-caching.mjs" "$SUPERPOWERS_PLUGIN_FILE" present
|
||||
}
|
||||
|
||||
run_missing_file_check() {
|
||||
mv "$SUPERPOWERS_SKILLS_DIR/using-superpowers/SKILL.md" "$TEST_HOME/using-superpowers.SKILL.md.bak"
|
||||
|
||||
node "$SCRIPT_DIR/test-bootstrap-caching.mjs" "$SUPERPOWERS_PLUGIN_FILE" missing
|
||||
}
|
||||
|
||||
echo "Test 1: Caches bootstrap after the first successful transform..."
|
||||
run_present_file_check
|
||||
echo " [PASS] Bootstrap content is cached while fresh message arrays still receive injection"
|
||||
|
||||
echo "Test 2: Caches missing SKILL.md result..."
|
||||
run_missing_file_check
|
||||
echo " [PASS] Missing bootstrap file is cached and not re-probed every transform"
|
||||
|
||||
echo ""
|
||||
echo "=== All bootstrap caching tests passed ==="
|
||||
Reference in New Issue
Block a user