mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-10 03:31:40 -10:00
test(daemon): failing tests for openrig-core plugin tree shape (slice 3.2 Phase 1)
Per IMPL-PRD §2 + DESIGN.md §5.5 + plugin-primitive Phase 3a slice 3.2. 25 tests, all currently FAIL (TDD red — vendored plugin tree at packages/daemon/assets/plugins/openrig-core/ doesn't exist yet; substrate draft at substrate/.../missions/plugin-primitive/openrig-core/ will be copied + finalized in next commit). Tests cover (HG-2.1, HG-2.2, partial HG-2.6/HG-2.7): - Tree shape: plugin root exists; LICENSE (Apache 2.0); README.md; both .claude-plugin/ and .codex-plugin/ manifest dirs (Obra Superpowers shape) - Manifest validation: name/version/description per Claude + Codex specs; skills + hooks pointing at correct paths; both manifests reference same skills/ subdir (cross-runtime portability) - Skills: 11 expected skills present; each has SKILL.md with required frontmatter (name + description ≤1024 chars per agentskills.io spec); no drift from expected list - Hooks: claude.json declares 4 events (incl Notification); codex.json declares 3 events (no Notification — Codex doesn't expose it); activity-relay.cjs script present + references /api/activity/hooks (the endpoint preserved in batch-1 D.2 rip per IMPL-PRD §1.2 endpoint discipline); CLAUDE_PLUGIN_ROOT + CODEX_PLUGIN_ROOT path substitution conventions used per runtime - Cross-batch integration: dual-manifest detection by batch-1's pluginAppliesToClaude/pluginAppliesToCodex filters under auto-detection Next commit: copy substrate advisor draft to packages/daemon/assets/plugins/ openrig-core/ — should turn all 25 tests green. Per IMPL-PRD §2 plugin-primitive Phase 3a slice 3.2 + per banked feedback_tdd_scope (TDD-first; restored from batch-1 Phase A self-audit gap).
This commit is contained in:
1 parent
5e5644ddc3
commit
059825d704
1 file changed
+174
@@ -0,0 +1,174 @@
|
||||
// Test suite for plugin-primitive Phase 3a slice 3.2 — openrig-core plugin
|
||||
// authoring + tree shape verification. Per IMPL-PRD §2 (HG-2.1, HG-2.2)
|
||||
// + DESIGN.md §5.5.
|
||||
//
|
||||
// The vendored plugin tree at packages/daemon/assets/plugins/openrig-core/
|
||||
// is the source of truth at v0 (auto-fetch from
|
||||
// github.com/mvschwarz/openrig-plugins is a graceful overlay; vendored
|
||||
// fallback is always available).
|
||||
//
|
||||
// This test suite asserts shape + contract — no copying or mutation. If
|
||||
// these tests pass, the plugin is consumable by both Claude Code and Codex
|
||||
// runtimes via their plugin loaders + by OpenRig's per-runtime
|
||||
// applicability filter (plugin_type=auto detects both manifest dirs).
|
||||
|
||||
import { describe, it, expect } from "vitest";
|
||||
import * as fs from "node:fs";
|
||||
import * as nodePath from "node:path";
|
||||
|
||||
const PLUGIN_ROOT = nodePath.resolve(import.meta.dirname, "../assets/plugins/openrig-core");
|
||||
|
||||
// Skills shipping in openrig-core v0 (per IMPL-PRD §2.6 + advisor draft):
|
||||
// 11 skills total. These are vendored canonical OpenRig skills that any
|
||||
// OpenRig agent can load via the plugin pathway.
|
||||
const EXPECTED_SKILLS = [
|
||||
"agent-startup-and-context-ingestion",
|
||||
"claude-compact-in-place",
|
||||
"claude-compaction-restore",
|
||||
"forming-an-openrig-mental-model",
|
||||
"openrig-architect",
|
||||
"openrig-operator",
|
||||
"openrig-user",
|
||||
"permission-posture",
|
||||
"queue-handoff",
|
||||
"seat-continuity-and-handover",
|
||||
"session-compaction-and-restore",
|
||||
];
|
||||
|
||||
describe("openrig-core plugin — vendored tree shape (HG-2.1)", () => {
|
||||
it("exists at packages/daemon/assets/plugins/openrig-core/", () => {
|
||||
expect(fs.existsSync(PLUGIN_ROOT)).toBe(true);
|
||||
expect(fs.statSync(PLUGIN_ROOT).isDirectory()).toBe(true);
|
||||
});
|
||||
|
||||
it("ships dual manifest (.claude-plugin/plugin.json + .codex-plugin/plugin.json) — Obra Superpowers shape", () => {
|
||||
expect(fs.existsSync(nodePath.join(PLUGIN_ROOT, ".claude-plugin", "plugin.json"))).toBe(true);
|
||||
expect(fs.existsSync(nodePath.join(PLUGIN_ROOT, ".codex-plugin", "plugin.json"))).toBe(true);
|
||||
});
|
||||
|
||||
it("LICENSE file present (Apache 2.0 per founder direction; public marketplace target)", () => {
|
||||
expect(fs.existsSync(nodePath.join(PLUGIN_ROOT, "LICENSE"))).toBe(true);
|
||||
const content = fs.readFileSync(nodePath.join(PLUGIN_ROOT, "LICENSE"), "utf-8");
|
||||
expect(content).toMatch(/Apache License/i);
|
||||
});
|
||||
|
||||
it("README.md present", () => {
|
||||
expect(fs.existsSync(nodePath.join(PLUGIN_ROOT, "README.md"))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("openrig-core plugin — manifest shape (HG-2.2)", () => {
|
||||
it(".claude-plugin/plugin.json validates as Claude plugin manifest", () => {
|
||||
const content = fs.readFileSync(nodePath.join(PLUGIN_ROOT, ".claude-plugin", "plugin.json"), "utf-8");
|
||||
const manifest = JSON.parse(content) as Record<string, unknown>;
|
||||
// Required fields per Claude plugin spec
|
||||
expect(manifest["name"]).toBe("openrig-core");
|
||||
expect(manifest["version"]).toBe("0.1.0");
|
||||
expect(typeof manifest["description"]).toBe("string");
|
||||
expect((manifest["description"] as string).length).toBeLessThanOrEqual(1024);
|
||||
// Hook + skills wiring
|
||||
expect(manifest["skills"]).toBe("./skills");
|
||||
expect(manifest["hooks"]).toBe("./hooks/claude.json");
|
||||
expect(manifest["repository"]).toMatch(/github:mvschwarz\/openrig-plugins/);
|
||||
expect(manifest["license"]).toBeDefined();
|
||||
});
|
||||
|
||||
it(".codex-plugin/plugin.json validates as Codex plugin manifest (required: name, version, description)", () => {
|
||||
const content = fs.readFileSync(nodePath.join(PLUGIN_ROOT, ".codex-plugin", "plugin.json"), "utf-8");
|
||||
const manifest = JSON.parse(content) as Record<string, unknown>;
|
||||
// Codex requires name + version + description (per IMPL-PRD §2.3)
|
||||
expect(manifest["name"]).toBe("openrig-core");
|
||||
expect(manifest["version"]).toBe("0.1.0");
|
||||
expect(typeof manifest["description"]).toBe("string");
|
||||
expect(manifest["hooks"]).toBe("./hooks/codex.json");
|
||||
expect(manifest["skills"]).toBe("./skills");
|
||||
});
|
||||
|
||||
it("both manifests reference the same skills/ subdir (cross-runtime portability)", () => {
|
||||
const claude = JSON.parse(fs.readFileSync(nodePath.join(PLUGIN_ROOT, ".claude-plugin", "plugin.json"), "utf-8")) as Record<string, unknown>;
|
||||
const codex = JSON.parse(fs.readFileSync(nodePath.join(PLUGIN_ROOT, ".codex-plugin", "plugin.json"), "utf-8")) as Record<string, unknown>;
|
||||
expect(claude["skills"]).toEqual(codex["skills"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("openrig-core plugin — skills (HG-2.1 skill content per agentskills.io spec)", () => {
|
||||
it.each(EXPECTED_SKILLS)("skill '%s' has SKILL.md with required frontmatter (name + description)", (skillId) => {
|
||||
const skillPath = nodePath.join(PLUGIN_ROOT, "skills", skillId, "SKILL.md");
|
||||
expect(fs.existsSync(skillPath)).toBe(true);
|
||||
const content = fs.readFileSync(skillPath, "utf-8");
|
||||
// Frontmatter: --- ... ---
|
||||
expect(content).toMatch(/^---\n/);
|
||||
// Required: name field
|
||||
expect(content).toMatch(/^name: \S+/m);
|
||||
// Required: description field (≤1024 chars per agentskills.io spec)
|
||||
const descMatch = content.match(/^description:\s*(?:>?-?\s*)?\n?([\s\S]*?)(?=\n\w+:|\n---)/m);
|
||||
expect(descMatch).toBeTruthy();
|
||||
const desc = descMatch?.[1]?.trim() ?? "";
|
||||
expect(desc.length).toBeGreaterThan(0);
|
||||
expect(desc.length).toBeLessThanOrEqual(1024);
|
||||
});
|
||||
|
||||
it("ships exactly the 11 expected skills (no drift; no missing skills)", () => {
|
||||
const skillsDir = nodePath.join(PLUGIN_ROOT, "skills");
|
||||
const actual = fs.readdirSync(skillsDir).filter((f) =>
|
||||
fs.statSync(nodePath.join(skillsDir, f)).isDirectory(),
|
||||
);
|
||||
expect(actual.sort()).toEqual([...EXPECTED_SKILLS].sort());
|
||||
});
|
||||
});
|
||||
|
||||
describe("openrig-core plugin — hooks (HG-2.6 + HG-2.7)", () => {
|
||||
it("hooks/claude.json declares 4 events (SessionStart, UserPromptSubmit, Stop, Notification)", () => {
|
||||
const content = fs.readFileSync(nodePath.join(PLUGIN_ROOT, "hooks", "claude.json"), "utf-8");
|
||||
const config = JSON.parse(content) as { hooks: Record<string, unknown> };
|
||||
expect(config.hooks).toBeDefined();
|
||||
expect(Object.keys(config.hooks).sort()).toEqual([
|
||||
"Notification", "SessionStart", "Stop", "UserPromptSubmit",
|
||||
]);
|
||||
});
|
||||
|
||||
it("hooks/codex.json declares 3 events (no Notification — Codex doesn't expose it)", () => {
|
||||
const content = fs.readFileSync(nodePath.join(PLUGIN_ROOT, "hooks", "codex.json"), "utf-8");
|
||||
const config = JSON.parse(content) as { hooks: Record<string, unknown> };
|
||||
expect(config.hooks).toBeDefined();
|
||||
expect(Object.keys(config.hooks).sort()).toEqual([
|
||||
"SessionStart", "Stop", "UserPromptSubmit",
|
||||
]);
|
||||
});
|
||||
|
||||
it("hooks/scripts/activity-relay.cjs exists (the canonical relay script that POSTs to /api/activity/hooks)", () => {
|
||||
const relayPath = nodePath.join(PLUGIN_ROOT, "hooks", "scripts", "activity-relay.cjs");
|
||||
expect(fs.existsSync(relayPath)).toBe(true);
|
||||
const content = fs.readFileSync(relayPath, "utf-8");
|
||||
// The script is what plugin-shipped hooks invoke; it should POST to the
|
||||
// activity-hooks endpoint that the daemon (post-rip) preserved per
|
||||
// IMPL-PRD §1.2 endpoint discipline.
|
||||
expect(content).toMatch(/activity\/hooks|activity-hooks/i);
|
||||
});
|
||||
|
||||
it("Claude hook commands reference ${CLAUDE_PLUGIN_ROOT} (Claude path substitution convention)", () => {
|
||||
const content = fs.readFileSync(nodePath.join(PLUGIN_ROOT, "hooks", "claude.json"), "utf-8");
|
||||
expect(content).toMatch(/\$\{CLAUDE_PLUGIN_ROOT\}/);
|
||||
// Should point at the relay script via substitution
|
||||
expect(content).toMatch(/CLAUDE_PLUGIN_ROOT.*activity-relay\.cjs/);
|
||||
});
|
||||
|
||||
it("Codex hook commands reference ${CODEX_PLUGIN_ROOT} (Codex path substitution convention)", () => {
|
||||
const content = fs.readFileSync(nodePath.join(PLUGIN_ROOT, "hooks", "codex.json"), "utf-8");
|
||||
expect(content).toMatch(/\$\{CODEX_PLUGIN_ROOT\}/);
|
||||
expect(content).toMatch(/CODEX_PLUGIN_ROOT.*activity-relay\.cjs/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("openrig-core plugin — projection-applicability (works with batch-1 pluginAppliesToX filters)", () => {
|
||||
it("dual-manifest plugin classifies as applicable to BOTH adapters under auto-detection", () => {
|
||||
// Per batch-1 pluginAppliesToClaude/pluginAppliesToCodex helpers:
|
||||
// auto + .claude-plugin/plugin.json present → applies to Claude
|
||||
// auto + .codex-plugin/plugin.json present → applies to Codex
|
||||
// openrig-core has BOTH manifest dirs, so auto-detect projects to both.
|
||||
const claudeManifest = nodePath.join(PLUGIN_ROOT, ".claude-plugin", "plugin.json");
|
||||
const codexManifest = nodePath.join(PLUGIN_ROOT, ".codex-plugin", "plugin.json");
|
||||
expect(fs.existsSync(claudeManifest)).toBe(true);
|
||||
expect(fs.existsSync(codexManifest)).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user