mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
2bb9f3fdf4
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.
78 lines
2.2 KiB
TypeScript
78 lines
2.2 KiB
TypeScript
export type Severity = "ok" | "warn" | "error";
|
|
|
|
export interface DriftItem {
|
|
severity: Severity;
|
|
instance: string;
|
|
kind:
|
|
| "verbatim-file"
|
|
| "package-json"
|
|
| "agent-tool"
|
|
| "agent-state"
|
|
| "prompt"
|
|
| "missing-instance";
|
|
subject: string;
|
|
detail?: string;
|
|
expected?: string;
|
|
actual?: string;
|
|
}
|
|
|
|
export interface Report {
|
|
instance: string;
|
|
items: DriftItem[];
|
|
}
|
|
|
|
export function hasErrors(reports: Report[]): boolean {
|
|
return reports.some((r) => r.items.some((i) => i.severity === "error"));
|
|
}
|
|
|
|
const RESET = "\x1b[0m";
|
|
const RED = "\x1b[31m";
|
|
const YEL = "\x1b[33m";
|
|
const GRN = "\x1b[32m";
|
|
const BOLD = "\x1b[1m";
|
|
const DIM = "\x1b[2m";
|
|
|
|
function color(enabled: boolean, code: string, s: string): string {
|
|
return enabled ? `${code}${s}${RESET}` : s;
|
|
}
|
|
|
|
export function printReports(reports: Report[], useColor = true): void {
|
|
for (const r of reports) {
|
|
const errors = r.items.filter((i) => i.severity === "error");
|
|
const warns = r.items.filter((i) => i.severity === "warn");
|
|
const oks = r.items.filter((i) => i.severity === "ok");
|
|
|
|
const header = color(
|
|
useColor,
|
|
BOLD,
|
|
`\n${r.instance} — ${oks.length} ok, ${warns.length} warn, ${errors.length} error`,
|
|
);
|
|
process.stdout.write(header + "\n");
|
|
|
|
for (const item of [...errors, ...warns]) {
|
|
const tag =
|
|
item.severity === "error"
|
|
? color(useColor, RED, " ✗")
|
|
: color(useColor, YEL, " !");
|
|
const kindStr = color(useColor, DIM, `[${item.kind}]`);
|
|
process.stdout.write(`${tag} ${kindStr} ${item.subject}\n`);
|
|
if (item.detail) process.stdout.write(` ${item.detail}\n`);
|
|
if (item.expected !== undefined || item.actual !== undefined) {
|
|
if (item.expected !== undefined)
|
|
process.stdout.write(` expected: ${truncate(item.expected)}\n`);
|
|
if (item.actual !== undefined)
|
|
process.stdout.write(` actual: ${truncate(item.actual)}\n`);
|
|
}
|
|
}
|
|
|
|
if (errors.length === 0 && warns.length === 0) {
|
|
process.stdout.write(color(useColor, GRN, " ✓ no drift\n"));
|
|
}
|
|
}
|
|
}
|
|
|
|
function truncate(s: string, max = 120): string {
|
|
if (s.length <= max) return s;
|
|
return s.slice(0, max) + "…";
|
|
}
|