← Files OpsTruthARCHIVED FILE
scripts/validate-plugin.mjs
3.47 KB · Oct 3, 2026 · 06:32 UTC
import { readFile, readdir, stat } from "node:fs/promises";
import { join } from "node:path";
import { TOOL_DEFINITIONS } from "../src/tools.js";
const root = new URL("..", import.meta.url);
const manifest = JSON.parse(await readFile(new URL("../.codex-plugin/plugin.json", import.meta.url), "utf8"));
const provenance = JSON.parse(await readFile(new URL("../provenance/sources.json", import.meta.url), "utf8"));
const errors = [];
if (manifest.name !== "opstruth") errors.push("plugin name must remain opstruth");
if (manifest.version !== "0.4.1") errors.push("plugin version mismatch");
if (manifest.author?.name !== "AYOBAMI JOHN HAASTRUP") errors.push("verified publisher name mismatch");
if (manifest.skills !== "./skills/") errors.push("skills path must be relative");
if (manifest.interface?.logo !== "./assets/logo-mark.png") errors.push("interface logo must reference the square product mark");
if (manifest.interface?.composerIcon !== "./assets/logo-mark.png") errors.push("composer icon must reference the square product mark");
if (!Array.isArray(provenance.sources) || provenance.sources.length < 6) errors.push("provenance must cover all source systems");
const logo = await stat(new URL(`../${manifest.interface?.logo || "missing"}`, import.meta.url)).catch(() => null);
if (!logo?.isFile() || logo.size === 0) errors.push("square product mark is missing or empty");
const skillRoot = new URL("../skills", import.meta.url);
const skillNames = await readdir(skillRoot);
if (skillNames.length !== 6) errors.push(`expected 6 skills, found ${skillNames.length}`);
for (const name of skillNames) {
const directory = join(skillRoot.pathname, name);
if (!(await stat(directory)).isDirectory()) continue;
const skill = await readFile(join(directory, "SKILL.md"), "utf8");
const agent = await readFile(join(directory, "agents/openai.yaml"), "utf8");
if (!skill.startsWith("---\nname:")) errors.push(`${name}: invalid SKILL.md frontmatter`);
if (!skill.includes(`name: ${name}\n`)) errors.push(`${name}: folder and frontmatter name differ`);
if (!/description: .{30,}/.test(skill)) errors.push(`${name}: description is missing or too short`);
if (!agent.includes("https://mcp.opstruth.io/mcp")) errors.push(`${name}: MCP dependency missing`);
}
const toolNames = TOOL_DEFINITIONS.map((tool) => tool.name);
if (new Set(toolNames).size !== toolNames.length) errors.push("tool names must be unique");
if (TOOL_DEFINITIONS.length !== 21) errors.push(`expected 21 tools, found ${TOOL_DEFINITIONS.length}`);
const openWorldTools = new Set(["opstruth_probe_deployment", "opstruth_snapshot_evidence", "opstruth_verify_execution_result", "opstruth_attest_donestate_handoff"]);
for (const tool of TOOL_DEFINITIONS) {
if (tool.annotations?.readOnlyHint !== true) errors.push(`${tool.name}: must be read-only`);
if (tool.annotations?.destructiveHint !== false) errors.push(`${tool.name}: destructive annotation mismatch`);
if (openWorldTools.has(tool.name) && tool.annotations?.openWorldHint !== true) errors.push(`${tool.name}: must declare open-world access`);
if (!openWorldTools.has(tool.name) && tool.annotations?.openWorldHint !== false) errors.push(`${tool.name}: open-world annotation mismatch`);
if (!tool.inputSchema || !tool.outputSchema) errors.push(`${tool.name}: schemas required`);
}
if (errors.length) {
console.error(errors.join("\n"));
process.exit(1);
}
console.log(`plugin validation passed: ${skillNames.length} skills, ${TOOL_DEFINITIONS.length} tools, ${provenance.sources.length} provenance sources`);
SHA-256: 5e770fd264466f8f28f05477e7aaed17acfd6c154b9f66ac67e01017059369da