This commit is contained in:
+105
@@ -1,3 +1,5 @@
|
||||
import { join } from "jsr:@std/path@1";
|
||||
|
||||
export type BotType = "claude" | "codex";
|
||||
|
||||
export type InstallCommand = {
|
||||
@@ -5,6 +7,24 @@ export type InstallCommand = {
|
||||
args: string[];
|
||||
};
|
||||
|
||||
export type SuperpowersReviewGuide = {
|
||||
version: string;
|
||||
skillPath: string;
|
||||
templatePath: string;
|
||||
skill: string;
|
||||
template: string;
|
||||
};
|
||||
|
||||
export type SuperpowersFileSystem = {
|
||||
readDir(path: string): AsyncIterable<Deno.DirEntry>;
|
||||
readTextFile(path: string): Promise<string>;
|
||||
};
|
||||
|
||||
const systemFileSystem: SuperpowersFileSystem = {
|
||||
readDir: Deno.readDir,
|
||||
readTextFile: Deno.readTextFile,
|
||||
};
|
||||
|
||||
export function parseBotType(value: string): BotType {
|
||||
if (value === "claude" || value === "codex") return value;
|
||||
throw new Error(`unsupported bot type: ${value}`);
|
||||
@@ -40,3 +60,88 @@ export function superpowersInstallCommands(
|
||||
args: ["plugin", "add", "superpowers@openai-curated-remote"],
|
||||
}];
|
||||
}
|
||||
|
||||
type Candidate = SuperpowersReviewGuide & { directory: string };
|
||||
|
||||
async function readCandidate(
|
||||
directory: string,
|
||||
fileSystem: SuperpowersFileSystem,
|
||||
): Promise<Candidate | null> {
|
||||
if (!directory.split(/[\\/]/).includes("superpowers")) return null;
|
||||
const skillPath = join(directory, "SKILL.md");
|
||||
const templatePath = join(directory, "code-reviewer.md");
|
||||
try {
|
||||
const [skill, template] = await Promise.all([
|
||||
fileSystem.readTextFile(skillPath),
|
||||
fileSystem.readTextFile(templatePath),
|
||||
]);
|
||||
return {
|
||||
directory,
|
||||
version: directory.split(/[\\/]/).at(-3) ?? "unknown",
|
||||
skillPath,
|
||||
templatePath,
|
||||
skill,
|
||||
template,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof Deno.errors.NotFound) return null;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function findCandidates(
|
||||
directory: string,
|
||||
candidates: Candidate[],
|
||||
fileSystem: SuperpowersFileSystem,
|
||||
): Promise<void> {
|
||||
let entries: Deno.DirEntry[];
|
||||
try {
|
||||
entries = [];
|
||||
for await (const entry of fileSystem.readDir(directory)) {
|
||||
entries.push(entry);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Deno.errors.NotFound) return;
|
||||
throw error;
|
||||
}
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory) continue;
|
||||
const child = join(directory, entry.name);
|
||||
if (entry.name === "requesting-code-review") {
|
||||
const candidate = await readCandidate(child, fileSystem);
|
||||
if (candidate !== null) candidates.push(candidate);
|
||||
} else {
|
||||
await findCandidates(child, candidates, fileSystem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load the installed files rather than trusting skill discovery in a later
|
||||
// non-interactive agent process. The newest cached plugin version is the one
|
||||
// the installers activate, and the exact paths are reported by the caller.
|
||||
export async function loadSuperpowersReviewGuide(
|
||||
bot: BotType,
|
||||
home: string,
|
||||
fileSystem: SuperpowersFileSystem = systemFileSystem,
|
||||
): Promise<SuperpowersReviewGuide> {
|
||||
const root = join(
|
||||
home,
|
||||
bot === "codex" ? ".codex" : ".claude",
|
||||
"plugins",
|
||||
"cache",
|
||||
);
|
||||
const candidates: Candidate[] = [];
|
||||
await findCandidates(root, candidates, fileSystem);
|
||||
candidates.sort((left, right) =>
|
||||
left.version.localeCompare(right.version, undefined, { numeric: true }) ||
|
||||
left.directory.localeCompare(right.directory)
|
||||
);
|
||||
const guide = candidates.at(-1);
|
||||
if (guide === undefined) {
|
||||
throw new Error(
|
||||
`installed Superpowers requesting-code-review files not found under ${root}`,
|
||||
);
|
||||
}
|
||||
const { directory: _, ...result } = guide;
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user