@appilots/cli 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import { SourceControlCandidate } from './types/index.js';
2
+ import * as t from '@babel/types';
3
+
1
4
  /**
2
5
  * Core types for the MCP Generator.
3
6
  */
@@ -16,6 +19,7 @@ interface ScreenDescriptor {
16
19
  filePath: string;
17
20
  title?: string;
18
21
  description?: string;
22
+ controlCandidates?: SourceControlCandidate[];
19
23
  components: ComponentDescriptor[];
20
24
  forms: FormDescriptor[];
21
25
  actions: ActionDescriptor[];
@@ -57,6 +61,36 @@ interface ScreenPermissionDescriptor {
57
61
  blockedActions?: string[];
58
62
  isPii?: boolean;
59
63
  }
64
+ /**
65
+ * O que a varredura alcançou — para o relatório do CLI, não para o agente.
66
+ *
67
+ * Mora aqui e NÃO dentro do `MCPDocument`: o documento é contrato com o SDK,
68
+ * a API e o dashboard, e "quantos arquivos eu li" não é dado do app. Todo
69
+ * campo é opcional para quem consome, porque um analisador de plataforma
70
+ * pode não produzir nenhum — o relatório omite a seção em vez de mentir zeros.
71
+ */
72
+ interface AnalysisDiagnostics {
73
+ navigation?: {
74
+ /** Arquivos que o glob de navegação alcançou. */
75
+ filesScanned: number;
76
+ /** Destes, quantos mencionam fábrica de navegador, ParamList, `.Navigator` ou `.Screen`. */
77
+ filesWithEvidence: number;
78
+ /** Fábricas reconhecidas atribuídas a um nome. Candidatos não contam. */
79
+ navigatorsDeclared: number;
80
+ /** Navegadores confirmados por `<X.Navigator>` no JSX. */
81
+ navigatorsWithJsx: number;
82
+ /** Rotas únicas no documento. */
83
+ routes: number;
84
+ /** Destas, quantas resolvem `component=` até um arquivo. */
85
+ routesLinkedToFile: number;
86
+ /** Roteador de arquivo encontrado, quando é o caso. */
87
+ fileBasedRouter?: {
88
+ kind: 'expo-router';
89
+ routeDir: string;
90
+ routes: number;
91
+ };
92
+ };
93
+ }
60
94
  interface NavigationGraph {
61
95
  screens: Record<string, NavigationNode>;
62
96
  initialScreen: string;
@@ -167,6 +201,21 @@ interface ActionDescriptor {
167
201
  effect?: 'read' | 'write' | 'destructive' | string;
168
202
  riskLevel?: 'low' | 'medium' | 'high' | string;
169
203
  requiresConfirmation?: boolean;
204
+ /**
205
+ * How long this action's work takes, in milliseconds, as DECLARED by
206
+ * the app in `registerScreen()`. Read from the source, never inferred:
207
+ * the analyzer can prove a handler awaits something (see
208
+ * `appilotsInferred.isAsyncTrigger`) but never how long that await
209
+ * takes, and the gap between the two is what a wrong constant costs —
210
+ * the SDK stops waiting mid-operation and the agent plans against a
211
+ * loading screen with no controls on it.
212
+ *
213
+ * Carried into the document verbatim and into `waitPolicy.maxMs`,
214
+ * where it overrides the inferred 10s default. The SDK clamps it to
215
+ * its own ceiling; nothing here caps it, because the analyzer has no
216
+ * business overruling what the app said about its own operation.
217
+ */
218
+ asyncBudgetMs?: number;
170
219
  /**
171
220
  * Static-analysis hints derived by the MCP generator from the
172
221
  * action's handler body. Used by the SDK to refine waiting and
@@ -308,11 +357,23 @@ declare class ScreenAnalyzer {
308
357
  private strictScreens;
309
358
  /** Glob patterns that identify screen files in strict mode */
310
359
  private screenPatterns;
360
+ /**
361
+ * Arquivos que uma ROTA monta (`component={…}` resolvido pelo
362
+ * `NavigationAnalyzer`). Passam pelo filtro estrito sem depender de
363
+ * convenção de nome, porque um componente que uma rota monta É uma tela por
364
+ * definição — não é heurística, é o que o app declarou.
365
+ *
366
+ * É isto que resolve o caso `rocketchat`, cujas telas se chamam `*View.tsx`
367
+ * em `app/views/`, e o `coopcycle`, que não tem diretório `screens/` nenhum.
368
+ */
369
+ private routeTargetFiles;
311
370
  /** §D: Count of screens filtered out in strict mode (available after analyze()) */
312
371
  screensFilteredOut: number;
372
+ controlEvidenceFiles: Record<string, SourceControlCandidate[]>;
313
373
  constructor(config: AnalyzerConfig, options?: {
314
374
  strictScreens?: boolean;
315
375
  screenPatterns?: string[];
376
+ routeTargetFiles?: Set<string>;
316
377
  });
317
378
  /** Analyze all screens in the project */
318
379
  analyze(): Promise<ScreenDescriptor[]>;
@@ -446,6 +507,64 @@ declare class ScreenAnalyzer {
446
507
  private extractScreenName;
447
508
  }
448
509
 
510
+ declare class ModuleGraph {
511
+ private readonly asts;
512
+ private readonly resolved;
513
+ /** `@src/*` → `<root>/src/*`, lido do tsconfig do app. */
514
+ private readonly aliases;
515
+ /** `uniswap` → `<repo>/packages/uniswap`, lido do workspace do monorepo. */
516
+ private readonly workspacePackages;
517
+ constructor(rootDir?: string);
518
+ /** AST de um arquivo, memoizada. `null` quando não parseia. */
519
+ parse(file: string): t.File | null;
520
+ /**
521
+ * `./account/Home` a partir de `src/navigation/index.tsx` → caminho absoluto.
522
+ * Só resolve caminho relativo: import de pacote (`@react-navigation/native`)
523
+ * é de terceiro e não tem tela nossa dentro.
524
+ */
525
+ resolve(fromFile: string, spec: string): string | null;
526
+ /** `import`s do arquivo, por nome local. */
527
+ private imports;
528
+ /** `const X = <init>` no topo do arquivo, incluindo `export const`. */
529
+ private topLevelInit;
530
+ /**
531
+ * Onde `name` é DEFINIDO — segue import e reexport de barrel.
532
+ * Devolve o arquivo e o nome sob o qual ele é definido lá.
533
+ */
534
+ resolveBinding(file: string, name: string, hops?: number): {
535
+ file: string;
536
+ name: string;
537
+ } | null;
538
+ /**
539
+ * O valor string de uma expressão de nome de rota, ou `null`.
540
+ *
541
+ * Cobre `"Chat"`, `ROUTES.CHAT`, `ROUTES.ONBOARDING.SPLASH` e `SOME_CONST` —
542
+ * seguindo import quando o objeto vem de outro arquivo. NÃO cobre template
543
+ * com interpolação nem valor calculado, de propósito.
544
+ */
545
+ stringConstant(file: string, node: t.Node | null | undefined, hops?: number): string | null;
546
+ /**
547
+ * O ARQUIVO onde vive o componente de uma rota, ou `null`.
548
+ *
549
+ * Aceita as três formas que o corpus mostrou: identificador
550
+ * (`component={Home}`), membro de barrel (`component={screens.AccountHome}`)
551
+ * e componente embrulhado em HOC (`component={gestureHandlerRootHOC(Chat)}`,
552
+ * que é como o `pocketpal` monta todas as telas do Drawer).
553
+ */
554
+ componentFile(file: string, node: t.Node | null | undefined, hops?: number): string | null;
555
+ /**
556
+ * Os arquivos DO PROJETO que este arquivo renderiza como JSX.
557
+ *
558
+ * `<ChatView …/>` em `ChatScreen.tsx` → `components/ChatView/ChatView.tsx`.
559
+ * Import de pacote devolve `null` no `resolve` e fica de fora: componente de
560
+ * terceiro não tem tela nossa dentro.
561
+ *
562
+ * Ignora `<X.Screen>` e `<X.Navigator>` de propósito — navegação é outro
563
+ * extractor, e um navegador não é conteúdo de tela.
564
+ */
565
+ renderedComponentFiles(file: string): string[];
566
+ }
567
+
449
568
  /**
450
569
  * Analyzes React Navigation configuration to build a navigation graph.
451
570
  */
@@ -455,16 +574,140 @@ declare class NavigationAnalyzer {
455
574
  private navigationInclude;
456
575
  /** Extra glob patterns to exclude from navigation analysis */
457
576
  private navigationExclude;
577
+ /** Exposto para a composição reusar o cache de AST em vez de reparsear a árvore. */
578
+ readonly graph: ModuleGraph;
579
+ /**
580
+ * Rota → arquivo do componente que ela monta. É a única ligação entre o grafo
581
+ * de navegação e a árvore de telas; sem ela as duas metades do documento
582
+ * falam de coisas diferentes. Populada por `analyze()`.
583
+ */
584
+ readonly routeTargets: Map<string, string>;
585
+ /**
586
+ * Arquivos que DECLARAM uma fábrica de navegador.
587
+ *
588
+ * Um navegador aninhado é montado como `component=` de uma rota — no
589
+ * `apps/example-app`, `<Stack.Screen name="Main" component={MainTabNavigator} />`.
590
+ * Promover esse arquivo a tela criaria seis "telas" sem uma única ação, que é
591
+ * exatamente o falso positivo que o corpus existe para não repetir. Um
592
+ * navegador é um contêiner de rotas; a tela está um nível abaixo.
593
+ *
594
+ * Entram aqui os dois lados: o arquivo que CHAMA a fábrica e o arquivo que
595
+ * RENDERIZA `<X.Navigator>`. Não são o mesmo — `bluewallet` declara
596
+ * `DetailViewStack` num arquivo e escreve o JSX em
597
+ * `navigation/DetailViewScreensStack.tsx`, e checar só o primeiro deixava o
598
+ * segundo entrar como tela.
599
+ */
600
+ readonly navigatorFiles: Set<string>;
601
+ /** Preenchido por `analyze()`. Vazio antes disso. */
602
+ diagnostics: NonNullable<AnalysisDiagnostics['navigation']>;
458
603
  constructor(config: AnalyzerConfig, options?: {
459
604
  navigationInclude?: string[];
460
605
  navigationExclude?: string[];
461
606
  });
462
607
  /** Build the full navigation graph */
463
608
  analyze(): Promise<NavigationGraph>;
464
- /** Find all navigation-related files */
609
+ /** Fase 1: as fábricas `create*Navigator()` atribuídas a um nome neste arquivo. */
610
+ private collectDeclarations;
611
+ /**
612
+ * As rotas de `createXNavigator({ screens: { … } })`.
613
+ *
614
+ * Quatro formas no corpus, todas em `rocketchat`:
615
+ * `NewServerView` — shorthand, nome = componente
616
+ * `LoginView: createNativeStackScreen({ screen })` — embrulho da própria lib
617
+ * `SelectListView: SelectListViewScreen` — identificador direto
618
+ * `ChatsStackNavigator: ChatsStack` — navegador aninhado
619
+ *
620
+ * `groups: { G: { screens: { … } } }` também entra: faz parte da API estática
621
+ * e agrupar rotas não muda o que elas são.
622
+ */
623
+ private parseStaticScreens;
624
+ /**
625
+ * Todo arquivo-fonte do projeto — não os que ficam num diretório com o nome
626
+ * certo.
627
+ *
628
+ * POR QUE ISTO MUDOU. A lista anterior era `**\/navigation/**`,
629
+ * `**\/navigator*` e `**\/routes*`, o que fazia da descoberta de navegação uma
630
+ * convenção de caminho. Medido contra 20 apps React Native de terceiros
631
+ * (`qa/eval-corpus`), o filtro errava por motivos que nada têm a ver com o
632
+ * app não ter navegação:
633
+ *
634
+ * - `pocketpal` declara 4 navegadores em `App.tsx` e dentro de `src/screens/`;
635
+ * - `comapeo` usa `src/frontend/Navigation/` — `N` maiúsculo, e o glob é
636
+ * sensível a caixa;
637
+ * - `abacus` usa `src/routes/index.tsx` — `routes` é o DIRETÓRIO, e o glob
638
+ * pedia um arquivo chamado `routes*`;
639
+ * - `discourse` declara em `js/Discourse.js` — o glob só aceitava `.ts`/`.tsx`;
640
+ * - `rainbow` tem 11 arquivos com navegador e o glob alcançava 1.
641
+ *
642
+ * Varrer tudo não custa uma varredura nova: `ReactNativePlatformAnalyzer` já
643
+ * globa e parseia a árvore inteira para `ComponentAnalyzer` e `FormAnalyzer`.
644
+ * O que segura o custo aqui é o portão por evidência em `analyze()`, que só
645
+ * parseia arquivo cujo texto menciona uma fábrica de navegador.
646
+ *
647
+ * Os globs antigos continuam na lista: se um projeto restringir `include`, o
648
+ * que era encontrado antes continua sendo.
649
+ */
465
650
  private findNavigationFiles;
466
- /** Parse navigator definitions from a file */
467
- private parseNavigators;
651
+ /**
652
+ * Fase 2: o JSX `<X.Navigator>` deste arquivo, ligado à declaração de `X` —
653
+ * que pode estar aqui ou em qualquer arquivo que este importe.
654
+ */
655
+ private parseNavigatorUsages;
656
+ /**
657
+ * Rotas declaradas FORA de qualquer `<X.Navigator>`.
658
+ *
659
+ * O CASO. A fase 2 desce a partir do `<X.Navigator>` e lê as `<X.Screen>`
660
+ * que estão DENTRO dele. Dois alvos do corpus não escrevem assim, e entre os
661
+ * dois são 171 rotas invisíveis:
662
+ *
663
+ * `bluesky` — `function commonScreens(Stack: typeof Flat) { return (<>
664
+ * <Stack.Screen name="NotFound" … /> … </>) }`, chamada de
665
+ * dentro de seis navegadores diferentes. 70 rotas.
666
+ * `comapeo` — `export const createAppScreens = ({intl}) => (<>
667
+ * <RootStack.Group><RootStack.Screen … /></RootStack.Group></>)`,
668
+ * num arquivo sem `<RootStack.Navigator>` nenhum. 101 rotas.
669
+ *
670
+ * A REGRA. Uma `<X.Screen name="…">` sem `<Y.Navigator>` ancestral é rota do
671
+ * navegador ao qual `X` se resolve. Não há palpite em jogo: o nome da rota é
672
+ * literal do fonte (ou constante que o resolvedor segue), e `X` precisa
673
+ * chegar a uma declaração de navegador que já existe. O que não resolve não
674
+ * vira nada.
675
+ *
676
+ * O ancestral é o que evita contar duas vezes — dentro do `<X.Navigator>` a
677
+ * fase 2 já leu, e somar aqui duplicaria cada rota do corpus inteiro.
678
+ *
679
+ * DUAS FORMAS DE RESOLVER `X`, e a segunda é o que o `bluesky` exige. A
680
+ * primeira é a de sempre (declaração local, ou `import` seguido até a
681
+ * origem) e resolve o `comapeo`. A segunda lê a ANOTAÇÃO DE TIPO do
682
+ * parâmetro: em `commonScreens(Stack: typeof Flat)`, quem diz que `Stack` é
683
+ * o navegador `Flat` é o próprio app, no fonte.
684
+ *
685
+ * O QUE ISTO NÃO FAZ. As 70 do `bluesky` ficam atribuídas a `Flat` — que as
686
+ * contém de fato (`{commonScreens(Flat, numUnread)}`) — e não aos outros
687
+ * cinco stacks que também chamam a mesma função. Seguir os seis pontos de
688
+ * chamada é análise interprocedural, e o ganho seria só de atribuição: o nome
689
+ * da rota e o arquivo da tela, que é o que o agente usa, já saem certos.
690
+ */
691
+ private parseDetachedScreens;
692
+ /**
693
+ * De um `X` usado como `<X.Screen>` até a declaração do navegador.
694
+ *
695
+ * Além do caminho normal — declaração local ou `import` seguido até a origem
696
+ * — aceita `X` como PARÂMETRO anotado com `typeof Y`. É o idioma do
697
+ * `bluesky`, e a anotação é declaração do app: nada aqui é inferido do nome.
698
+ */
699
+ private resolveDetachedNavigator;
700
+ /**
701
+ * De `<X.Navigator>` até a declaração de `X`.
702
+ *
703
+ * Primeiro no próprio arquivo; se não estiver, segue o `import` até onde `X`
704
+ * é definido. Resolver pelo import é o que torna a junção entre arquivos
705
+ * segura: dois `Stack` de arquivos diferentes nunca colidem, porque a chave
706
+ * é o arquivo de DEFINIÇÃO.
707
+ */
708
+ private resolveNavigator;
709
+ /** O valor string de um atributo JSX, resolvendo constante importada. */
710
+ private attributeString;
468
711
  /**
469
712
  * Is this JSX element `<navigatorVarName.MEMBER …>`?
470
713
  */
@@ -487,6 +730,14 @@ declare class NavigationAnalyzer {
487
730
  private collectScreenElements;
488
731
  /** Extract screens from a navigator JSX element */
489
732
  private extractScreensFromNavigator;
733
+ /**
734
+ * Uma `<X.Screen>` isolada até a rota que ela declara.
735
+ *
736
+ * Separado de `extractScreensFromNavigator` porque a MESMA leitura serve para
737
+ * a `<X.Screen>` que mora dentro do `<X.Navigator>` e para a que mora fora
738
+ * dele — só a forma de chegar até o elemento muda.
739
+ */
740
+ private parseScreenElement;
490
741
  /** Extract string attribute value from JSX attributes */
491
742
  private extractAttributeValue;
492
743
  /** Parse TypeScript type exports (ParamList types) */
@@ -565,12 +816,18 @@ interface PlatformAnalyzerOptions {
565
816
  navigationExclude?: string[];
566
817
  }
567
818
  interface PlatformAnalyzerResult {
819
+ controlEvidenceFiles?: Record<string, SourceControlCandidate[]>;
568
820
  screens: ScreenDescriptor[];
569
821
  navigation: NavigationGraph;
570
822
  /** Number of source files matched by glob (before screen filtering). */
571
823
  analyzedFiles: number;
572
824
  /** Number of files excluded by strict screen filtering, when applicable. */
573
825
  screensFilteredOut?: number;
826
+ /**
827
+ * O que a varredura alcançou. Opcional: o analisador web ainda não produz,
828
+ * e o relatório do CLI degrada omitindo a seção em vez de mentir zeros.
829
+ */
830
+ diagnostics?: AnalysisDiagnostics;
574
831
  }
575
832
  /**
576
833
  * Seam `MCPGenerator` consumes to turn a project's source tree into
@@ -907,6 +1164,14 @@ interface MCPOutput {
907
1164
  filePath: string;
908
1165
  format: 'json';
909
1166
  checksum: string;
1167
+ /**
1168
+ * O que a varredura alcançou, para o relatório do CLI.
1169
+ *
1170
+ * Fica FORA do `MCPDocument` de propósito: o documento é contrato com o
1171
+ * SDK, a API e o dashboard, e diagnóstico não é dado do app. Opcional
1172
+ * porque o analisador web não produz — quem consome trata a ausência.
1173
+ */
1174
+ diagnostics?: AnalysisDiagnostics;
910
1175
  }
911
1176
 
912
1177
  /**
@@ -1069,6 +1334,14 @@ interface AppilotsConfig {
1069
1334
  manifestPath?: string;
1070
1335
  /** `appilots eval` settings — see docs: Testing your agent between releases. */
1071
1336
  eval?: EvalConfig;
1337
+ /** Knowledge base / RAG configuration — see docs: Knowledge base. */
1338
+ knowledge?: KnowledgeConfig;
1339
+ }
1340
+ interface KnowledgeConfig {
1341
+ /** Glob patterns for documentation files to index (e.g. "docs/*.md"). */
1342
+ sources?: string[];
1343
+ /** Glob patterns to exclude from indexing. */
1344
+ exclude?: string[];
1072
1345
  }
1073
1346
  interface EvalConfig {
1074
1347
  /** Directory of `*.json` scenario files. Default: `<outputDir>/scenarios`. */
@@ -1117,7 +1390,20 @@ declare function getEnvOverrides(env?: NodeJS.ProcessEnv): EnvOverrides;
1117
1390
  * @returns AppilotsConfig if a file or APPILOTS_API_KEY exists, null otherwise
1118
1391
  * @throws when the file is unparseable or the merged config fails validation
1119
1392
  */
1120
- declare function loadConfig(onWarn?: (message: string) => void): AppilotsConfig | null;
1393
+ interface LoadConfigOptions {
1394
+ /**
1395
+ * `false` quando o comando não fala com o servidor.
1396
+ *
1397
+ * `generate` é local: não precisa de `apiKey` e não deveria ser impedido por
1398
+ * ela faltar. Com `requireApiKey: true` (o padrão), um `.appilotsrc` que só
1399
+ * declara `screenPatterns` derruba a validação, `generate` engole o erro e
1400
+ * IGNORA a config inteira — o comentário dele dizia "não pode bloquear" e o
1401
+ * efeito real era "não pode ser lida". O resultado prático: `generate` e
1402
+ * `sync` produzem documentos diferentes para o mesmo projeto.
1403
+ */
1404
+ requireApiKey?: boolean;
1405
+ }
1406
+ declare function loadConfig(onWarn?: (message: string) => void, options?: LoadConfigOptions): AppilotsConfig | null;
1121
1407
  /**
1122
1408
  * Saves configuration to .appilotsrc in the given directory
1123
1409
  *
@@ -1245,6 +1531,21 @@ interface StatusResult {
1245
1531
  };
1246
1532
  error?: string;
1247
1533
  }
1534
+ /**
1535
+ * Result from a knowledge sync operation
1536
+ */
1537
+ interface KnowledgeSyncResult {
1538
+ success: boolean;
1539
+ uploaded: number;
1540
+ skipped: number;
1541
+ errors: number;
1542
+ error?: string;
1543
+ details?: Array<{
1544
+ filename: string;
1545
+ status: 'uploaded' | 'skipped' | 'error';
1546
+ error?: string;
1547
+ }>;
1548
+ }
1248
1549
  /**
1249
1550
  * Appilots API client configuration
1250
1551
  */
@@ -1307,6 +1608,18 @@ declare class AppilotsAPIClient {
1307
1608
  * EVAL_MAX_SCENARIOS_PER_RUN (currently 20) as a cost guard.
1308
1609
  */
1309
1610
  evalRun(scenarios: EvalScenarioRequest[]): Promise<EvalRunResult>;
1611
+ /**
1612
+ * Syncs knowledge documents with the Appilots backend.
1613
+ *
1614
+ * @param documents Array of documents to sync (filename, base64 content, mimeType, checksum)
1615
+ * @returns KnowledgeSyncResult with counts of uploaded, skipped, and errored docs
1616
+ */
1617
+ knowledgeSync(documents: Array<{
1618
+ filename: string;
1619
+ content: string;
1620
+ mimeType: string;
1621
+ checksum: string;
1622
+ }>): Promise<KnowledgeSyncResult>;
1310
1623
  /**
1311
1624
  * Checks if the Appilots API server is healthy
1312
1625
  *
@@ -1322,6 +1635,6 @@ declare class AppilotsAPIClient {
1322
1635
  * banner. Kept identical to package.json by the release flow;
1323
1636
  * `version.test.ts` fails if the two ever disagree.
1324
1637
  */
1325
- declare const CLI_VERSION = "0.11.0";
1638
+ declare const CLI_VERSION = "0.13.0";
1326
1639
 
1327
1640
  export { type ActionDescriptor, type AnalyzerConfig, AppilotsAPIClient, type AppilotsConfig, type AppilotsManifest, CLI_VERSION, ComponentAnalyzer, type ComponentDescriptor, DEFAULT_MANIFEST_FILENAME, DEFAULT_WEB_SCREEN_PATTERNS, type EnvOverrides, type FlowDescriptor, type FlowStepDescriptor, FormAnalyzer, type FormDescriptor, type FormFieldDescriptor, GenericPlatformAnalyzer, type LoadManifestResult, type LocatorDescriptor, type MCPDocument, MCPGenerator, type MCPGeneratorConfig, type MCPGeneratorOptions, type MCPOutput, type MetadataLintWarning, NavigationAnalyzer, type NavigationGraph, type NavigationNode, type NavigatorDescriptor, type ParamDescriptor, type PlatformAnalyzer, type PlatformAnalyzerOptions, type PlatformAnalyzerResult, ReactNativePlatformAnalyzer, ReactWebPlatformAnalyzer, type ScreenAgentHints, ScreenAnalyzer, type ScreenDescriptor, type SignalDescriptor, type StatusResult, type SyncResult, type TargetDescriptor, type WaitPolicyDescriptor, WebNavigationAnalyzer, type WebNavigationResult, type WebRoute, WebScreenAnalyzer, formatMetadataWarnings, getConfigPath, getEnvOverrides, lintActionMetadata, loadConfig, loadManifest, mergeManifestNavigation, mergeManifestScreens, resolvePathToScreen, saveConfig, screenNameFromPath, validateConfig };