Files
superpowers-zh/.pi/extensions/superpowers.ts
AI不止语 4909388f35 feat(pi): 新增 Pi (oh-my-pi) harness 支持(关 #44,对齐上游 v6.0.0)
issue #44:Pi 像 opencode 一样开放,skill:// 能直接用,已验证可手动使用,
希望原生支持。上游 obra/superpowers 在 v6.0.0 已用扩展模型原生集成 Pi,
本提交把同样的集成方式落到本 fork。

Pi 走扩展模型,通过 package.json 的 pi 字段声明,直接指向仓库现有
skills/,不复制 skill、无运行时依赖。扩展内容中立——读取 fork 现有的
中文 using-superpowers/SKILL.md 自动注入,故扩展代码逐字节照搬上游。

- .pi/extensions/superpowers.ts:注册 resources_discover / session_start /
  session_compact / agent_end / context 生命周期钩子,在会话注入
  using-superpowers bootstrap + Pi 工具映射(带去重标记、插在 compaction
  summary 之后)
- package.json:加 pi.skills=["./skills"] + pi.extensions + pi-package
  keyword;.pi/extensions/ 加入 files(npm 发布需含扩展)
- skills/using-superpowers/references/pi-tools.md:Pi 工具映射参考
- docs/README.pi.md:中文安装/原理/工具映射/验证指南;README.md 工具列表加链接
- tests/pi/:上游扩展行为测试(适配 fork:name=superpowers-zh)+ 运行包装

验证:bash tests/pi/run-tests.sh 6/6 通过 exit 0(校验 pi 包配置、生命周期
钩子无 pre-compaction 注入、resources_discover 贡献 skills 目录、session_start
注入 You-have-superpowers + Pi-tool-mapping、pi-tools 参考存在);package.json
合法;scripts/audit.sh 静态 0 FAIL;README→docs/README.pi.md 链接可解析。

注:扩展是 TS(仅 import type,运行时无类型依赖),Node 22.6–23.5 需
--experimental-strip-types(run-tests.sh 已带),23.6+ 默认支持。
Pi 内实际 skill 触发需在 Pi 内验证(与本 fork 其它 harness 同样限制)。
2026-06-20 02:53:31 +08:00

122 lines
4.2 KiB
TypeScript

import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
const EXTREMELY_IMPORTANT_MARKER = "<EXTREMELY_IMPORTANT>";
const BOOTSTRAP_MARKER = "superpowers:using-superpowers bootstrap for pi";
const extensionDir = dirname(fileURLToPath(import.meta.url));
const packageRoot = resolve(extensionDir, "../..");
const skillsDir = resolve(packageRoot, "skills");
const bootstrapSkillPath = resolve(skillsDir, "using-superpowers", "SKILL.md");
let cachedBootstrap: string | null | undefined;
export default function superpowersPiExtension(pi: ExtensionAPI) {
let injectBootstrap = true;
pi.on("resources_discover", async () => ({
skillPaths: [skillsDir],
}));
pi.on("session_start", async () => {
injectBootstrap = true;
});
pi.on("session_compact", async () => {
injectBootstrap = true;
});
pi.on("agent_end", async () => {
injectBootstrap = false;
});
pi.on("context", async (event) => {
if (!injectBootstrap) return;
if (event.messages.some(messageContainsBootstrap)) return;
const bootstrap = getBootstrapContent();
if (!bootstrap) return;
const bootstrapMessage = {
role: "user" as const,
content: [{ type: "text" as const, text: bootstrap }],
timestamp: Date.now(),
};
const insertAt = firstNonCompactionSummaryIndex(event.messages);
return {
messages: [
...event.messages.slice(0, insertAt),
bootstrapMessage,
...event.messages.slice(insertAt),
],
};
});
}
function getBootstrapContent(): string | null {
if (cachedBootstrap !== undefined) return cachedBootstrap;
try {
const skillContent = readFileSync(bootstrapSkillPath, "utf8");
const body = stripFrontmatter(skillContent);
cachedBootstrap = `${EXTREMELY_IMPORTANT_MARKER}
${BOOTSTRAP_MARKER}
You have superpowers.
The using-superpowers skill content is included below and is already loaded for this Pi session. Follow it now. Do not try to load using-superpowers again.
${body}
${piToolMapping()}
</EXTREMELY_IMPORTANT>`;
return cachedBootstrap;
} catch {
cachedBootstrap = null;
return null;
}
}
function stripFrontmatter(content: string): string {
const match = content.match(/^---\n[\s\S]*?\n---\n([\s\S]*)$/);
return (match ? match[1] : content).trim();
}
function piToolMapping(): string {
return `## Pi tool mapping
Pi has native skills but does not expose Claude Code's \`Skill\` tool. When a Superpowers instruction says to invoke a skill, use Pi's native skill system instead: load the relevant \`SKILL.md\` with \`read\` when the skill applies, or let a human invoke \`/skill:name\` explicitly.
Pi's built-in coding tools are lowercase: \`read\`, \`write\`, \`edit\`, \`bash\`, plus optional \`grep\`, \`find\`, and \`ls\`. Use those for the corresponding actions: read a file, create or edit files, run shell commands, search file contents, find files by name, and list directories.
Pi does not ship a standard subagent tool. If a subagent tool such as \`subagent\` from \`pi-subagents\` is available, use it for Superpowers subagent workflows. If no subagent tool is available, do the work in this session or explain the missing capability instead of inventing \`Task\` calls.
Pi does not ship a standard task-list tool. If an installed todo/task tool is available, use it. Otherwise track work in plan files or a repo-local \`TODO.md\` when task tracking is needed. Treat older \`TodoWrite\` references as this task-tracking action.`;
}
function messageContainsBootstrap(message: unknown): boolean {
const content = (message as { content?: unknown }).content;
if (typeof content === "string") return content.includes(BOOTSTRAP_MARKER);
if (!Array.isArray(content)) return false;
return content.some((part) => {
return (
part &&
typeof part === "object" &&
(part as { type?: unknown }).type === "text" &&
typeof (part as { text?: unknown }).text === "string" &&
(part as { text: string }).text.includes(BOOTSTRAP_MARKER)
);
});
}
function firstNonCompactionSummaryIndex(messages: unknown[]): number {
let index = 0;
while ((messages[index] as { role?: unknown } | undefined)?.role === "compactionSummary") {
index += 1;
}
return index;
}