/** * Authoring-hygiene rules for `DataContract`. Lint rule registry — * warnings only, never thrown by {@link validateContract}; surfaced * by {@link lintContract} so authoring tools can offer "your contract * is technically valid, but here are the polish items" feedback. * * Ships the most universally applicable subset: * * - `LINT_ORPHAN_AGENT_TOOL` — `agentCapabilities.tools[X]` is * declared but never referenced from any `actionSpec[*].nextStep` * or `streamSpec[*].source.tool`. The entry is dead weight — either * wire it up or drop it. (Catches the common "agent dropped a * reference but left the catalog entry behind" drift.) * * - `LINT_MISSING_USAGE` — `agentCapabilities.tools[*]` or * `clientCapabilities.gadgets[*]` lacks a `usage` field. * `usage` is the free-form LLM-targeted prose that bare * `description` lacks — when omitted, the agent's reasoning * loop loses important context-of-use information. * * - `LINT_MISSING_EXAMPLE` — `agentCapabilities.tools[*]` lacks an * `example`. Examples ground the agent's invocation patterns; a * tool without one is harder to use correctly on the first call. * * - `LINT_GADGET_DUPLICATE_EXPORT` — two `clientCapabilities.gadgets[*]` * entries declare the same export name (a `hook` name or a * `component` name). The boilerplate generator emits one import * per export name; a collision is unresolvable in module scope. * Keyed on the export name alone. * * Gadget lints split by timing into two surfaces: * * - **Wire-side** ({@link checkHygiene}, input `DataContract`): * `checkGadgetHookNames` + `checkDuplicateGadgetHooks`. * - **Registry-side** ({@link lintGadgetCatalog}, input * `readonly GadgetDescriptor[]`): permission + immutability + * duplicate-hook + unscoped-package checks, run at registration * time. Some codes are fatal — see {@link FATAL_CATALOG_LINT_CODES}. * * Pure checks; return violations rather than throwing. The wire-side * set is wired into `lintContract` via `phaseHygiene`; consumers that * want strict gates layer their own assertions on top. */ import type { DataContract, GadgetDescriptor } from '../types/data-contract.js'; /** * Stable codes for hygiene rules. Each is a `LINT_*` rather than a * `CTR_*` — the convention: errors are `CTR_*`, warnings are `LINT_*`. * * The gadget lints split into two timing buckets: * * - **Wire-side** (run on a `DataContract` by {@link checkHygiene}): * `LINT_GADGET_UNKNOWN_HOOK`, `LINT_GADGET_DUPLICATE_EXPORT`. * - **Registry-side** (run on an `App.gadgets` catalog at * registration time by {@link lintGadgetCatalog}): * `LINT_GADGET_MISSING_PERMISSION`, `LINT_GADGET_UNKNOWN_PERMISSION`, * `LINT_GADGET_UNSCOPED_PACKAGE`, `LINT_GADGET_IMMUTABLE_MUTATION`, * `LINT_GADGET_DUPLICATE_EXPORT_IN_CATALOG`. */ export declare const LINT_ORPHAN_AGENT_TOOL = "LINT_ORPHAN_AGENT_TOOL"; export declare const LINT_MISSING_USAGE = "LINT_MISSING_USAGE"; export declare const LINT_MISSING_EXAMPLE = "LINT_MISSING_EXAMPLE"; export declare const LINT_GADGET_UNKNOWN_HOOK = "LINT_GADGET_UNKNOWN_HOOK"; export declare const LINT_GADGET_DUPLICATE_EXPORT = "LINT_GADGET_DUPLICATE_EXPORT"; export declare const LINT_CONTRACT_RETIRED_FIELD = "LINT_CONTRACT_RETIRED_FIELD"; export declare const LINT_GADGET_MISSING_PERMISSION = "LINT_GADGET_MISSING_PERMISSION"; export declare const LINT_GADGET_UNKNOWN_PERMISSION = "LINT_GADGET_UNKNOWN_PERMISSION"; export declare const LINT_GADGET_UNSCOPED_PACKAGE = "LINT_GADGET_UNSCOPED_PACKAGE"; export declare const LINT_GADGET_IMMUTABLE_MUTATION = "LINT_GADGET_IMMUTABLE_MUTATION"; export declare const LINT_GADGET_DUPLICATE_EXPORT_IN_CATALOG = "LINT_GADGET_DUPLICATE_EXPORT_IN_CATALOG"; export declare const LINT_GADGET_DUPLICATE_PACKAGE = "LINT_GADGET_DUPLICATE_PACKAGE"; /** * Registry-side lint codes that denote a HARD integrity violation — * registration handlers MUST reject the catalog (not just warn) when * {@link lintGadgetCatalog} emits one of these. The lint function * itself stays pure (returns warnings); severity classification is * the caller's, so this set is the single source of truth for "which * codes are fatal." * * - `LINT_GADGET_IMMUTABLE_MUTATION` — two descriptors share a * `(package, version)` tuple but disagree on `bundleSri`. The * same immutable bundle cannot have two hashes; cached blueprints * keyed on that version would silently break. * - `LINT_GADGET_DUPLICATE_EXPORT_IN_CATALOG` — two descriptors * export the same name (a `hook` name or a `component` name). The * boilerplate generator emits one * `import { } from ''` per export; a name * collision in module scope is unresolvable. */ export declare const FATAL_CATALOG_LINT_CODES: ReadonlySet; /** * Retired top-level `DataContract` field names. The contract schema * is `.passthrough()` at the type system level (forward-compat * hedge), but these specific names denote fields that have a known * replacement in the current protocol. Carrying one of them is a * caller bug — silent pass-through would mask the migration. * * Replacements (kept here so the lint message can teach the fix): * - `libraries` → `clientCapabilities.gadgets` * - `dispatch` → `agentCapabilities.tools` + `actionSpec[*].nextStep` * - `wiredTools` → `agentCapabilities.tools` * - `clientTools` → `clientCapabilities.gadgets` * - `broadcast` → `streamSpec[ch].source` * - `capabilities` → `agentCapabilities` + `clientCapabilities` * * Render-gate handlers re-use this list to hard-reject; surfacing it * here keeps the wire vocabulary single-sourced. */ export declare const RETIRED_CONTRACT_FIELDS: Readonly>; /** * Permission strings the Web Permissions API ratifies, plus the * MCP Apps `_meta.ui.permissions` enum members for host * passthrough. * * Exported as a tuple + literal-union type so * `strictGadgetDescriptorSchema.permission` can use * `z.enum(KNOWN_PERMISSION_NAMES)` for a hard reject at parse time: * typos (`'geolocaiton'`) and unsupported values fail at the wire * boundary instead of being demoted to a soft warning. Forward-compat * additions land via a protocol version bump. */ export declare const KNOWN_PERMISSION_NAMES: readonly ["geolocation", "notifications", "microphone", "camera", "persistent-storage", "midi", "clipboard-read", "clipboard-write", "speaker-selection", "storage-access", "background-sync", "accelerometer", "gyroscope", "magnetometer", "ambient-light-sensor", "screen-wake-lock"]; export type KnownPermissionName = (typeof KNOWN_PERMISSION_NAMES)[number]; /** * Hygiene-rule warning. Internal — surfaced through `ContractIssue` * via the converters in `lint-contract.ts`. Keep the shape minimal: * code + path + message + fixHint cover the rendering needs of the * authoring tools that consume `lintContract` warnings. */ export interface HygieneWarning { readonly code: string; readonly path: string; readonly message: string; readonly fixHint?: string; } /** * Find agentCapabilities.tools entries that are declared but never * referenced from actionSpec or streamSpec. Each orphan is dead * weight — either wire it up or drop it from the catalog. */ export declare function checkOrphanAgentTools(contract: DataContract): HygieneWarning[]; /** * Find `agentCapabilities.tools` entries missing the `usage` field. * `usage` is the LLM-targeted "when / why / by-whom" prose; without it * the agent's reasoning loop loses context-of-use information. * * Scope is `agentCapabilities.tools` ONLY. `clientCapabilities.gadgets` * is intentionally NOT linted here: `GadgetExportUse.usage` is an * OPTIONAL intent-OVERRIDE, and the SPEC-documented canonical wire * form is the bare identity reference `gadgets[][] = {}`. * Render-time resolution inherits the registered descriptor's `usage`, * and the registry-side `lintGadgetCatalog` (via * `strictGadgetExportSchema`) already enforces real teaching text at * registration time. Flagging an empty wire-side use object would * false-positive the documented happy path. */ export declare function checkMissingUsage(contract: DataContract): HygieneWarning[]; /** * Find agentCapabilities.tools entries missing the `example` field. * Examples ground the agent's invocation patterns; a tool without * one is harder to use correctly on the first call. */ export declare function checkMissingExample(contract: DataContract): HygieneWarning[]; /** * Wire-side gadget hook-name lint. For every * `clientCapabilities.gadgets[*]` whose `package` is the first-party * stdlib (`@ggui-ai/gadgets`), the `hook` MUST be one the stdlib * actually exports — catches typos (`useGeoLocation`) + stale * references against a constant catalog. * * Third-party packages (any `package !== DEFAULT_GADGET_PACKAGE`) are * NOT checked here — the lint can't know an operator's own hook * names. The registry-side {@link lintGadgetCatalog} + the render-time * {@link assertGadgetsRegistered} gate cover third-party resolution. * * Permission checks live on the registry-side `lintGadgetCatalog` * (the wire gadget reference carries no `permission` field). This * function is the pure wire-only residue: a constant-catalog * hook-name check. */ export declare function checkGadgetHookNames(contract: DataContract): HygieneWarning[]; /** * Find `clientCapabilities.gadgets` exports that declare the same * export NAME from two different packages. * * The wire is package-keyed, so the same name cannot repeat WITHIN a * package (object-key uniqueness). The hazard is cross-package: two * packages each exporting `useCheckout`. The boilerplate generator * emits one `import { } from ''` per export; two * imports of the same name — from different packages — produce an * unresolvable identifier collision in the generated module scope. * * Keys on the export name alone, matching the render-time hard gate * `assertNoDuplicateGadgetHooks`. Soft mirror of that gate so * authoring tools surface the issue before a render round-trip. */ export declare function checkDuplicateGadgetHooks(contract: DataContract): HygieneWarning[]; /** * Registry-side catalog lint — runs on an `App.gadgets` descriptor * array at registration time (ggui.json load, config push, * `ggui gadget install`). Pure function; returns warnings. The caller * (registration handler) treats any code in * {@link FATAL_CATALOG_LINT_CODES} as a hard reject. * * Checks: * * - `LINT_GADGET_DUPLICATE_EXPORT_IN_CATALOG` (fatal) — two * descriptors export the same name (a `hook` name or a * `component` name). Each export name is unique per app; the * boilerplate's per-export import would collide. * - `LINT_GADGET_IMMUTABLE_MUTATION` (fatal) — two descriptors * carry the same `(package, version)` tuple but different * `bundleSri`. The same immutable bundle cannot have two hashes; * a cached blueprint pinned to that version would break. * - `LINT_GADGET_MISSING_PERMISSION` — a known-permission stdlib * hook (geolocation, camera, …) registered without a * `permission` field. The agent's reasoning loop reads it to * surface "this UI prompts for X." * - `LINT_GADGET_UNKNOWN_PERMISSION` — `permission` set to a value * outside the Web Permissions API set. (The strict registry * schema enum-checks this too; the lint is defence-in-depth for * permissively-parsed catalogs.) * - `LINT_GADGET_UNSCOPED_PACKAGE` — `package` lacks an `@scope/` * prefix. Soft recommendation: scoped names avoid registry * squatting + name collisions. */ export declare function lintGadgetCatalog(descriptors: readonly GadgetDescriptor[]): HygieneWarning[]; /** * Find top-level retired-field carriers on the contract. The schema * is `.passthrough()`, so a stray `libraries`/`dispatch`/`wiredTools`/ * `clientTools`/`broadcast`/`capabilities` slips through silently. The * render-gate hard-rejects these (see * `mcp-server-handlers/.../assert-contract-no-retired-fields.ts`); this * lint surface keeps authoring tools symmetric — show the warning before * the render call so the author can fix it without a server round-trip. */ export declare function checkRetiredContractFields(contract: DataContract): HygieneWarning[]; /** * Run every WIRE-side hygiene rule on a `DataContract`. Aggregates * warnings; order is stable (orphans → usage → example → gadget * hook-names → duplicate-hook) so authoring tools render a predictable * checklist. Retired-field detection is NOT here — it is promoted to an * ERROR phase (`phaseRetired` in lint-contract.ts); the detector * `checkRetiredContractFields` stays exported for the author-time * surface and the render-gate assert. * * Registry-side gadget lints (`lintGadgetCatalog`) are NOT run here: * they need an `App.gadgets` descriptor array, not a contract, and * fire at registration time rather than render time. * Call {@link lintGadgetCatalog} separately at the registration * boundary. */ export declare function checkHygiene(contract: DataContract): HygieneWarning[]; //# sourceMappingURL=hygiene-rules.d.ts.map