export const DESCRIPTOR_VERSION = 6 as const; export interface ModuleSourceRef { readonly path: string; readonly sourceHash: string; } export interface ToolchainRef { readonly bundler: { readonly name: string; readonly version: string; }; readonly executor: { readonly name: string; readonly version: string; }; readonly target: string; } export interface IntegrationDecl { readonly slug: string; readonly mode: "one" | "many"; readonly description?: string; } export interface ToolDescriptor { readonly name: string; readonly sourcePath: string; readonly bundleKey: string; readonly source: ModuleSourceRef; readonly description: string; readonly integrations: Readonly>; readonly inputSchema?: unknown; readonly outputSchema?: unknown; readonly annotations?: { readonly readOnly?: boolean; readonly destructive?: boolean; readonly requiresApproval?: boolean; }; } export interface DeferredDescriptor { readonly path: string; readonly reason: "not supported yet"; } export interface AppDescriptor { readonly version: typeof DESCRIPTOR_VERSION; readonly app: string; readonly sourceRef: string; readonly publishedAt: number; readonly toolchain: ToolchainRef; readonly tools: readonly ToolDescriptor[]; readonly workflows: readonly DeferredDescriptor[]; readonly ui: readonly DeferredDescriptor[]; readonly skills: readonly DeferredDescriptor[]; readonly skipped: readonly DeferredDescriptor[]; } export const stableStringify = (value: unknown): string => { const seen = new WeakSet(); const walk = (v: unknown): unknown => { if (v === null || typeof v !== "object") return v; if (seen.has(v)) return "[Circular]"; seen.add(v); if (Array.isArray(v)) return v.map(walk); const out: Record = {}; for (const key of Object.keys(v as Record).sort()) { const inner = (v as Record)[key]; if (inner !== undefined) out[key] = walk(inner); } return out; }; return JSON.stringify(walk(value)); };