feat(packs): Tier 2 domain packs monorepo with pack-sdk contract

- pack-sdk 1.0.0: definePack(), PackManifestSchema (Zod), ready()
  handshake type, packConformance() (manifest/hostCompat/hooks/
  tool round-trip/readiness/lane-image checks), hostSatisfiesCompat()
- pack-godot 1.0.0: exact @opencode/plugin 2.0.26, ready() host+binary
  handshake, godot tool stubs + guard, companion addon identity,
  Dockerfile.lane (headless Godot 4.x)
- pack-gdevelop 1.0.0: consolidated shim+studio boundary, representative
  patch stack with git-apply verification, donor fork decommissioned
- pack-rea 1.0.0: readiness.ts handshake, agents/skills stubs, exact pins
- 22-test conformance suite (bun test), tsc + oxlint clean
This commit is contained in:
Heretek-OpenCode Coordinator committed 2026-10-12 12:56:24 -04:00
1 parent 4c1686b5f8
commit 6635d9bb64
36 files changed
+2431 -2

No files matched your search

+1273
View File
File diff suppressed because it is too large. Load diff
+1
View File
@@ -10,6 +10,7 @@
"test": "bun test"
},
"devDependencies": {
"@types/bun": "1.3.4",
"@types/node": "^22.0.0",
"oxlint": "^0.15.0",
"typescript": "^5.5.0",
+18
View File
@@ -0,0 +1,18 @@
{
"name": "@heretek-ai/opencode-packs",
"version": "1.0.0",
"private": true,
"description": "Tier 2 domain pack monorepo: pack-sdk + pack-godot + pack-gdevelop + pack-rea",
"workspaces": [
"packages/*",
"packs/*"
],
"scripts": {
"check": "bunx tsc --noEmit -p tsconfig.json",
"test": "bun test packages/pack-sdk/test"
},
"devDependencies": {
"@types/bun": "1.3.4",
"typescript": "^5.5.0"
}
}
@@ -0,0 +1,35 @@
# `@heretek-ai/pack-sdk`
Shared contract for Tier 2 domain packs (`opencode-packs` monorepo).
## Contract
```ts
import { definePack, packConformance, hostSatisfiesCompat } from "@heretek-ai/pack-sdk";
export default definePack({
id: "mypack",
version: "1.0.0",
hostCompat: "2.0.x",
laneRequires: [{ capability: "mypack", image: "ghcr.io/heretek-opencode/lane-mypack:1.x" }],
tools: [{ server: "./mcp/mypack.js", transport: "stdio" }],
skills: ["./skills/mypack.md"],
commands: [{ name: "mypack.do", handler: "tools.mypackDo" }],
hooks: { toolGuard: "./src/guard.ts" },
ready: async (hostVersion) =>
hostSatisfiesCompat("2.0.x", hostVersion)
? { status: "ready" }
: { status: "degraded", reason: `needs host 2.0.x, got ${hostVersion}` },
});
```
## Rules
- `hostCompat` supports exact (`2.0.26`), x-range (`2.0.x`), caret, tilde, `*`.
- `packConformance(manifest)` checks: manifest valid, hostCompat well-formed,
hook signatures present, tool entries JSON round-trip, `ready()` is a function,
lane image ref present whenever `laneRequires` is declared.
- Readiness is a handshake, not a crash: a pack that cannot verify its runtime
(missing binary, unreachable MCP) returns `{ status: "degraded", reason }`
with a machine-readable, actionable reason.
- All `@opencode/*` dependencies in every pack are pinned exact (`2.0.26`).
@@ -0,0 +1,22 @@
{
"name": "@heretek-ai/pack-sdk",
"version": "1.0.0",
"description": "Shared pack contract: definePack(), PackManifestSchema, ready() handshake, packConformance()",
"license": "MIT",
"type": "module",
"main": "./src/index.ts",
"exports": {
".": "./src/index.ts"
},
"files": ["src", "README.md"],
"scripts": {
"check": "bunx tsc --noEmit -p ../../tsconfig.json",
"test": "bun test test"
},
"dependencies": {
"zod": "^3.23.8"
},
"devDependencies": {
"typescript": "^5.5.0"
}
}
@@ -0,0 +1,220 @@
import { z } from "zod";
/* ------------------------------------------------------------------ */
/* Pack manifest schema */
/* ------------------------------------------------------------------ */
export const LaneRequireSchema = z.object({
capability: z.string().min(1),
image: z.string().min(1),
});
export const ToolRefSchema = z.object({
server: z.string().min(1),
transport: z.enum(["stdio", "http"]),
/** Optional JSON-Schema-ish tool input schema; must survive a JSON round-trip. */
schema: z.record(z.unknown()).optional(),
});
export const CommandRefSchema = z.object({
name: z.string().min(1),
handler: z.string().min(1),
});
/** Hook name -> handler module path. Values must be non-empty module refs. */
export const HooksSchema = z.record(z.string().min(1));
export const PackManifestSchema = z.object({
id: z.string().min(1),
version: z.string().min(1),
hostCompat: z.string().min(1),
laneRequires: z.array(LaneRequireSchema).optional(),
tools: z.array(ToolRefSchema).optional(),
skills: z.array(z.string().min(1)).optional(),
commands: z.array(CommandRefSchema).optional(),
hooks: HooksSchema.optional(),
agents: z.array(z.string().min(1)).optional(),
providers: z.array(z.string().min(1)).optional(),
});
export type PackManifestData = z.infer<typeof PackManifestSchema>;
export type LaneRequire = z.infer<typeof LaneRequireSchema>;
export type ToolRef = z.infer<typeof ToolRefSchema>;
export type CommandRef = z.infer<typeof CommandRefSchema>;
/* ------------------------------------------------------------------ */
/* ready() handshake */
/* ------------------------------------------------------------------ */
export interface ReadyResult {
status: "ready" | "degraded";
reason?: string;
}
export type ReadyFn = (hostVersion: string) => Promise<ReadyResult>;
export interface PackManifest extends PackManifestData {
ready: ReadyFn;
}
/** Declare a pack. Validates the data half with Zod and requires a ready() handshake. */
export function definePack(manifest: PackManifest): PackManifest {
const { ready, ...data } = manifest;
PackManifestSchema.parse(data);
if (typeof ready !== "function") {
const id = (data as { id?: unknown }).id;
throw new Error(
`pack ${JSON.stringify(id)}: manifest must export a ready(hostVersion) handshake function`,
);
}
return manifest;
}
/* ------------------------------------------------------------------ */
/* hostCompat range handling */
/* ------------------------------------------------------------------ */
const EXACT_RE = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/;
const XRANGE_RE = /^\d+(\.\d+)?(\.[xX*]|\.x)?$/;
const CARET_RE = /^\^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/;
const TILDE_RE = /^~\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/;
/** True when the hostCompat string is a supported range expression. */
export function isValidCompatRange(range: string): boolean {
return (
range === "*" ||
EXACT_RE.test(range) ||
XRANGE_RE.test(range) ||
CARET_RE.test(range) ||
TILDE_RE.test(range)
);
}
function parseVersion(v: string): [number, number, number] | null {
const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v.trim());
if (!m) return null;
return [Number(m[1]), Number(m[2]), Number(m[3])];
}
function cmp(a: [number, number, number], b: [number, number, number]): number {
for (let i = 0; i < 3; i++) {
if (a[i] !== b[i]) return a[i] < b[i] ? -1 : 1;
}
return 0;
}
/** Evaluate hostVersion against a hostCompat range. Unknown shapes fail closed (false). */
export function hostSatisfiesCompat(hostCompat: string, hostVersion: string): boolean {
const range = hostCompat.trim();
const ver = parseVersion(hostVersion);
if (!ver) return false;
if (range === "*") return true;
if (EXACT_RE.test(range)) {
const base = parseVersion(range);
return base !== null && cmp(ver, base) === 0;
}
if (XRANGE_RE.test(range)) {
const parts = range.split(".");
const major = Number(parts[0]);
if (ver[0] !== major) return false;
if (parts.length === 1) return true;
if (parts[1] === "x" || parts[1] === "X" || parts[1] === "*") return true;
if (ver[1] !== Number(parts[1])) return false;
if (parts.length === 2) return true;
const patch = parts[2];
return patch === "x" || patch === "X" || patch === "*";
}
if (CARET_RE.test(range)) {
const base = parseVersion(range.slice(1))!;
return cmp(ver, base) >= 0 && ver[0] === base[0];
}
if (TILDE_RE.test(range)) {
const base = parseVersion(range.slice(1))!;
return cmp(ver, base) >= 0 && ver[0] === base[0] && ver[1] === base[1];
}
return false;
}
/* ------------------------------------------------------------------ */
/* Conformance */
/* ------------------------------------------------------------------ */
export interface ConformanceResult {
pass: boolean;
fail: string[];
}
function jsonRoundTrips(value: unknown): boolean {
try {
return JSON.stringify(JSON.parse(JSON.stringify(value))) === JSON.stringify(value);
} catch {
return false;
}
}
/**
* Run the pack contract checks:
* manifest valid, hostCompat range well-formed, hook signatures present,
* tool entries round-trip through JSON, readiness handshake is a function,
* lane image refs present whenever laneRequires is declared.
*/
export function packConformance(manifest: unknown): ConformanceResult {
const fail: string[] = [];
if (typeof manifest !== "object" || manifest === null) {
return { pass: false, fail: ["manifest must be an object"] };
}
const m = manifest as Record<string, unknown>;
const { ready, ...data } = m;
const parsed = PackManifestSchema.safeParse(data);
if (!parsed.success) {
fail.push(`manifest invalid: ${parsed.error.issues.map((i) => i.message).join("; ")}`);
}
const id = typeof m.id === "string" && m.id.length > 0 ? m.id : "<unknown>";
if (typeof ready !== "function") {
fail.push(`pack "${id}": ready(hostVersion) handshake missing (must be a function)`);
}
if (typeof m.hostCompat !== "string" || !isValidCompatRange(m.hostCompat)) {
fail.push(
`pack "${id}": hostCompat ${JSON.stringify(m.hostCompat)} is not a supported semver range ` +
`(exact "2.0.26", x-range "2.0.x", caret "^2.0.0", tilde "~2.0.0", or "*")`,
);
}
if (m.hooks !== undefined) {
if (typeof m.hooks !== "object" || m.hooks === null || Array.isArray(m.hooks)) {
fail.push(`pack "${id}": hooks must be a { name: handlerPath } map`);
} else {
for (const [name, handler] of Object.entries(m.hooks as Record<string, unknown>)) {
if (typeof handler !== "string" || handler.length === 0) {
fail.push(`pack "${id}": hook "${name}" must map to a non-empty handler module path`);
}
}
}
}
if (m.tools !== undefined && Array.isArray(m.tools)) {
m.tools.forEach((t, i) => {
if (!jsonRoundTrips(t)) {
fail.push(`pack "${id}": tools[${i}] does not survive a JSON schema round-trip`);
}
});
}
if (m.laneRequires !== undefined && Array.isArray(m.laneRequires)) {
m.laneRequires.forEach((l, i) => {
const lane = l as { capability?: unknown; image?: unknown };
if (typeof lane.image !== "string" || lane.image.length === 0) {
fail.push(
`pack "${id}": laneRequires[${i}] declares capability ` +
`${JSON.stringify(lane.capability)} without a lane image ref`,
);
}
});
}
return { pass: fail.length === 0, fail };
}
@@ -0,0 +1,183 @@
import { describe, expect, test } from "bun:test";
import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import {
hostSatisfiesCompat,
packConformance,
} from "../src/index";
import godotManifest, { ready as godotReady } from "../../../packs/pack-godot/src/index";
import { GODOT_TOOLS } from "../../../packs/pack-godot/src/tools";
import { guardTool } from "../../../packs/pack-godot/src/guard";
import gdevelopManifest, { ready as gdevelopReady } from "../../../packs/pack-gdevelop/src/index";
import reaManifest, { ready as reaReady } from "../../../packs/pack-rea/src/index";
import { checkReaReadiness } from "../../../packs/pack-rea/src/readiness";
const HOST = "2.0.26";
describe("packConformance", () => {
test("passes for pack-godot", () => {
const result = packConformance(godotManifest);
expect({ result }).toEqual({ result: { pass: true, fail: [] } });
});
test("passes for pack-gdevelop", () => {
expect(packConformance(gdevelopManifest)).toEqual({ pass: true, fail: [] });
});
test("passes for pack-rea", () => {
expect(packConformance(reaManifest)).toEqual({ pass: true, fail: [] });
});
test("fails on missing ready() handshake", () => {
const { ready: _drop, ...data } = godotManifest;
const result = packConformance(data);
expect(result.pass).toBe(false);
expect(result.fail.some((f) => f.includes("ready"))).toBe(true);
});
test("fails on malformed hostCompat", () => {
const result = packConformance({ ...godotManifest, hostCompat: "sometime-later" });
expect(result.pass).toBe(false);
expect(result.fail.some((f) => f.includes("hostCompat"))).toBe(true);
});
test("fails on empty hook handler", () => {
const result = packConformance({ ...godotManifest, hooks: { toolGuard: "" } });
expect(result.pass).toBe(false);
expect(result.fail.some((f) => f.includes("toolGuard"))).toBe(true);
});
test("fails on laneRequires without an image ref", () => {
const result = packConformance({
...godotManifest,
laneRequires: [{ capability: "godot", image: "" }],
});
expect(result.pass).toBe(false);
expect(result.fail.some((f) => f.includes("image"))).toBe(true);
});
test("fails on tool entry that does not JSON round-trip", () => {
const result = packConformance({
...godotManifest,
tools: [{ server: "./mcp/godot.js", transport: "stdio", schema: { n: 1n } }],
});
expect(result.pass).toBe(false);
expect(result.fail.some((f) => f.includes("round-trip"))).toBe(true);
});
test("fails on an invalid manifest body", () => {
const result = packConformance({ ...godotManifest, id: "" });
expect(result.pass).toBe(false);
expect(result.fail.some((f) => f.includes("manifest invalid"))).toBe(true);
});
});
describe("hostSatisfiesCompat", () => {
test("x-range, exact, caret, tilde, star", () => {
expect(hostSatisfiesCompat("2.0.x", "2.0.26")).toBe(true);
expect(hostSatisfiesCompat("2.0.x", "2.1.0")).toBe(false);
expect(hostSatisfiesCompat("2.0.26", "2.0.26")).toBe(true);
expect(hostSatisfiesCompat("2.0.26", "2.0.27")).toBe(false);
expect(hostSatisfiesCompat("^2.0.0", "2.9.0")).toBe(true);
expect(hostSatisfiesCompat("^2.0.0", "3.0.0")).toBe(false);
expect(hostSatisfiesCompat("~2.0.0", "2.0.9")).toBe(true);
expect(hostSatisfiesCompat("~2.0.0", "2.1.0")).toBe(false);
expect(hostSatisfiesCompat("*", "9.9.9")).toBe(true);
expect(hostSatisfiesCompat("bogus", HOST)).toBe(false);
expect(hostSatisfiesCompat("2.0.x", "not-a-version")).toBe(false);
});
});
describe("ready() handshakes", () => {
test("godot: ready when host matches and binary present", async () => {
expect(await godotReady(HOST, { probeGodot: () => true })).toEqual({ status: "ready" });
});
test("godot: degraded on host mismatch", async () => {
const r = await godotReady("1.9.0", { probeGodot: () => true });
expect(r.status).toBe("degraded");
expect(r.reason).toContain("1.9.0");
});
test("godot: degraded with reason when binary missing", async () => {
const r = await godotReady(HOST, { probeGodot: () => false });
expect(r.status).toBe("degraded");
expect(r.reason).toContain("godot binary");
});
test("gdevelop: ready on matching host, degraded otherwise", async () => {
expect(await gdevelopReady(HOST)).toEqual({ status: "ready" });
const r = await gdevelopReady("3.0.0");
expect(r.status).toBe("degraded");
expect(r.reason).toContain("3.0.0");
});
test("rea: ready when host matches and REA responsive", async () => {
expect(await reaReady(HOST, { rea: { probe: () => true } })).toEqual({ status: "ready" });
});
test("rea: degraded with actionable remediation when REA missing", async () => {
const r = await reaReady(HOST, { rea: { probe: () => false } });
expect(r.status).toBe("degraded");
expect(r.reason).toContain("rea doctor");
expect(r.reason).toContain("rea setup");
});
test("rea: degraded on host mismatch without probing REA", async () => {
let probed = false;
const r = await reaReady("1.0.0", {
rea: {
probe: () => {
probed = true;
return true;
},
},
});
expect(r.status).toBe("degraded");
expect(probed).toBe(false);
});
test("checkReaReadiness returns remediation advice only (never throws)", async () => {
const r = await checkReaReadiness({ probe: () => false });
expect(r.status).toBe("degraded");
expect(r.remediation.length).toBeGreaterThan(0);
});
});
describe("godot tool stubs", () => {
test("tool defs JSON round-trip and guard allows only godot.*", () => {
for (const tool of GODOT_TOOLS) {
expect(JSON.parse(JSON.stringify(tool))).toEqual(tool);
expect(tool.name.startsWith("godot.")).toBe(true);
}
expect(guardTool("godot.openScene").allow).toBe(true);
expect(guardTool("fetch.raw").allow).toBe(false);
});
});
describe("@opencode/* version pins", () => {
const testDir = dirname(fileURLToPath(import.meta.url));
const packsDir = join(testDir, "..", "..", "..", "packs");
const sections = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
for (const pack of ["pack-godot", "pack-gdevelop", "pack-rea"]) {
test(`${pack}: every @opencode/* dep is exactly 2.0.26`, () => {
const pkg = JSON.parse(readFileSync(join(packsDir, pack, "package.json"), "utf8")) as Record<
string,
Record<string, string> | undefined
>;
const pins: Array<[string, string, string]> = [];
for (const section of sections) {
for (const [name, version] of Object.entries(pkg[section] ?? {})) {
if (name.startsWith("@opencode/")) pins.push([section, name, version]);
}
}
expect(pins.length).toBeGreaterThan(0);
for (const [section, name, version] of pins) {
expect(version, `${pack} ${section}:${name}`).toBe("2.0.26");
}
});
}
});
@@ -0,0 +1,17 @@
# lane-gdevelop — GDevelop verification lane for pack-gdevelop.
# Built and published by CI as ghcr.io/heretek-opencode/lane-gdevelop:5.x.
# CI passes --build-arg BASE_IMAGE=<digest-pinned ref>; the default below is
# for local iteration only and must not be released unpinned.
ARG BASE_IMAGE=oven/bun:1
FROM ${BASE_IMAGE} AS lane
LABEL org.opencontainers.image.title="lane-gdevelop" \
org.opencontainers.image.description="GDevelop patch-stack verification lane for pack-gdevelop"
ENV OPENCE_PACK=gdevelop
WORKDIR /work
# Rebase-discipline gate (donor issue #13): the patch stack must apply cleanly.
# The pack source is mounted at /work by the lane runner before this runs.
CMD ["bun", "run", "verify-patches"]
@@ -0,0 +1,14 @@
# `@heretek-ai/pack-gdevelop`
GDevelop domain pack: one consolidated boundary from the donor's four
packages (`ai-studio` + `opencode-gdevelop` + `unlock-shim`; the provider
bridge stays optional at `./provider-bridge`, BYOK only).
- Exact `@opencode/plugin` pin: `2.0.26`.
- `patches/` carries representative patch seams with automated
`git apply --check` verification (`bun run verify-patches`) — the
per-upstream-release rebase discipline (donor issue #13).
- **Donor fork decommissioned (donor issue #6):** the `GDevelop-Unlocked`
donor fork is archived and its daily sync dropped. This pack is the single
source of truth; the donor remains readable history only.
- `Dockerfile.lane` provides the reproducible verification environment.
@@ -0,0 +1,22 @@
{
"name": "@heretek-ai/pack-gdevelop",
"version": "1.0.0",
"description": "GDevelop domain pack: consolidated unlock shim + BYOK AI studio + patch-stack verification",
"license": "MIT",
"type": "module",
"main": "./src/index.ts",
"exports": {
".": "./src/index.ts"
},
"files": ["src", "skills", "patches", "scripts", "Dockerfile.lane", "README.md"],
"scripts": {
"check": "bunx tsc --noEmit -p ../../tsconfig.json",
"verify-patches": "bun scripts/verify-patches.ts"
},
"dependencies": {
"@opencode/plugin": "2.0.26"
},
"devDependencies": {
"typescript": "^5.5.0"
}
}
@@ -0,0 +1,26 @@
From <commit> Mon Sep 17 00:00:00 2001
From: t <t@t>
Date: Mon, 12 Oct 2026 12:50:55 -0400
Subject: [PATCH] shim
---
unlock-shim/control-shim.js | 10 ++++++++++
1 file changed, 10 insertions(+)
create mode 100644 unlock-shim/control-shim.js
diff --git a/unlock-shim/control-shim.js b/unlock-shim/control-shim.js
new file mode 100644
index 0000000..a471730
--- /dev/null
+++ b/unlock-shim/control-shim.js
@@ -0,0 +1,10 @@
+// unlock-shim control stub (representative of donor patch 06-electron-hook).
+// The full shim preloads before the editor shell and exposes the local
+// control channel consumed by opencode-gdevelop offline tools.
+export const SHIM_CHANNEL = "heretek:gdevelop-control";
+
+export function installControlShim(target) {
+ if (!target || typeof target !== "object") return { installed: false };
+ target[SHIM_CHANNEL] = { version: 1, transport: "preload" };
+ return { installed: true, channel: SHIM_CHANNEL };
+}
@@ -0,0 +1,27 @@
From <commit> Mon Sep 17 00:00:00 2001
From: t <t@t>
Date: Mon, 12 Oct 2026 12:50:55 -0400
Subject: [PATCH] studio
---
ai-studio/mount.js | 11 +++++++++++
1 file changed, 11 insertions(+)
create mode 100644 ai-studio/mount.js
diff --git a/ai-studio/mount.js b/ai-studio/mount.js
new file mode 100644
index 0000000..8536d03
--- /dev/null
+++ b/ai-studio/mount.js
@@ -0,0 +1,11 @@
+// ai-studio mount stub (representative of donor patch 11-studio-runtime-mount).
+// The full studio mounts a BYOK session runtime into the editor container;
+// API keys are held in memory only and never persisted.
+export const STUDIO_MOUNT_POINT = "ai-studio";
+
+export function mountStudio(container, { apiKey } = {}) {
+ if (!container || typeof apiKey !== "string" || apiKey.length === 0) {
+ return { mounted: false, reason: "container and in-memory apiKey required" };
+ }
+ return { mounted: true, mount: STUDIO_MOUNT_POINT, persistKey: false };
+}
@@ -0,0 +1,31 @@
# patches/ — GDevelop patch stack (representative samples + verification)
## Donor stack (context)
The `GDevelop-Unlocked` donor carried a 14-patch surgical stack
(`01-usage-predicate` … `14-ai-utils`) converting stock upstream GDevelop
source to fork parity: entitlement unlocks (01–06), AI generation glue and
BYOK routing (07–14), plus the `ai-studio` / `unlock-shim` runtime overlay
that the hunks import. Patches were generated, anchor-gated, and applied by
the donor's driver script before building.
## What lives here
This pack keeps **minimal representative patches** proving the rebase
discipline, not the full donor stack:
| Patch | Represents |
|---|---|
| `01-unlock-shim-stub.patch` | Electron-hook / control-shim preload seam (donor 06) |
| `02-ai-studio-mount.patch` | Studio runtime mount seam (donor 11) |
## Verification (donor issue #13)
`bun run verify-patches` (i.e. `scripts/verify-patches.ts`) asserts every
`*.patch` applies cleanly via `git apply --check` in a pristine temp repo.
On upstream GDevelop releases, CI re-runs this job and fails loudly on drift.
## Donor fork decommissioned (donor issue #6)
The donor fork is archived: daily sync is dropped, and this patch stack plus
the consolidated pack are the single source of truth. See `../README.md`.
@@ -0,0 +1,55 @@
import { spawnSync } from "node:child_process";
import { mkdtempSync, readdirSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
/**
* Patch-stack rebase discipline (donor issue #13): every patch in patches/
* must apply cleanly onto a pristine tree. Each patch is checked with
* `git apply --check` inside a fresh empty repo so the verification is
* independent of whatever checkout runs it.
*/
const thisDir = dirname(fileURLToPath(import.meta.url));
const patchesDir = join(thisDir, "..", "patches");
function checkPatch(path: string): { ok: boolean; output: string } {
const workdir = mkdtempSync(join(tmpdir(), "gdevelop-patch-check-"));
try {
const init = spawnSync("git", ["init", "-q", "."], { cwd: workdir });
if (init.status !== 0) {
return { ok: false, output: `git init failed: ${init.stderr.toString()}` };
}
const check = spawnSync("git", ["apply", "--check", path], { cwd: workdir });
const output = `${check.stdout.toString()}${check.stderr.toString()}`.trim();
return { ok: check.status === 0, output };
} finally {
rmSync(workdir, { recursive: true, force: true });
}
}
const patches = readdirSync(patchesDir)
.filter((f) => f.endsWith(".patch"))
.sort();
if (patches.length === 0) {
console.error("verify-patches: no patches found");
process.exit(1);
}
let failed = 0;
for (const patch of patches) {
const result = checkPatch(join(patchesDir, patch));
if (result.ok) {
console.log(`ok ${patch}`);
} else {
failed += 1;
console.error(`FAIL ${patch}${result.output ? `: ${result.output}` : ""}`);
}
}
if (failed > 0) {
console.error(`verify-patches: ${failed}/${patches.length} patches do not apply cleanly`);
process.exit(1);
}
console.log(`verify-patches: ${patches.length}/${patches.length} patches apply cleanly`);
@@ -0,0 +1,10 @@
# gdevelop-byok skill (pack-gdevelop)
Applies when the operator asks for GDevelop AI assistance.
1. BYOK only: the API key lives in memory for the session. Never persist it,
never send project content to hosted storage (no S3 presigned uploads).
2. If the request cannot be served locally, say so and stop — do not silently
fall back to a hosted endpoint.
3. Patch-stack changes go through `bun run verify-patches` before anything
else; a patch that does not apply cleanly is a hard stop.
@@ -0,0 +1,42 @@
import {
definePack,
hostSatisfiesCompat,
type ReadyResult,
} from "../../../packages/pack-sdk/src/index";
export const PACK_ID = "gdevelop";
export const PACK_VERSION = "1.0.0";
export const HOST_COMPAT = "2.0.x";
/**
* Consolidation note: the donor carried four packages (ai-studio, opencode-gdevelop,
* opencode-provider, unlock-shim). This pack merges the in-editor surface
* (unlock-shim + opencode-gdevelop + ai-studio) into one boundary; the provider
* bridge remains an optional subpath (`./provider-bridge`, BYOK only).
*/
export const CONSOLIDATED_MODULES = ["unlock-shim", "opencode-gdevelop", "ai-studio"] as const;
export const OPTIONAL_PROVIDER_BRIDGE = "./provider-bridge";
export async function ready(hostVersion: string): Promise<ReadyResult> {
if (!hostSatisfiesCompat(HOST_COMPAT, hostVersion)) {
return {
status: "degraded",
reason: `pack-gdevelop ${PACK_VERSION} requires host ${HOST_COMPAT}, got ${hostVersion}`,
};
}
return { status: "ready" };
}
export const manifest = definePack({
id: PACK_ID,
version: PACK_VERSION,
hostCompat: HOST_COMPAT,
laneRequires: [{ capability: "gdevelop", image: "ghcr.io/heretek-opencode/lane-gdevelop:5.x" }],
tools: [{ server: "./mcp/gdevelop.js", transport: "stdio" }],
skills: ["./skills/gdevelop-byok.md"],
commands: [{ name: "gdevelop.verify-patches", handler: "scripts.verifyPatches" }],
providers: [OPTIONAL_PROVIDER_BRIDGE],
ready,
});
export default manifest;
@@ -0,0 +1,20 @@
# lane-godot — headless Godot 4.x verification lane for pack-godot.
# Built and published by CI as ghcr.io/heretek-opencode/lane-godot:4.x.
# CI passes --build-arg BASE_IMAGE=<digest-pinned Godot headless ref>; the
# default below is for local iteration only and must not be released unpinned.
ARG BASE_IMAGE=barichello/godot-ci:latest
FROM ${BASE_IMAGE} AS lane
LABEL org.opencontainers.image.title="lane-godot" \
org.opencontainers.image.description="Headless Godot 4.x lane for pack-godot task verification"
ENV GODOT_HEADLESS=1 \
OPENCE_PACK=godot
WORKDIR /work
# godot.ping handshake target when no editor is attached.
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \
CMD godot --version || exit 1
CMD ["godot", "--headless", "--version"]
@@ -0,0 +1,11 @@
# `@heretek-ai/pack-godot`
Godot 4 domain pack: host-side plugin entry (`src/`), GDScript companion
addon artifact (`companion/`), scene skill (`skills/`), headless lane image
(`Dockerfile.lane`).
- Exact `@opencode/plugin` pin: `2.0.26` (fixes the donor's unpinned drift).
- `ready(hostVersion)` requires host `2.0.x` and a resolvable `godot` binary;
otherwise it returns `degraded` with an actionable reason.
- `companion/` ships as the versioned `godot_opencode-<version>.zip` release
artifact; only identity files live in-repo.
@@ -0,0 +1,16 @@
# companion/addons/godot_opencode/
GDScript editor-side companion addon for `pack-godot`.
- The full editor addon (connection, dispatcher, domain handlers, debugger,
undo manager) ships as a **versioned zip artifact** attached to each
`pack-godot` release (`godot_opencode-<version>.zip`), installed by
extracting into `<project>/addons/godot_opencode/`.
- This directory carries the minimum needed to identify the artifact:
`plugin.cfg` (identity: name, version, entry script) and a minimal
entry-point stub. The stub documents the bridge contract (authenticated
loopback bridge, default port 9500) and points at the release zip for the
full implementation.
- Ported from the `godot-opencode` donor (`companion/addons/godot_opencode/`);
the donor's unpinned host dependency is fixed here by the pack's exact
`@opencode/plugin` pin (`2.0.26`).
@@ -0,0 +1,19 @@
@tool
extends EditorPlugin
## Godot OpenCode companion addon entry point (pack-godot stub).
##
## The full implementation (authenticated loopback bridge, dispatcher, domain
## handlers, debugger integration) ships as the versioned release artifact
## `godot_opencode-<version>.zip`. This stub exists so the addon identity
## (plugin.cfg) resolves in a project tree; it performs no bridge I/O itself.
const BRIDGE_HOST := "127.0.0.1"
const BRIDGE_PORT := 9500
func _enter_tree() -> void:
print("[godot_opencode] stub loaded; install godot_opencode-<version>.zip for the full bridge")
func _exit_tree() -> void:
pass
@@ -0,0 +1,7 @@
[plugin]
name="Godot OpenCode"
description="Godot Editor Companion Addon for OpenCode (authenticated loopback bridge; full implementation ships as the versioned release zip)"
author="Heretek-OpenCode"
version="1.0.0"
script="godot_opencode.gd"
@@ -0,0 +1,23 @@
{
"name": "@heretek-ai/pack-godot",
"version": "1.0.0",
"description": "Godot 4 domain pack: OpenCode plugin + GDScript companion addon + headless lane image",
"license": "MIT",
"type": "module",
"main": "./src/index.ts",
"exports": {
".": "./src/index.ts"
},
"files": ["src", "skills", "companion", "Dockerfile.lane", "README.md"],
"scripts": {
"check": "bunx tsc --noEmit -p ../../tsconfig.json"
},
"dependencies": {
"@opencode/plugin": "2.0.26"
},
"devDependencies": {
"@opencode/cli": "2.0.26",
"@opencode/sdk": "2.0.26",
"typescript": "^5.5.0"
}
}
@@ -0,0 +1,10 @@
# godot-scene skill (pack-godot)
Applies when the operator asks to inspect or change a Godot scene.
1. Run the `godot.ping` handshake first; on `degraded`, stop and surface the
readiness reason instead of guessing at scene state.
2. Prefer `godot.openScene` with a project-relative path (`res://…`).
3. Never invent node paths: list the scene tree through the bridge before editing.
4. Headless verification (`godot.runScene`) runs in the `lane-godot` image,
never against the operator's live editor.
@@ -0,0 +1,8 @@
/** Minimal tool-guard stub: allow only godot.* tools through this pack's lane. */
const ALLOWED_PREFIX = "godot.";
export function guardTool(toolName: string): { allow: boolean; reason?: string } {
if (toolName.startsWith(ALLOWED_PREFIX)) return { allow: true };
return { allow: false, reason: `pack-godot guard blocks non-godot tool "${toolName}"` };
}
@@ -0,0 +1,81 @@
import { accessSync, constants as fsConstants } from "node:fs";
import { join } from "node:path";
import {
definePack,
hostSatisfiesCompat,
type ReadyResult,
} from "../../../packages/pack-sdk/src/index";
import { GODOT_TOOLS } from "./tools";
export const PACK_ID = "godot";
export const PACK_VERSION = "1.0.0";
export const HOST_COMPAT = "2.0.x";
export const GODOT_BRIDGE_PORT = 9500;
export const GODOT_BRIDGE_HOST = "127.0.0.1";
export interface GodotReadyOptions {
/** Override the godot-binary probe (tests inject true/false here). */
probeGodot?: () => boolean | Promise<boolean>;
}
/** True when a `godot` binary is resolvable (Bun.which, falling back to a PATH scan). */
export function defaultGodotProbe(): boolean {
const bunWhich = (globalThis as { Bun?: { which?: (cmd: string) => string | null } }).Bun?.which;
if (typeof bunWhich === "function") {
try {
if (bunWhich("godot") !== null) return true;
} catch {
/* fall through to PATH scan */
}
}
const delimiter = process.platform === "win32" ? ";" : ":";
const suffixes = process.platform === "win32" ? ["", ".exe", ".cmd"] : [""];
for (const dir of (process.env.PATH ?? "").split(delimiter)) {
if (!dir) continue;
for (const suffix of suffixes) {
try {
accessSync(join(dir, `godot${suffix}`), fsConstants.X_OK);
return true;
} catch {
/* keep scanning */
}
}
}
return false;
}
export async function ready(
hostVersion: string,
opts: GodotReadyOptions = {},
): Promise<ReadyResult> {
if (!hostSatisfiesCompat(HOST_COMPAT, hostVersion)) {
return {
status: "degraded",
reason: `pack-godot ${PACK_VERSION} requires host ${HOST_COMPAT}, got ${hostVersion}`,
};
}
const probe = opts.probeGodot ?? defaultGodotProbe;
if (!(await probe())) {
return {
status: "degraded",
reason:
"godot binary not found on PATH; install a Godot 4.x headless editor build or run inside the lane-godot image",
};
}
return { status: "ready" };
}
export const manifest = definePack({
id: PACK_ID,
version: PACK_VERSION,
hostCompat: HOST_COMPAT,
laneRequires: [{ capability: "godot", image: "ghcr.io/heretek-opencode/lane-godot:4.x" }],
tools: [{ server: "./mcp/godot.js", transport: "stdio" }],
skills: ["./skills/godot-scene.md"],
commands: [{ name: "godot.open-scene", handler: "tools.godotOpenScene" }],
hooks: { toolGuard: "./src/guard.ts" },
ready,
});
export { GODOT_TOOLS };
export default manifest;
@@ -0,0 +1,44 @@
/** Stub tool definitions for the Godot editor bridge (scene open / headless run / ping). */
export interface GodotToolDef {
name: string;
description: string;
inputSchema: Record<string, unknown>;
}
export const GODOT_TOOLS: GodotToolDef[] = [
{
name: "godot.ping",
description: "Handshake check: verify the Godot editor bridge is reachable.",
inputSchema: {
type: "object",
properties: {},
additionalProperties: false,
},
},
{
name: "godot.openScene",
description: "Open a scene file in the attached Godot editor.",
inputSchema: {
type: "object",
properties: {
path: { type: "string", description: "Project-relative scene path, e.g. res://main.tscn" },
},
required: ["path"],
additionalProperties: false,
},
},
{
name: "godot.runScene",
description: "Run a scene headless and return captured output.",
inputSchema: {
type: "object",
properties: {
path: { type: "string", description: "Project-relative scene path" },
timeoutMs: { type: "number", description: "Kill timeout in milliseconds" },
},
required: ["path"],
additionalProperties: false,
},
},
];
@@ -0,0 +1,11 @@
# `@heretek-ai/pack-rea`
REA reverse-engineering domain pack: standardized `readiness.ts` handshake
plus agent and skill stubs.
- Exact pins: `@opencode/plugin` / `@opencode/cli` / `@opencode/sdk` `2.0.26`.
- `ready(hostVersion)` requires host `2.0.x` **and** a responsive REA side
(`rea` CLI on PATH); otherwise `degraded` with actionable remediation
(`rea doctor --client opencode --json`, `rea setup --client opencode --dry-run`).
- The readiness pattern here is the reference implementation promoted into
`@heretek-ai/pack-sdk`'s pack contract.
@@ -0,0 +1,10 @@
# rea-analyst agent (pack-rea)
Reverse-engineering analyst: triages an unknown binary or codebase surface
and produces a falsifiable investigation plan.
1. Start from `rea.check-readiness`; on `degraded`, hand the operator the
remediation commands instead of proceeding blind.
2. State every claim as verified or inferred, with the evidence that backs it.
3. Prefer static observation over execution; never run untrusted binaries
outside the pack lane.
@@ -0,0 +1,8 @@
# rea-investigator agent (pack-rea)
Deep-dive investigator: executes the analyst's plan and reconstructs
behavior, data flows, and trust boundaries.
1. Work strictly inside the lane; no ambient network.
2. Record provenance (tool, params hash, timestamp) for every finding.
3. Stop at the first contradiction and re-plan — do not pave over anomalies.
@@ -0,0 +1,25 @@
{
"name": "@heretek-ai/pack-rea",
"version": "1.0.0",
"description": "REA reverse-engineering domain pack: readiness handshake + agents + skills",
"license": "MIT",
"type": "module",
"main": "./src/index.ts",
"exports": {
".": "./src/index.ts"
},
"files": ["src", "agents", "skills", "README.md"],
"scripts": {
"check": "bunx tsc --noEmit -p ../../tsconfig.json"
},
"dependencies": {
"@opencode/plugin": "2.0.26"
},
"devDependencies": {
"@opencode/cli": "2.0.26",
"@opencode/plugin": "2.0.26",
"@opencode/sdk": "2.0.26",
"rea-agents": "6.4.0",
"typescript": "^5.5.0"
}
}
@@ -0,0 +1,5 @@
# rea-evidence skill (pack-rea)
Every REA finding ships with evidence: the command, its exit code, and a
content hash of its output. A finding without evidence is a hypothesis —
label it as one.
@@ -0,0 +1,7 @@
# rea-readiness skill (pack-rea)
Before any REA work, run `rea.check-readiness`:
- `ready` → proceed.
- `degraded` → surface `reason` plus each `remediation` command verbatim and
stop. Do not improvise alternative tooling.
@@ -0,0 +1,45 @@
import {
definePack,
hostSatisfiesCompat,
type ReadyResult,
} from "../../../packages/pack-sdk/src/index";
import { checkReaReadiness, type ReaReadinessOptions } from "./readiness";
export const PACK_ID = "rea";
export const PACK_VERSION = "1.0.0";
export const HOST_COMPAT = "2.0.x";
export interface ReaReadyOptions {
rea?: ReaReadinessOptions;
}
export async function ready(hostVersion: string, opts: ReaReadyOptions = {}): Promise<ReadyResult> {
if (!hostSatisfiesCompat(HOST_COMPAT, hostVersion)) {
return {
status: "degraded",
reason: `pack-rea ${PACK_VERSION} requires host ${HOST_COMPAT}, got ${hostVersion}`,
};
}
const rea = await checkReaReadiness(opts.rea);
if (rea.status !== "ready") {
return {
status: "degraded",
reason: `${rea.reason}; remediation: ${rea.remediation.join(" | ")}`,
};
}
return { status: "ready" };
}
export const manifest = definePack({
id: PACK_ID,
version: PACK_VERSION,
hostCompat: HOST_COMPAT,
tools: [{ server: "./mcp/rea.js", transport: "stdio" }],
agents: ["./agents/rea-analyst.md", "./agents/rea-investigator.md"],
skills: ["./skills/rea-readiness.md", "./skills/rea-evidence.md"],
commands: [{ name: "rea.check-readiness", handler: "readiness.checkReaReadiness" }],
ready,
});
export { checkReaReadiness };
export default manifest;
@@ -0,0 +1,62 @@
import { accessSync, constants as fsConstants } from "node:fs";
import { join } from "node:path";
/**
* Standardized readiness pattern (promoted from the opencode-rea donor):
* check whether the REA MCP side is responsive and return ready|degraded
* with actionable remediation. Advice strings only — never executed here.
*/
export const DOCTOR_CMD = "rea doctor --client opencode --json";
export const SETUP_CMD = "rea setup --client opencode --dry-run";
export interface ReaReadiness {
status: "ready" | "degraded";
reason?: string;
remediation: string[];
}
export interface ReaReadinessOptions {
/** Override the responsiveness probe (tests inject true/false here). */
probe?: () => boolean | Promise<boolean>;
}
function defaultReaProbe(): boolean {
const bunWhich = (globalThis as { Bun?: { which?: (cmd: string) => string | null } }).Bun?.which;
if (typeof bunWhich === "function") {
try {
if (bunWhich("rea") !== null) return true;
} catch {
/* fall through to PATH scan */
}
}
const delimiter = process.platform === "win32" ? ";" : ":";
const suffixes = process.platform === "win32" ? ["", ".exe", ".cmd"] : [""];
for (const dir of (process.env.PATH ?? "").split(delimiter)) {
if (!dir) continue;
for (const suffix of suffixes) {
try {
accessSync(join(dir, `rea${suffix}`), fsConstants.X_OK);
return true;
} catch {
/* keep scanning */
}
}
}
return false;
}
export async function checkReaReadiness(
opts: ReaReadinessOptions = {},
): Promise<ReaReadiness> {
const probe = opts.probe ?? defaultReaProbe;
if (await probe()) {
return { status: "ready", remediation: [] };
}
return {
status: "degraded",
reason:
"REA MCP server is not responsive: the `rea` CLI was not found on PATH, so no REA tools are reachable",
remediation: [DOCTOR_CMD, SETUP_CMD],
};
}
+21
View File
@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"skipLibCheck": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"declaration": true,
"sourceMap": true,
"types": ["node", "bun"]
},
"include": [
"packages/*/src/**/*",
"packages/*/test/**/*",
"packs/*/src/**/*",
"packs/*/scripts/**/*"
],
"exclude": ["node_modules", "dist"]
}
+2 -2
View File
@@ -11,8 +11,8 @@
"declarationMap": true,
"sourceMap": true,
"outDir": "dist",
"types": ["node"]
"types": ["node", "bun"]
},
"include": ["packages/*/src/**/*", "packages/*/test/**/*", "packages/opencode-packs/packs/*/src/**/*"],
"include": ["packages/*/src/**/*", "packages/*/test/**/*", "packages/opencode-packs/packs/*/src/**/*", "packages/opencode-packs/packs/*/scripts/**/*", "packages/opencode-packs/packages/*/src/**/*", "packages/opencode-packs/packages/*/test/**/*"],
"exclude": ["node_modules", "dist", "worktrees"]
}