test(plugins): pin bundled hook names

This commit is contained in:
Vincent Koc
2026-04-22 12:22:21 -07:00
parent d8935ca838
commit 9a14307306

View File

@@ -19,6 +19,33 @@ const BUNDLED_TYPED_HOOK_REGISTRATION_FILES = [
"extensions/skill-workshop/index.ts",
"extensions/thread-ownership/index.ts",
] as const;
const BUNDLED_TYPED_HOOK_REGISTRATION_GUARDS = {
"extensions/acpx/index.ts": ["reply_dispatch"],
"extensions/active-memory/index.ts": ["before_prompt_build"],
"extensions/diffs/src/plugin.ts": ["before_prompt_build"],
"extensions/discord/subagent-hooks-api.ts": [
"subagent_delivery_target",
"subagent_ended",
"subagent_spawning",
],
"extensions/feishu/subagent-hooks-api.ts": [
"subagent_delivery_target",
"subagent_ended",
"subagent_spawning",
],
"extensions/matrix/subagent-hooks-api.ts": [
"subagent_delivery_target",
"subagent_ended",
"subagent_spawning",
],
"extensions/memory-core/src/dreaming.ts": ["before_agent_reply", "gateway_start"],
"extensions/memory-lancedb/index.ts": ["agent_end", "before_prompt_build"],
"extensions/skill-workshop/index.ts": ["agent_end", "before_prompt_build"],
"extensions/thread-ownership/index.ts": ["message_received", "message_sending"],
} as const satisfies Record<
(typeof BUNDLED_TYPED_HOOK_REGISTRATION_FILES)[number],
readonly string[]
>;
type FileFilter = {
excludeTests?: boolean;
@@ -86,6 +113,13 @@ function collectBundledExtensionImports(source: string): string[] {
.filter((specifier): specifier is string => typeof specifier === "string");
}
function collectTypedHookNames(source: string): string[] {
return [...source.matchAll(/\bapi\.on\(\s*"([^"]+)"/gu)]
.map((match) => match[1])
.filter((hookName): hookName is string => typeof hookName === "string")
.toSorted();
}
describe("plugin contract boundary invariants", () => {
it("keeps bundled-capability-metadata confined to contract/test inventory", () => {
const files = listTsFiles("src");
@@ -163,6 +197,17 @@ describe("plugin contract boundary invariants", () => {
expect(hookRegistrationFiles).toEqual(BUNDLED_TYPED_HOOK_REGISTRATION_FILES);
});
it("keeps bundled plugin typed hook names on an explicit allowlist", () => {
expect(
Object.fromEntries(
BUNDLED_TYPED_HOOK_REGISTRATION_FILES.map((file) => [
file,
collectTypedHookNames(readRepoSource(file)),
]),
),
).toEqual(BUNDLED_TYPED_HOOK_REGISTRATION_GUARDS);
});
it("keeps bundled plugin production code off raw registerHook calls", () => {
const files = listTsFiles("extensions", { excludeTests: true });
const offenders = files.filter((file) => /\bregisterHook\(/u.test(readRepoSource(file)));