import { z } from "zod"; /** * A target repo's COMPONENTS: the deployable/runnable units its source tree is * divided into, declared as data in `.loopgraph/config.json`. * * WHY THIS IS CONFIG AND NOT INFERENCE. Two features need to say "which unit * does this file belong to": * - the backtest metric (判据 A) reports coverage per partition, because one * global number hides structural gaps — a model can look 36% healthy while * being flatly 0% on every frontend; * - the topology view labels each node by entry type, because a graph of * unlabeled boxes "能看但答不了问题". * Both could be faked by hardcoding `apps/*` + `packages/*`. That would bake * one target repo's layout into an engine whose entire premise (Proposal 010) * is that it carries zero target knowledge — and it would silently mis-partition * any repo shaped differently, which is worse than not partitioning at all. * So: declared, or absent. Never guessed. */ /** * Entry-type vocabulary — CLOSED, and that is the point. * * The plan this implements requires every node to carry an entry type * (平台 API / 用户前端 / 后台进程). An open string would satisfy the schema * while letting a target write five spellings of "worker", and the legend a * reader relies on to interpret the graph degrades back to unlabeled. A closed * set is what makes the annotation mean the same thing in every repo. * * `library` is here for shared packages that are not processes at all: they own * files (so the partition report can attribute commits to them) but are not * topology entry points. Anything that genuinely does not fit is a signal the * vocabulary needs a considered addition, not a free-text escape hatch. */ export declare const COMPONENT_ROLES: readonly ["frontend", "api", "worker", "sandbox", "library"]; export type ComponentRole = (typeof COMPONENT_ROLES)[number]; export declare const Component: z.ZodObject<{ /** Stable id, used as the node id in topology and the partition key in reports. */ id: z.ZodString; /** Human label for views. Falls back to `id` when absent. */ label: z.ZodOptional; /** Entry type — see COMPONENT_ROLES. */ role: z.ZodEnum<["frontend", "api", "worker", "sandbox", "library"]>; /** Repo-relative path prefixes this component owns (e.g. `apps/web`). */ paths: z.ZodArray; /** * What this component is called in OTel — `service.name`/`service` * attribute on its own traces/spans/logs, when it differs from `id` * (e.g. a component id `web-app` whose OTel service name is actually * `webapp`, no hyphen). OPTIONAL and PURELY DECLARATIVE: this engine * never reads or interprets the value itself (it has zero opinion on * OTel, or on any specific observability backend — same posture as * `paths`/`role` having no built-in notion of "npm workspace" or "k8s * deployment"). It exists so that mapping is declared ONCE, here, * instead of living only inside whatever external tool exports a * `--compare-edges` file (see `views/topology-html.ts`'s edge-diff * module doc) — a mapping kept only in an export script has no schema to * keep it honest and drifts silently as ids on either side get renamed. * Any tool that already depends on this engine's `loadComponents` can * read it back out instead of maintaining its own copy. */ otelService: z.ZodOptional; }, "strict", z.ZodTypeAny, { id: string; role: "frontend" | "api" | "worker" | "sandbox" | "library"; paths: string[]; label?: string | undefined; otelService?: string | undefined; }, { id: string; role: "frontend" | "api" | "worker" | "sandbox" | "library"; paths: string[]; label?: string | undefined; otelService?: string | undefined; }>; export type Component = z.infer; export declare const ComponentsConfig: z.ZodArray; /** Entry type — see COMPONENT_ROLES. */ role: z.ZodEnum<["frontend", "api", "worker", "sandbox", "library"]>; /** Repo-relative path prefixes this component owns (e.g. `apps/web`). */ paths: z.ZodArray; /** * What this component is called in OTel — `service.name`/`service` * attribute on its own traces/spans/logs, when it differs from `id` * (e.g. a component id `web-app` whose OTel service name is actually * `webapp`, no hyphen). OPTIONAL and PURELY DECLARATIVE: this engine * never reads or interprets the value itself (it has zero opinion on * OTel, or on any specific observability backend — same posture as * `paths`/`role` having no built-in notion of "npm workspace" or "k8s * deployment"). It exists so that mapping is declared ONCE, here, * instead of living only inside whatever external tool exports a * `--compare-edges` file (see `views/topology-html.ts`'s edge-diff * module doc) — a mapping kept only in an export script has no schema to * keep it honest and drifts silently as ids on either side get renamed. * Any tool that already depends on this engine's `loadComponents` can * read it back out instead of maintaining its own copy. */ otelService: z.ZodOptional; }, "strict", z.ZodTypeAny, { id: string; role: "frontend" | "api" | "worker" | "sandbox" | "library"; paths: string[]; label?: string | undefined; otelService?: string | undefined; }, { id: string; role: "frontend" | "api" | "worker" | "sandbox" | "library"; paths: string[]; label?: string | undefined; otelService?: string | undefined; }>, "many">; export type ComponentsConfig = z.infer; export interface LoadComponentsResult { /** Parsed components, or undefined when none are declared (feature simply off). */ components?: Component[]; /** Set when the config exists but its `components` section is malformed. */ error?: string; } /** * Loads the `components` section of `/.loopgraph/config.json`. * * Absent file, or a file with no `components` key → `{}`: partitioning and * topology node labelling are opt-in, and a target that has not declared its * layout gets the honest degraded output (one "overall" partition) rather than * a guessed one. */ export declare function loadComponents(dir: string): Promise; /** * The component owning a repo-relative file, or `undefined`. * * LONGEST prefix wins, so a nested declaration can carve a sub-tree out of a * broader one (`packages` → a shared library component, `packages/api` → its * own component) without the outer one shadowing it. * * NO TIE-BREAK RULE IS NEEDED, and that is a property of the data rather than * an oversight: two matching prefixes of EQUAL length are necessarily the same * string (both are prefixes of the same path, cut at the same offset), and * `loadComponents` rejects the same path declared twice. So the strict `>` * below can never be reached with a genuine tie, and the result does not depend * on declaration order. */ export declare function componentOf(components: readonly Component[], repoRelativePath: string): Component | undefined; /** Display name for a component: its `label`, or its `id`. */ export declare function componentLabel(component: Component): string; //# sourceMappingURL=components.d.ts.map