import { readFileSync, readdirSync } from "node:fs" ;
import { dirname, relative, resolve, sep } from "node:path" ;
import { fileURLToPath } from "node:url" ;
import { describe, expect, it } from "vitest" ;
const SRC_ROOT = resolve(dirname(fileURLToPath(import .meta.url)), "../.." );
const REPO_ROOT = resolve(SRC_ROOT, ".." );
const sourceCache = new Map<string, string>();
const tsFilesCache = new Map<string, string[]>();
const BUNDLED_TYPED_HOOK_REGISTRATION_FILES = [
"extensions/acpx/index.ts" ,
"extensions/active-memory/index.ts" ,
"extensions/codex/index.ts" ,
"extensions/diffs/src/plugin.ts" ,
"extensions/discord/subagent-hooks-api.ts" ,
"extensions/feishu/subagent-hooks-api.ts" ,
"extensions/matrix/subagent-hooks-api.ts" ,
"extensions/memory-core/src/dreaming.ts" ,
"extensions/memory-lancedb/index.ts" ,
"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/codex/index.ts" : ["inbound_claim" ],
"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[]
>;
const BUNDLED_LIVE_CONFIG_HOOK_GUARDS = {
"extensions/active-memory/index.ts" : ["resolveLivePluginConfigObject(" , '"active-memory"' ],
"extensions/codex/index.ts" : ["resolveLivePluginConfigObject(" , '"codex"' ],
"extensions/diffs/src/plugin.ts" : [
"resolveLivePluginConfigObject(" ,
'"diffs"' ,
"api.runtime.config?.loadConfig?.() ?? api.config" ,
],
"extensions/memory-core/src/dreaming.ts" : [
'params.reason === "runtime"' ,
"resolveMemoryCorePluginConfig(startupCfg)" ,
"api.runtime.config?.loadConfig?.() ?? api.config" ,
],
"extensions/memory-lancedb/index.ts" : ["resolveLivePluginConfigObject(" , '"memory-lancedb"' ],
"extensions/skill-workshop/index.ts" : ["resolveLivePluginConfigObject(" , '"skill-workshop"' ],
"extensions/thread-ownership/index.ts" : [
"resolveLivePluginConfigObject(" ,
'"thread-ownership"' ,
"api.runtime.config?.loadConfig?.() ?? api.config" ,
],
} as const satisfies Record<string, readonly string[]>;
const BUNDLED_LIVE_CONFIG_PROVIDER_GUARDS = {
"extensions/amazon-bedrock/register.sync.runtime.ts" : [
"resolvePluginConfigObject(" ,
"const startupPluginConfig = (api.pluginConfig ?? {})" ,
"const currentPluginConfig = resolveCurrentPluginConfig(ctx.config);" ,
"const currentGuardrail = resolveCurrentPluginConfig(config)?.guardrail;" ,
],
"extensions/codex/provider.ts" : [
"resolvePluginConfigObject(" ,
"const runtimePluginConfig = resolvePluginConfigObject(ctx.config, CODEX_PROVIDER_ID);" ,
"const pluginConfig = runtimePluginConfig ?? (ctx.config ? undefined : options.pluginConfig);" ,
],
"extensions/github-copilot/index.ts" : [
"resolvePluginConfigObject(" ,
'const runtimePluginConfig = resolvePluginConfigObject(config, "github-copilot");' ,
"return config ? {} : startupPluginConfig;" ,
],
"extensions/ollama/index.ts" : [
"resolvePluginConfigObject(" ,
'const runtimePluginConfig = resolvePluginConfigObject(config, "ollama");' ,
"return config ? {} : startupPluginConfig;" ,
],
"extensions/openai/index.ts" : [
"resolvePluginConfigObject(" ,
'const runtimePluginConfig = resolvePluginConfigObject(ctx.config, "openai");' ,
"runtimePluginConfig ??" ,
"ctx.config ? undefined : (api.pluginConfig as Record<string, unknown>)" ,
],
} as const satisfies Record<string, readonly string[]>;
const BUNDLED_STARTUP_GATED_HOOK_FORBIDDEN_SNIPPETS = {
"extensions/memory-lancedb/index.ts" : ["if (cfg.autoRecall)" , "if (cfg.autoCapture)" ],
"extensions/skill-workshop/index.ts" : [
"if (!startupConfig.enabled)" ,
'if (startupConfig.autoCapture && startupConfig.reviewMode !== "off")' ,
],
} as const satisfies Record<string, readonly string[]>;
type FileFilter = {
excludeTests?: boolean ;
testOnly?: boolean ;
};
function listTsFiles(rootRelativePath: string, filter: FileFilter = {}): string[] {
const cacheKey = `${rootRelativePath}:${filter.excludeTests ? "exclude-tests" : "" }:${filter.testOnly ? "test-only" : "" }`;
const cached = tsFilesCache.get(cacheKey);
if (cached) {
return cached;
}
const root = resolve(REPO_ROOT, rootRelativePath);
const files: string[] = [];
function walk(directory: string) {
for (const entry of readdirSync(directory, { withFileTypes: true })) {
const fullPath = resolve(directory, entry.name);
if (entry.isDirectory()) {
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git" ) {
continue ;
}
walk(fullPath);
continue ;
}
if (!entry.isFile() || !entry.name.endsWith(".ts" )) {
continue ;
}
const repoRelativePath = relative(REPO_ROOT, fullPath).split(sep).join("/" );
if (filter.excludeTests && repoRelativePath.endsWith(".test.ts" )) {
continue ;
}
if (filter.testOnly && !repoRelativePath.endsWith(".test.ts" )) {
continue ;
}
files.push(repoRelativePath);
}
}
walk(root);
const sorted = files.toSorted();
tsFilesCache.set(cacheKey, sorted);
return sorted;
}
function readRepoSource(file: string): string {
const cached = sourceCache.get(file);
if (cached !== undefined) {
return cached;
}
const source = readFileSync(resolve(REPO_ROOT, file), "utf8" );
sourceCache.set(file, source);
return source;
}
function isAllowedBundledExtensionImport(specifier: string): boolean {
return /(?:^|\/)extensions\/[^/]+\/(?:api|runtime-api)\.js$/u.test(specifier);
}
function collectBundledExtensionImports(source: string): string[] {
const matches = [
...source.matchAll(/from\s+["']([^" ']*extensions\/[^"' ]+)["']/gu),
...source.matchAll(/vi\.(?:mock|doMock)\(\s*["']([^" ']*extensions\/[^"' ]+)["']/gu),
...source.matchAll(/importActual(?:<[^>]*>)?\(\s*["']([^" ']*extensions\/[^"' ]+)["']/gu),
];
return matches
.map((match) => match[1 ])
.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" );
const offenders = files.filter((file) => {
if (
file === "src/plugins/contracts/boundary-invariants.test.ts" ||
file.endsWith(".contract.test.ts" ) ||
file.endsWith("-capability-metadata.test.ts" )
) {
return false ;
}
return readRepoSource(file).includes("contracts/inventory/bundled-capability-metadata" );
});
expect(offenders).toEqual([]);
});
it("keeps the bundled contract inventory out of non-test runtime code" , () => {
const files = listTsFiles("src" , { excludeTests: true });
const offenders = files.filter((file) => {
return readRepoSource(file).includes("contracts/inventory/bundled-capability-metadata" );
});
expect(offenders).toEqual([]);
});
it("keeps core tests off bundled extension deep imports" , () => {
const files = listTsFiles("src" , { testOnly: true });
const offenders = files.filter((file) => {
return collectBundledExtensionImports(readRepoSource(file)).some(
(specifier) => !isAllowedBundledExtensionImport(specifier),
);
});
expect(offenders).toEqual([]);
});
it("keeps plugin contract tests off bundled path helpers unless the test is explicitly about paths" , () => {
const files = listTsFiles("src/plugins/contracts" , { testOnly: true });
const offenders = files.filter((file) => {
if (file === "src/plugins/contracts/boundary-invariants.test.ts" ) {
return false ;
}
return readRepoSource(file).includes("test/helpers/bundled-plugin-paths" );
});
expect(offenders).toEqual([]);
});
it("keeps channel production code off bundled-plugin-metadata helpers" , () => {
const files = listTsFiles("src/channels" , { excludeTests: true });
const offenders = files.filter((file) => {
return readRepoSource(file).includes("plugins/bundled-plugin-metadata" );
});
expect(offenders).toEqual([]);
});
it("keeps contract loaders off hand-built bundled extension paths" , () => {
const files = [
...listTsFiles("src/plugins" , { excludeTests: true }),
...listTsFiles("src/channels" , { excludeTests: true }),
].toSorted();
const offenders = files.filter((file) => {
const source = readRepoSource(file);
return /extensions\/\$\{|\.\.\/\.\.\/\.\.\/\.\.\/extensions\//u.test(source);
});
expect(offenders).toEqual([]);
});
it("keeps bundled plugin production code off legacy before_agent_start hooks" , () => {
const files = listTsFiles("extensions" , { excludeTests: true });
const offenders = files.filter((file) => readRepoSource(file).includes("before_agent_start" ));
expect(offenders).toEqual([]);
});
it("keeps bundled plugin typed hook registrations on an explicit allowlist" , () => {
const files = listTsFiles("extensions" , { excludeTests: true });
const hookRegistrationFiles = files.filter((file) => /\bapi\.on\(/u.test(readRepoSource(file)));
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)));
expect(offenders).toEqual([]);
});
it("keeps long-lived bundled hook handlers on live runtime config lookups" , () => {
const missingGuards = Object.entries(BUNDLED_LIVE_CONFIG_HOOK_GUARDS).flatMap(
([file, requiredSnippets]) => {
const source = readRepoSource(file);
return requiredSnippets
.filter((snippet) => !source.includes(snippet))
.map((snippet) => `${file}: ${snippet}`);
},
);
expect(missingGuards).toEqual([]);
});
it("keeps live provider config surfaces on runtime config lookups" , () => {
const missingGuards = Object.entries(BUNDLED_LIVE_CONFIG_PROVIDER_GUARDS).flatMap(
([file, requiredSnippets]) => {
const source = readRepoSource(file);
return requiredSnippets
.filter((snippet) => !source.includes(snippet))
.map((snippet) => `${file}: ${snippet}`);
},
);
expect(missingGuards).toEqual([]);
});
it("keeps long-lived bundled hook handlers off startup-only registration gates" , () => {
const offenders = Object.entries(BUNDLED_STARTUP_GATED_HOOK_FORBIDDEN_SNIPPETS).flatMap(
([file, forbiddenSnippets]) => {
const source = readRepoSource(file);
return forbiddenSnippets
.filter((snippet) => source.includes(snippet))
.map((snippet) => `${file}: ${snippet}`);
},
);
expect(offenders).toEqual([]);
});
});
Messung V0.5 in Prozent C=100 H=99 G=99
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet am 2026-06-10)
¤
*© Formatika GbR, Deutschland