Files
copilotkit__copilotkit/examples/integrations/_parity/lib/manifest.ts
Ran Shem Tov 2bb9f3fdf4 chore(integrations): add _parity tooling + copilotkit-demo-parity skill
Introduce machinery for keeping examples/integrations/* demos aligned to a
single north-star (langgraph-python). Built first so the upcoming
langgraph-js and langgraph-fastapi alignment PRs have a mechanical baseline
to work against instead of manual copy-paste.

- examples/integrations/_parity/manifest.json declares verbatim files,
  tracked package.json keys, and expected agent surface (tool names,
  state keys) per instance plus allowed-divergence lists.
- _parity/sync.ts copies verbatim files + rewrites tracked package.json
  keys from north-star to a target instance. Dry-run supported.
- _parity/verify.ts diffs each instance vs north-star and exits non-zero
  on unexpected drift. Checks verbatim content, tracked keys, canonical
  prompt equality, and agent-surface grep-level presence.
- Canonical prompt at _parity/canonical/PROMPT.md — synced into each
  instance's agent/PROMPT.md on parity:sync.
- Root package.json: pnpm parity:sync, parity:verify, parity:check.
- CI: .github/workflows/integrations_parity.yml runs parity:check on PRs
  touching examples/integrations/**.
- Skill: .claude/skills/copilotkit-demo-parity/SKILL.md teaches agents
  how to drive sync/verify and handle manual-merge zones (agent code,
  api route, Dockerfile).

Does NOT touch the existing instance demos yet. Those alignment commits
follow in the same PR.
2026-05-01 12:31:04 +02:00

97 lines
2.7 KiB
TypeScript

import { readFileSync } from "node:fs";
import { resolve } from "node:path";
export type InstanceRole = "north-star" | "instance";
export interface AgentSurface {
toolNames: string[];
stateKeys: string[];
modelFamily: string;
}
export interface TrackedSurface {
verbatimFiles: string[];
packageJsonPaths: string[];
agentSurface: AgentSurface;
}
export interface InstanceAgent {
language: "python" | "typescript";
runtime: string;
}
export interface Instance {
role: InstanceRole;
agent: InstanceAgent;
allowedDivergence: string[];
packageJsonOverrides: Record<string, string>;
}
export interface Manifest {
version: number;
northStar: string;
canonicalPromptFile: string;
tracked: TrackedSurface;
instances: Record<string, Instance>;
}
export interface ParityRoot {
/** Absolute path to the parity dir, e.g. …/examples/integrations/_parity */
parityDir: string;
/** Absolute path to the integrations root, e.g. …/examples/integrations */
integrationsDir: string;
manifest: Manifest;
}
export function loadManifest(parityDir: string): ParityRoot {
const manifestPath = resolve(parityDir, "manifest.json");
const raw = readFileSync(manifestPath, "utf8");
const parsed = JSON.parse(raw) as Manifest;
validate(parsed, manifestPath);
return {
parityDir,
integrationsDir: resolve(parityDir, ".."),
manifest: parsed,
};
}
function validate(m: Manifest, source: string): void {
const err = (msg: string): never => {
throw new Error(`[parity] invalid manifest at ${source}: ${msg}`);
};
if (m.version !== 1) err(`unsupported version ${m.version}`);
if (!m.northStar) err("missing northStar");
if (!m.instances?.[m.northStar])
err(`northStar '${m.northStar}' not in instances`);
if (m.instances[m.northStar].role !== "north-star")
err(`northStar '${m.northStar}' must have role=north-star`);
const nonNorthStar = Object.entries(m.instances).filter(
([n]) => n !== m.northStar,
);
for (const [name, inst] of nonNorthStar) {
if (inst.role !== "instance")
err(`instance '${name}' must have role=instance`);
}
if (!m.tracked?.verbatimFiles?.length) err("tracked.verbatimFiles empty");
if (!m.tracked?.packageJsonPaths?.length)
err("tracked.packageJsonPaths empty");
if (!m.tracked?.agentSurface?.toolNames?.length)
err("tracked.agentSurface.toolNames empty");
}
export function instanceDir(root: ParityRoot, name: string): string {
return resolve(root.integrationsDir, name);
}
export function northStarDir(root: ParityRoot): string {
return instanceDir(root, root.manifest.northStar);
}
export function listInstances(root: ParityRoot): string[] {
return Object.keys(root.manifest.instances).filter(
(n) => n !== root.manifest.northStar,
);
}