← Files OpsTruthARCHIVED FILE

scripts/validate-plugin.mjs

3.47 KB · Oct 2, 2026 · 00:31 UTC

↓ Download file

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