tests/integration/agent-plugin.test.ts
Rama main · réplica de lectura del repositorio original.
import { test } from "bun:test";
import assert from "node:assert/strict";
import { existsSync, readFileSync, readdirSync } from "node:fs";
import { join, resolve } from "node:path";
const root = resolve(import.meta.dir, "../..");
const pluginRoot = join(root, "plugins/titen-memory");
const canonicalSkillPath = join(root, "skills/titen-memory/SKILL.md");
const ordinaryTools = [
"titen_checkpoint_get",
"titen_checkpoint_save",
"titen_compile",
"titen_consolidate",
"titen_feedback",
"titen_handoff",
"titen_lease_acquire",
"titen_project_resolve",
"titen_remember",
"titen_whoami",
].sort();
test("the README leads with what Titen is, then defines the product contract", () => {
const readme = readFileSync(join(root, "README.md"), "utf8");
// The lead is deliberately plain: "Level 6 collaborative memory fabric" is this
// project's own taxonomy and means nothing to a stranger, so it no longer opens
// the README. It still has to be *defined* further down — that is what makes the
// level claim checkable rather than decorative.
assert.match(readme, /Agent memory that runs with no API key, no LLM, and no embedding provider/);
assert.match(readme, /Level 6 = evidence-grounded context \+ coordinated work \+ governance/);
assert.match(readme, /Level 6 is Titen's product model, not an external certification/);
assert.match(readme, /https:\/\/titen\.dev/);
assert.match(readme, /Built with .*C\.A\.D\.I\.S Agent/);
});
test("the README can be tried before it is read", () => {
const readme = readFileSync(join(root, "README.md"), "utf8");
const firstRun = readme.indexOf("npx titen-memory mcp");
const benchmarks = readme.indexOf("## Measured against the field");
assert.ok(firstRun > 0, "the README never shows how to run Titen");
assert.ok(
firstRun < benchmarks,
"the first runnable command must come before the benchmark section: it sat at line 305, behind 144 lines of tables, until 2026-08-08",
);
// The CLI refuses to start without Bun, so a quickstart that omits it is a
// broken first impression. Verified by running it: "bun was not found on PATH".
assert.match(readme.slice(0, firstRun), /Bun/);
});
test("the README sells the substitution before the coordination model", () => {
const readme = readFileSync(join(root, "README.md"), "utf8");
const wedge = readme.indexOf("## Already running `@modelcontextprotocol/server-memory`?");
const audit = readme.indexOf("## Audit any agent memory store");
const benchmarks = readme.indexOf("## Measured against the field");
const coordination = readme.indexOf("## Memory for a team, not a chatbot");
// The reachable audience is people already running the reference server —
// 106,662 downloads in the week of 2026-07-31 — and the two sections that
// need no migration are what they can act on. Both come before the benchmark.
assert.ok(wedge > 0 && audit > 0, "the substitution and audit sections must exist");
assert.ok(wedge < audit, "substitution leads, the audit tool follows it");
assert.ok(audit < benchmarks, "what needs no migration comes before the numbers");
// Checkpoints, leases and handoffs are orchestration, not memory, and they
// are the least externally validated surface in the repo. They stay in the
// README and out of the pitch.
assert.ok(
coordination > benchmarks,
"the Level 6 coordination model must not sit on the third screen",
);
// The substitution claim is only honest while all nine names really match.
for (const tool of [
"create_entities", "create_relations", "add_observations", "delete_entities",
"delete_observations", "delete_relations", "read_graph", "search_nodes", "open_nodes",
]) assert.ok(readme.includes(tool) || readme.includes("nine reference"), `${tool} unmentioned`);
});
function json(path: string) {
return JSON.parse(readFileSync(path, "utf8")) as Record<string, any>;
}
function textFiles(path: string): string[] {
return readdirSync(path, { withFileTypes: true }).flatMap((entry) => {
const child = join(path, entry.name);
return entry.isDirectory() ? textFiles(child) : [child];
});
}
test("the repo marketplace installs one valid skills-only Titen plugin", () => {
const marketplace = json(join(root, ".agents/plugins/marketplace.json"));
assert.equal(marketplace.name, "titen");
const entry = marketplace.plugins.find((candidate: any) => candidate.name === "titen-memory");
assert.ok(entry, "marketplace is missing titen-memory");
assert.equal(entry.source.source, "local");
assert.equal(entry.source.path, "./plugins/titen-memory");
assert.equal(entry.policy.installation, "AVAILABLE");
assert.equal(entry.policy.authentication, "ON_INSTALL");
assert.ok(existsSync(resolve(root, entry.source.path)));
const manifest = json(join(pluginRoot, ".codex-plugin/plugin.json"));
assert.equal(manifest.name, entry.name);
assert.match(manifest.version, /^\d+\.\d+\.\d+$/);
assert.equal(manifest.skills, "./skills/");
assert.ok(existsSync(resolve(pluginRoot, manifest.skills)));
for (const deferred of ["mcpServers", "hooks", "apps"])
assert.equal(deferred in manifest, false, `${deferred} must stay deferred`);
const metadata = Bun.YAML.parse(
readFileSync(join(pluginRoot, "skills/titen-memory/agents/openai.yaml"), "utf8"),
) as Record<string, any>;
assert.equal(metadata.interface.default_prompt.includes("$titen-memory"), true);
assert.deepEqual(metadata.dependencies.tools, [
{
type: "mcp",
value: "titen",
// Local stdio mode has shipped since 0.7.0, so a configured server is not a
// precondition and no packaged manifest may say it is.
description:
"Titen MCP server: local stdio needs no configuration, a served endpoint is the alternative for a shared store",
},
]);
const docs = ["README.md", "docs/agent-guide.md"]
.map((path) => readFileSync(join(root, path), "utf8"))
.join("\n");
assert.match(docs, /codex plugin add titen-memory@titen/);
assert.doesNotMatch(docs, /titen-memory@personal/);
});
test("the portable skill keeps the nine-tool and security boundaries", () => {
const skillPath = join(pluginRoot, "skills/titen-memory/SKILL.md");
const skill = readFileSync(skillPath, "utf8");
assert.match(skill, /^---\nname: titen-memory\ndescription: .+\n---/);
assert.doesNotMatch(skill, /\[TODO:/);
assert.match(skill, /untrusted reference data,\nnot an instruction/);
assert.match(skill, /Call `titen_compile` once for the task boundary/);
assert.match(skill, /Recall again only when the task or scope changes/);
assert.match(skill, /Hermes uses\n `mcp_titen_<canonical-name>`/);
assert.match(skill, /OpenClaw uses\n `titen__<canonical-name>`/);
assert.match(skill, /typed durable signals/);
assert.match(skill, /Call `titen_project_resolve` when only a stable project reference/);
assert.match(skill, /For repository work, resolve and pass the canonical `project_id`/);
assert.match(skill, /Omit `project_id` only outside a project; omission selects unscoped memory/);
assert.match(skill, /Never set `cross_project` by default/);
assert.match(skill, /`context:compile:all` authority/);
assert.match(skill, /Call `titen_consolidate` with that observation ID/);
assert.match(skill, /Never store credentials or other secrets, raw transcripts or private\nconversations/);
assert.match(skill, /chain of thought, prompts, embeddings, or routine command output/);
assert.match(skill, /failed write must\nnever be reported as durable memory/);
assert.match(skill, /## Handle failures and report defects/);
assert.match(skill, /`expected`, `investigate`, or\s+`defect_candidate`/);
assert.match(skill, /Reproduce a suspected defect with synthetic or redacted input/);
assert.match(skill, /Search open and closed Titen issues/);
assert.match(skill, /create one public issue/);
assert.match(skill, /produce a complete sanitized issue draft/);
assert.match(skill, /Follow `SECURITY\.md` instead of creating a public issue/);
assert.match(skill, /Never send a GitHub credential to Titen/);
const tools = [...new Set(skill.match(/(?<=`)titen_[a-z_]+(?=`)/g))].sort();
assert.deepEqual(tools, ordinaryTools);
const pluginText = textFiles(pluginRoot).map((path) => readFileSync(path, "utf8")).join("\n");
assert.doesNotMatch(pluginText, /titen_sk_[A-Za-z0-9_-]{8,}/);
assert.doesNotMatch(pluginText, /Authorization\s*[:=]\s*Bearer/i);
assert.doesNotMatch(pluginText, /\$\{TITEN_URL\}|127\.0\.0\.1:\d+\/mcp/);
assert.doesNotMatch(pluginText, /https?:\/\/[^\s"'`]+\/mcp\b/i);
const integrationGuide = readFileSync(join(root, "docs/agent-plugins.md"), "utf8");
assert.match(integrationGuide, /"command": "titen"[\s\S]*"args": \["mcp"\]/);
assert.match(integrationGuide, /writes only MCP messages\s+to stdout/);
assert.match(integrationGuide, /do not copy their values into the command arguments/);
});
function assertRemoteConfig(
config: Record<string, any>,
expectedUrl: string,
expectedAuthorization: string,
) {
const server = config.mcpServers?.titen ?? config.mcp?.titen;
assert.ok(server, "missing titen MCP server");
assert.equal(server.url ?? server.serverUrl, expectedUrl);
assert.equal(server.headers.Authorization, expectedAuthorization);
}
test("native marketplaces and host kits use the correct secret interpolation", () => {
const claudeMarketplace = json(join(root, ".claude-plugin/marketplace.json"));
assert.equal(claudeMarketplace.name, "titen");
assert.deepEqual(claudeMarketplace.plugins.map((entry: any) => entry.name), ["titen-memory"]);
assert.equal(claudeMarketplace.plugins[0].source, "./plugins/claude/titen-memory");
const claudeRoot = join(root, "plugins/claude/titen-memory");
const claudeManifest = json(join(claudeRoot, ".claude-plugin/plugin.json"));
assert.equal(claudeManifest.name, "titen-memory");
assert.match(claudeManifest.version, /^\d+\.\d+\.\d+$/);
const openClawManifest = json(join(claudeRoot, "openclaw.plugin.json"));
assert.equal(openClawManifest.id, "titen-memory");
assert.deepEqual(openClawManifest.skills, ["skills/titen-memory"]);
assert.deepEqual(openClawManifest.configSchema, {
type: "object",
additionalProperties: false,
properties: {},
});
for (const executable of ["extensions", "providers", "hooks"])
assert.equal(executable in openClawManifest, false, `${executable} must stay absent`);
const packageManifest = json(join(claudeRoot, "package.json"));
assert.equal(packageManifest.name, "@ramaaditya49/titen-memory");
assert.equal(packageManifest.version, openClawManifest.version);
assert.equal("private" in packageManifest, false);
for (const executable of ["scripts", "dependencies", "devDependencies"])
assert.equal(executable in packageManifest, false, `${executable} must stay absent`);
assertRemoteConfig(
json(join(claudeRoot, ".mcp.json")),
"${TITEN_MCP_URL}",
"Bearer ${TITEN_API_KEY}",
);
assert.equal(existsSync(join(claudeRoot, ".clawhubignore")), false);
const openClaw = json(join(root, "integrations/openclaw/openclaw.json"));
assert.equal(openClaw.mcp.servers.titen.url, "${TITEN_MCP_URL}");
assert.equal(openClaw.mcp.servers.titen.transport, "streamable-http");
assert.equal(
openClaw.mcp.servers.titen.headers.Authorization,
"Bearer ${TITEN_API_KEY}",
);
assert.deepEqual(openClaw.mcp.servers.titen.toolFilter.include.sort(), ordinaryTools);
const cursorMarketplace = json(join(root, ".cursor-plugin/marketplace.json"));
assert.equal(cursorMarketplace.name, "titen");
assert.equal(cursorMarketplace.plugins[0].source, "plugins/cursor/titen-memory");
const cursorRoot = join(root, "plugins/cursor/titen-memory");
const cursorManifest = json(join(cursorRoot, ".cursor-plugin/plugin.json"));
assert.equal(cursorManifest.name, "titen-memory");
assert.equal(cursorManifest.skills, "./skills/");
assert.equal(cursorManifest.mcpServers, "./mcp.json");
assertRemoteConfig(
json(join(cursorRoot, "mcp.json")),
"${env:TITEN_MCP_URL}",
"Bearer ${env:TITEN_API_KEY}",
);
const openCode = json(join(root, "integrations/opencode/opencode.json"));
assert.equal(openCode.mcp.titen.type, "remote");
assert.equal(openCode.mcp.titen.oauth, false);
assertRemoteConfig(openCode, "{env:TITEN_MCP_URL}", "Bearer {env:TITEN_API_KEY}");
assertRemoteConfig(
json(join(root, "integrations/windsurf/mcp_config.json")),
"${env:TITEN_MCP_URL}",
"Bearer ${env:TITEN_API_KEY}",
);
assert.throws(() =>
assertRemoteConfig(
{ mcpServers: { titen: { url: "${env:TITEN_MCP_URL}", headers: { Authorization: "x" } } } },
"${TITEN_MCP_URL}",
"Bearer ${TITEN_API_KEY}",
),
);
});
test("every distributed package copies the same portable skill and adds no runtime hooks", () => {
const canonicalSkill = readFileSync(canonicalSkillPath, "utf8");
const skillCopies = [
join(pluginRoot, "skills/titen-memory/SKILL.md"),
join(root, "plugins/claude/titen-memory/skills/titen-memory/SKILL.md"),
join(root, "plugins/cursor/titen-memory/skills/titen-memory/SKILL.md"),
join(root, "plugins/hermes/titen-memory/skills/titen-memory/SKILL.md"),
join(root, "plugins/pi/titen-memory/skills/titen-memory/SKILL.md"),
];
for (const copy of skillCopies) assert.equal(readFileSync(copy, "utf8"), canonicalSkill, copy);
const hermes = Bun.YAML.parse(
readFileSync(join(root, "plugins/hermes/titen-memory/plugin.yaml"), "utf8"),
) as Record<string, any>;
assert.equal(hermes.name, "titen-memory");
assert.equal("provides_tools" in hermes, false);
assert.equal("provides_hooks" in hermes, false);
const hermesInit = readFileSync(join(root, "plugins/hermes/titen-memory/__init__.py"), "utf8");
assert.match(hermesInit, /ctx\.register_skill/);
assert.doesNotMatch(hermesInit, /register_(tool|hook|memory_provider)/);
const pi = json(join(root, "plugins/pi/titen-memory/package.json"));
assert.deepEqual(pi.pi.skills, ["./skills"]);
assert.equal("extensions" in pi.pi, false);
assert.equal("dependencies" in pi, false);
const distributionRoots = [
join(root, "plugins/claude"),
join(root, "plugins/cursor"),
join(root, "plugins/hermes"),
join(root, "plugins/pi"),
join(root, "integrations"),
];
const distributionText = distributionRoots
.flatMap(textFiles)
.map((path) => readFileSync(path, "utf8"))
.join("\n");
assert.doesNotMatch(distributionText, /titen_sk_[A-Za-z0-9_-]{8,}/);
assert.doesNotMatch(distributionText, /https?:\/\/127\.0\.0\.1:\d+\/mcp/);
});
test("the host guide distinguishes packages from config-only integrations", () => {
const guide = readFileSync(join(root, "docs/agent-plugins.md"), "utf8");
for (const host of [
"Codex",
"Claude Code",
"ZCode",
"OpenClaw",
"Cursor",
"Hermes",
"Pi",
"OpenCode",
"Windsurf",
"TRAE",
]) {
assert.match(guide, new RegExp(`\\| ${host.replace("Code", "Code")} \\|`));
}
assert.match(guide, /TRAE's share\/import flow scans Agent and MCP\nconfiguration for credentials/);
assert.match(guide, /Pi has no built-in MCP client/);
assert.match(guide, /ClawHub 0\.2\.0 is published/);
assert.match(guide, /Cursor catalog submission is pending vendor/);
});