/**
* The single source of truth for which packages LLM-generated
* component code may import.
*
* Before this module the allowlist was duplicated — as `startsWith`
* chains, as literal arrays, as hand-written error strings — across
* `adapters/tools.ts`, `check/run-tier0.ts`, `check/type-checker.ts`
* and `validation/component-detailed.ts`. The copies had already
* drifted (one rejected `react-dom`, others allowed it). Every
* import-validation site now derives from {@link isAllowedImport}.
*
* The design system is imported through ONE specifier — `@ggui-ai/design`
* — never the `/primitives`, `/components`, `/compositions`, `/interact`
* subpaths. The barrel re-exports every layer, so the generation LLM
* never has to predict which subpath a component lives in. Subpaths
* still resolve (the predicate allows any `@ggui-ai/design/...`), but
* the prompt and catalog teach the barrel exclusively.
*/
/**
* Static package specifiers generated code may import. A subpath of
* any base (`/...`) is allowed too. Per-contract gadget packages
* are dynamic — pass them to {@link isAllowedImport}.
*/
export const ALLOWED_IMPORT_BASES = [
'react',
'react-dom',
'@ggui-ai/design',
'@ggui-ai/wire',
'@ggui-ai/gadgets',
] as const;
/**
* True if `specifier` is a package generated component code may import:
* one of {@link ALLOWED_IMPORT_BASES} (or a subpath of one), or a
* contract-declared gadget package.
*/
export function isAllowedImport(
specifier: string,
gadgetPackages?: ReadonlySet,
): boolean {
for (const base of ALLOWED_IMPORT_BASES) {
if (specifier === base || specifier.startsWith(`${base}/`)) return true;
}
return gadgetPackages?.has(specifier) ?? false;
}
/**
* Human-readable allowed-import list for error messages shown to the
* generation LLM.
*/
export function describeAllowedImports(): string {
return 'react, @ggui-ai/design, @ggui-ai/wire, @ggui-ai/gadgets, or a gadget package declared on the contract';
}