@vyuhlabs/dxkit 2.18.1 → 2.20.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.
Files changed (68) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/dist/analyzers/flow/csv.d.ts +17 -0
  3. package/dist/analyzers/flow/csv.d.ts.map +1 -0
  4. package/dist/analyzers/flow/csv.js +64 -0
  5. package/dist/analyzers/flow/csv.js.map +1 -0
  6. package/dist/analyzers/flow/extract.d.ts +64 -0
  7. package/dist/analyzers/flow/extract.d.ts.map +1 -0
  8. package/dist/analyzers/flow/extract.js +254 -0
  9. package/dist/analyzers/flow/extract.js.map +1 -0
  10. package/dist/analyzers/flow/gather.d.ts +24 -0
  11. package/dist/analyzers/flow/gather.d.ts.map +1 -0
  12. package/dist/analyzers/flow/gather.js +50 -0
  13. package/dist/analyzers/flow/gather.js.map +1 -0
  14. package/dist/analyzers/flow/model.d.ts +63 -0
  15. package/dist/analyzers/flow/model.d.ts.map +1 -0
  16. package/dist/analyzers/flow/model.js +78 -0
  17. package/dist/analyzers/flow/model.js.map +1 -0
  18. package/dist/analyzers/flow/normalize.d.ts +76 -0
  19. package/dist/analyzers/flow/normalize.d.ts.map +1 -0
  20. package/dist/analyzers/flow/normalize.js +133 -0
  21. package/dist/analyzers/flow/normalize.js.map +1 -0
  22. package/dist/analyzers/flow/spec-source.d.ts +44 -0
  23. package/dist/analyzers/flow/spec-source.d.ts.map +1 -0
  24. package/dist/analyzers/flow/spec-source.js +75 -0
  25. package/dist/analyzers/flow/spec-source.js.map +1 -0
  26. package/dist/ast/parse.d.ts +71 -0
  27. package/dist/ast/parse.d.ts.map +1 -0
  28. package/dist/ast/parse.js +220 -0
  29. package/dist/ast/parse.js.map +1 -0
  30. package/dist/cli.d.ts.map +1 -1
  31. package/dist/cli.js +46 -0
  32. package/dist/cli.js.map +1 -1
  33. package/dist/dashboard/graph-adapter.d.ts.map +1 -1
  34. package/dist/dashboard/graph-adapter.js +1 -4
  35. package/dist/dashboard/graph-adapter.js.map +1 -1
  36. package/dist/explore/flow-graph.d.ts +62 -0
  37. package/dist/explore/flow-graph.d.ts.map +1 -0
  38. package/dist/explore/flow-graph.js +202 -0
  39. package/dist/explore/flow-graph.js.map +1 -0
  40. package/dist/explore/flow-view.d.ts +33 -0
  41. package/dist/explore/flow-view.d.ts.map +1 -0
  42. package/dist/explore/flow-view.js +85 -0
  43. package/dist/explore/flow-view.js.map +1 -0
  44. package/dist/explore/load.d.ts.map +1 -1
  45. package/dist/explore/load.js +16 -3
  46. package/dist/explore/load.js.map +1 -1
  47. package/dist/explore/queries.d.ts +92 -1
  48. package/dist/explore/queries.d.ts.map +1 -1
  49. package/dist/explore/queries.js +142 -0
  50. package/dist/explore/queries.js.map +1 -1
  51. package/dist/explore/types.d.ts +56 -3
  52. package/dist/explore/types.d.ts.map +1 -1
  53. package/dist/explore/types.js +10 -2
  54. package/dist/explore/types.js.map +1 -1
  55. package/dist/flow-cli.d.ts +38 -0
  56. package/dist/flow-cli.d.ts.map +1 -0
  57. package/dist/flow-cli.js +200 -0
  58. package/dist/flow-cli.js.map +1 -0
  59. package/dist/languages/index.d.ts +15 -2
  60. package/dist/languages/index.d.ts.map +1 -1
  61. package/dist/languages/index.js +18 -0
  62. package/dist/languages/index.js.map +1 -1
  63. package/dist/languages/types.d.ts +106 -0
  64. package/dist/languages/types.d.ts.map +1 -1
  65. package/dist/languages/typescript.d.ts.map +1 -1
  66. package/dist/languages/typescript.js +32 -0
  67. package/dist/languages/typescript.js.map +1 -1
  68. package/package.json +4 -2
package/CHANGELOG.md CHANGED
@@ -7,6 +7,61 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.20.0] - 2026-07-01
11
+
12
+ ### Added — native flow map + blast radius (`flow`, `flow trace`)
13
+
14
+ The Flow feature's second slice makes UI→API traceability a first-class part of
15
+ the code graph, so a change's cross-boundary blast radius is a query, not a
16
+ guess.
17
+
18
+ - **Graph schema v2 — the endpoint overlay.** `graph.json` now carries
19
+ `http-endpoint` nodes (one per served `(method, path)`) and `calls-endpoint`
20
+ edges (a UI call site → the endpoint it hits) — the cross-boundary join the
21
+ structural graph could not previously express. The overlay is purely additive:
22
+ a v1 artifact migrates forward to an empty overlay, and every pre-flow query is
23
+ untouched. The consuming call site's coordinates ride on the edge, so the map
24
+ works even where graphify never ran (a pure-frontend repo) — the flow layer
25
+ stays graphify-independent.
26
+ - **`vyuh-dxkit flow`** writes the overlay and prints every endpoint with its
27
+ consuming UI surfaces, plus the served-but-unconsumed set (a dead-route or
28
+ cross-repo-consumer candidate — surfaced, not flagged as a defect).
29
+ - **`vyuh-dxkit flow trace "<METHOD> <path>"`** shows one endpoint's handler,
30
+ every UI call site, and the change blast radius — direct consumers extended
31
+ transitively through the structural call graph.
32
+ - Both take `--json`. New pure queries (`endpointCallers`, `flowTrace`,
33
+ `flowBlastRadius`, `flowMapQuery`) live in the canonical query module; the
34
+ overlay is regenerated each run (never accumulated).
35
+
36
+ ### Added — advisory file-size budget in the pre-commit slop check
37
+
38
+ A warn-only, diff-scoped 500-LoC budget nudges when a changed source file
39
+ sprawls, exempting the modules that are large by architectural mandate (language
40
+ packs, the canonical query/registry modules, the CLI dispatch). It never blocks.
41
+
42
+ ## [2.19.0] - 2026-06-30
43
+
44
+ ### Added — application-flow extraction (`flow extract`)
45
+
46
+ `vyuh-dxkit flow extract` statically maps a frontend's HTTP calls to a backend's
47
+ routes and writes the result as CSVs. It is the first slice of the Flow feature
48
+ (UI to API traceability).
49
+
50
+ - **AST-based, not regex.** A new in-process, graphify-independent tree-sitter
51
+ layer (`src/ast/`, web-tree-sitter/wasm) parses source; a per-language
52
+ `httpFlow` descriptor declares which constructs are HTTP clients and routes,
53
+ so no framework literal is hardcoded in the analyzer.
54
+ - **Both sides plus the join.** It extracts outbound client calls
55
+ (fetch/axios/wrappers) and inbound routes (LoopBack/NestJS decorators, Express
56
+ app/router), canonicalizes URLs and route paths to a common shape, and binds
57
+ each call to the route it targets with a confidence score.
58
+ - **OpenAPI when present.** An existing OpenAPI spec is consumed as the
59
+ authoritative served side (`--specs`), unioned with static extraction (spec
60
+ for authority, static for recall, since generated specs are often incomplete).
61
+ - **Usage:** `flow extract [--frontend <dir>] [--backend <dir>] [--specs a.json,b.json] [--out <dir>]`;
62
+ writes `api_calls.csv`, `routes.csv`, `api_route_mapping.csv`. TypeScript and
63
+ JavaScript in this release; more languages follow.
64
+
10
65
  ## [2.18.1] - 2026-06-23
11
66
 
12
67
  ### Fixed — next-step hints now use a resolvable invocation
@@ -0,0 +1,17 @@
1
+ /**
2
+ * CSV renderers over a `FlowModel` — the parity output surface (mirrors the
3
+ * reference tool's `api_data` / `controller_data` / `api_controller_mapping`
4
+ * CSVs). Pure: a model in, CSV text out. CSV is one renderer among several
5
+ * (the graph + the gate consume the same model), so nothing analytical lives
6
+ * here — only serialization.
7
+ */
8
+ import type { FlowModel } from './model';
9
+ /** Outbound HTTP calls (the consumed side). */
10
+ export declare function callsCsv(model: FlowModel): string;
11
+ /** Served routes (the served side), from source extraction and/or a spec. */
12
+ export declare function routesCsv(model: FlowModel): string;
13
+ /** The join: each client call mapped to the route it targets (or not). */
14
+ export declare function mappingCsv(model: FlowModel): string;
15
+ /** The full parity CSV set, keyed by output filename. */
16
+ export declare function flowCsvFiles(model: FlowModel): Record<string, string>;
17
+ //# sourceMappingURL=csv.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csv.d.ts","sourceRoot":"","sources":["../../../src/analyzers/flow/csv.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAczC,+CAA+C;AAC/C,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAKjD;AAED,6EAA6E;AAC7E,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAKlD;AAED,0EAA0E;AAC1E,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CA2BnD;AAED,yDAAyD;AACzD,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMrE"}
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ /**
3
+ * CSV renderers over a `FlowModel` — the parity output surface (mirrors the
4
+ * reference tool's `api_data` / `controller_data` / `api_controller_mapping`
5
+ * CSVs). Pure: a model in, CSV text out. CSV is one renderer among several
6
+ * (the graph + the gate consume the same model), so nothing analytical lives
7
+ * here — only serialization.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.callsCsv = callsCsv;
11
+ exports.routesCsv = routesCsv;
12
+ exports.mappingCsv = mappingCsv;
13
+ exports.flowCsvFiles = flowCsvFiles;
14
+ /** Quote every field (CSV-safe: doubles embedded quotes), join a row. */
15
+ function row(fields) {
16
+ return fields.map((f) => `"${String(f ?? '').replace(/"/g, '""')}"`).join(',');
17
+ }
18
+ function toCsv(header, rows) {
19
+ return [row(header), ...rows.map(row)].join('\n') + '\n';
20
+ }
21
+ /** Outbound HTTP calls (the consumed side). */
22
+ function callsCsv(model) {
23
+ return toCsv(['method', 'path', 'raw_url', 'receiver', 'file', 'line'], model.calls.map((c) => [c.method, c.path, c.rawUrl, c.receiver, c.file, c.line]));
24
+ }
25
+ /** Served routes (the served side), from source extraction and/or a spec. */
26
+ function routesCsv(model) {
27
+ return toCsv(['method', 'path', 'via', 'handler', 'file', 'line'], // arch-shape-ok: CSV column name, not a role label
28
+ model.routes.map((r) => [r.method, r.path, r.via, r.handler, r.file, r.line]));
29
+ }
30
+ /** The join: each client call mapped to the route it targets (or not). */
31
+ function mappingCsv(model) {
32
+ return toCsv([
33
+ 'call_method',
34
+ 'call_path',
35
+ 'call_file',
36
+ 'call_line',
37
+ 'reason',
38
+ 'confidence',
39
+ 'route_path',
40
+ 'route_handler',
41
+ 'route_via',
42
+ 'route_file',
43
+ ], model.bindings.map((b) => [
44
+ b.call.method,
45
+ b.call.path,
46
+ b.call.file,
47
+ b.call.line,
48
+ b.reason,
49
+ b.confidence,
50
+ b.route?.path ?? '',
51
+ b.route?.handler ?? '',
52
+ b.route?.via ?? '',
53
+ b.route?.file ?? '',
54
+ ]));
55
+ }
56
+ /** The full parity CSV set, keyed by output filename. */
57
+ function flowCsvFiles(model) {
58
+ return {
59
+ 'api_calls.csv': callsCsv(model),
60
+ 'routes.csv': routesCsv(model),
61
+ 'api_route_mapping.csv': mappingCsv(model),
62
+ };
63
+ }
64
+ //# sourceMappingURL=csv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csv.js","sourceRoot":"","sources":["../../../src/analyzers/flow/csv.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;AAiBH,4BAKC;AAGD,8BAKC;AAGD,gCA2BC;AAGD,oCAMC;AAjED,yEAAyE;AACzE,SAAS,GAAG,CAAC,MAA6C;IACxD,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACjF,CAAC;AAED,SAAS,KAAK,CACZ,MAAyB,EACzB,IAA0D;IAE1D,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAC3D,CAAC;AAED,+CAA+C;AAC/C,SAAgB,QAAQ,CAAC,KAAgB;IACvC,OAAO,KAAK,CACV,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,EACzD,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CACjF,CAAC;AACJ,CAAC;AAED,6EAA6E;AAC7E,SAAgB,SAAS,CAAC,KAAgB;IACxC,OAAO,KAAK,CACV,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,mDAAmD;IACzG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAC9E,CAAC;AACJ,CAAC;AAED,0EAA0E;AAC1E,SAAgB,UAAU,CAAC,KAAgB;IACzC,OAAO,KAAK,CACV;QACE,aAAa;QACb,WAAW;QACX,WAAW;QACX,WAAW;QACX,QAAQ;QACR,YAAY;QACZ,YAAY;QACZ,eAAe;QACf,WAAW;QACX,YAAY;KACb,EACD,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QACxB,CAAC,CAAC,IAAI,CAAC,MAAM;QACb,CAAC,CAAC,IAAI,CAAC,IAAI;QACX,CAAC,CAAC,IAAI,CAAC,IAAI;QACX,CAAC,CAAC,IAAI,CAAC,IAAI;QACX,CAAC,CAAC,MAAM;QACR,CAAC,CAAC,UAAU;QACZ,CAAC,CAAC,KAAK,EAAE,IAAI,IAAI,EAAE;QACnB,CAAC,CAAC,KAAK,EAAE,OAAO,IAAI,EAAE;QACtB,CAAC,CAAC,KAAK,EAAE,GAAG,IAAI,EAAE;QAClB,CAAC,CAAC,KAAK,EAAE,IAAI,IAAI,EAAE;KACpB,CAAC,CACH,CAAC;AACJ,CAAC;AAED,yDAAyD;AACzD,SAAgB,YAAY,CAAC,KAAgB;IAC3C,OAAO;QACL,eAAe,EAAE,QAAQ,CAAC,KAAK,CAAC;QAChC,YAAY,EAAE,SAAS,CAAC,KAAK,CAAC;QAC9B,uBAAuB,EAAE,UAAU,CAAC,KAAK,CAAC;KAC3C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Flow extraction — the AST pass that turns source into the two sides of an
3
+ * HTTP integration: outbound client calls (CONSUMED) and inbound route
4
+ * declarations (SERVED).
5
+ *
6
+ * It is the cross-cutting consumer of three seams and hardcodes none of them:
7
+ * - the canonical AST layer (`src/ast/`) for parsing — graphify-independent,
8
+ * and registered as its own concern, never folded into graphify's pass;
9
+ * - each pack's `httpFlow` descriptor (Rule 6) for WHICH constructs are HTTP
10
+ * — no `fetch`/`axios`/`@get` literal lives here;
11
+ * - the shared normalizer for canonical paths + verbs.
12
+ *
13
+ * Every source file is scanned for BOTH surfaces: a backend that serves routes
14
+ * AND calls other services contributes to both lists (the consumer/provider
15
+ * model — a participant is whatever its served/consumed sets make it). Role
16
+ * assignment happens above this module, never inside it.
17
+ *
18
+ * Precision guard (validated): a member call with no declared receiver allowlist
19
+ * (`axios.get`, `requests.get`, `agent.X.del`) only counts as a client call when
20
+ * its first argument is a path-like literal — that filter keeps non-HTTP `.get`/
21
+ * `.delete` (lodash, Maps) out while still catching app-specific wrappers.
22
+ */
23
+ import type { HttpFlowSupport } from '../../languages/types';
24
+ import { type Node } from '../../ast/parse';
25
+ import { type HttpMethod, type NormalizeConfig } from './normalize';
26
+ /** An outbound HTTP call found in source (the consumed side). */
27
+ export interface ClientCall {
28
+ readonly method: HttpMethod;
29
+ readonly rawUrl: string;
30
+ /** Normalized path, or `null` when the URL is dynamic/external (unresolved). */
31
+ readonly path: string | null;
32
+ readonly receiver: string;
33
+ readonly file: string;
34
+ readonly line: number;
35
+ }
36
+ /** An inbound route a service serves (the served side). `via` records how it
37
+ * was discovered: a source decorator, an Express-style route call, or an
38
+ * ingested OpenAPI/spec document (preferred when available). */
39
+ export interface RouteEndpoint {
40
+ readonly method: HttpMethod;
41
+ readonly path: string;
42
+ readonly via: 'decorator' | 'router-call' | 'spec';
43
+ readonly handler: string | null;
44
+ readonly file: string;
45
+ readonly line: number;
46
+ }
47
+ /** Both HTTP surfaces extracted from one file. */
48
+ export interface FileFlow {
49
+ readonly calls: ClientCall[];
50
+ readonly routes: RouteEndpoint[];
51
+ }
52
+ /**
53
+ * Extract both HTTP surfaces from one already-parsed tree using a pack's
54
+ * httpFlow descriptor. Pure over its inputs.
55
+ */
56
+ export declare function extractFromTree(root: Node, hf: HttpFlowSupport, file: string, config?: NormalizeConfig): FileFlow;
57
+ /**
58
+ * Extract both HTTP surfaces from one file on disk. Returns empty surfaces when
59
+ * the language has no httpFlow descriptor, and `null` when the file can't be
60
+ * parsed (engine/grammar unavailable or unreadable) — callers treat `null` as
61
+ * "skip this file", never an error.
62
+ */
63
+ export declare function extractFileFlow(filePath: string, config?: NormalizeConfig): Promise<FileFlow | null>;
64
+ //# sourceMappingURL=extract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../../src/analyzers/flow/extract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAc,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAAmB,KAAK,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAkC,KAAK,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAEpG,iEAAiE;AACjE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;iEAEiE;AACjE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,WAAW,GAAG,aAAa,GAAG,MAAM,CAAC;IACnD,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,kDAAkD;AAClD,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC;CAClC;AAiGD;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,IAAI,EACV,EAAE,EAAE,eAAe,EACnB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,eAAe,GACvB,QAAQ,CA0GV;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,eAAe,GACvB,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAM1B"}
@@ -0,0 +1,254 @@
1
+ "use strict";
2
+ /**
3
+ * Flow extraction — the AST pass that turns source into the two sides of an
4
+ * HTTP integration: outbound client calls (CONSUMED) and inbound route
5
+ * declarations (SERVED).
6
+ *
7
+ * It is the cross-cutting consumer of three seams and hardcodes none of them:
8
+ * - the canonical AST layer (`src/ast/`) for parsing — graphify-independent,
9
+ * and registered as its own concern, never folded into graphify's pass;
10
+ * - each pack's `httpFlow` descriptor (Rule 6) for WHICH constructs are HTTP
11
+ * — no `fetch`/`axios`/`@get` literal lives here;
12
+ * - the shared normalizer for canonical paths + verbs.
13
+ *
14
+ * Every source file is scanned for BOTH surfaces: a backend that serves routes
15
+ * AND calls other services contributes to both lists (the consumer/provider
16
+ * model — a participant is whatever its served/consumed sets make it). Role
17
+ * assignment happens above this module, never inside it.
18
+ *
19
+ * Precision guard (validated): a member call with no declared receiver allowlist
20
+ * (`axios.get`, `requests.get`, `agent.X.del`) only counts as a client call when
21
+ * its first argument is a path-like literal — that filter keeps non-HTTP `.get`/
22
+ * `.delete` (lodash, Maps) out while still catching app-specific wrappers.
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.extractFromTree = extractFromTree;
26
+ exports.extractFileFlow = extractFileFlow;
27
+ const languages_1 = require("../../languages");
28
+ const parse_1 = require("../../ast/parse");
29
+ const normalize_1 = require("./normalize");
30
+ const HTTP_VERB_METHODS = new Set([
31
+ 'get',
32
+ 'post',
33
+ 'put',
34
+ 'patch',
35
+ 'delete',
36
+ 'del',
37
+ 'head',
38
+ 'options',
39
+ ]);
40
+ function line(node) {
41
+ return node.startPosition.row + 1;
42
+ }
43
+ /** Text of a string/template-string node, else null (a dynamic argument). */
44
+ function literalText(node) {
45
+ if (!node)
46
+ return null;
47
+ return node.type === 'string' || node.type === 'template_string' ? node.text : null;
48
+ }
49
+ /**
50
+ * Does a raw literal look like a URL/path at the source level — i.e. could this
51
+ * `.get(...)`/`.post(...)` plausibly be an HTTP call rather than a Map/cache/
52
+ * lodash accessor? Used only for member calls with NO declared receiver
53
+ * allowlist, where it is the precision guard: a leading `/`, a leading template
54
+ * (`${host}/…`), an explicit scheme, or any embedded `/` qualifies; a bare token
55
+ * like `'config-key'` does not. (Route decorators are exempt — a framework route
56
+ * string is known to be a path even without a leading slash.)
57
+ */
58
+ function looksLikeUrlLiteral(raw) {
59
+ if (raw == null)
60
+ return false;
61
+ let s = raw.trim();
62
+ if (s.length >= 2 && /^['"`]/.test(s) && s.endsWith(s[0]))
63
+ s = s.slice(1, -1);
64
+ return s.startsWith('/') || s.startsWith('${') || s.includes('://') || s.includes('/');
65
+ }
66
+ function firstNamedArg(callOrArgs) {
67
+ const args = callOrArgs?.childForFieldName('arguments') ?? null;
68
+ if (!args)
69
+ return null;
70
+ for (const c of args.namedChildren)
71
+ if (c)
72
+ return c;
73
+ return null;
74
+ }
75
+ /** Does a member-call receiver match one of the declared bases (e.g. `app`, or
76
+ * `this.app`)? */
77
+ function receiverMatchesBase(receiver, bases) {
78
+ return bases.some((b) => receiver === b || receiver.endsWith(`.${b}`));
79
+ }
80
+ /** Pull `method: 'X'` out of a fetch options object (2nd arg), default GET. */
81
+ function fetchMethod(call, hf) {
82
+ const args = call.childForFieldName('arguments');
83
+ const opts = args?.namedChildren?.[1] ?? null;
84
+ if (opts && opts.type === 'object') {
85
+ let verb = null;
86
+ (0, parse_1.walk)(opts, (n) => {
87
+ if (verb)
88
+ return false;
89
+ if (n.type === 'pair') {
90
+ const key = n.childForFieldName('key');
91
+ const val = n.childForFieldName('value');
92
+ const keyName = key ? key.text.replace(/['"`]/g, '') : '';
93
+ if (keyName === 'method') {
94
+ const raw = literalText(val);
95
+ if (raw)
96
+ verb = (0, normalize_1.normalizeMethod)(raw.replace(/['"`]/g, ''), hf.methodAliases);
97
+ }
98
+ }
99
+ });
100
+ if (verb)
101
+ return verb;
102
+ }
103
+ return 'GET';
104
+ }
105
+ /**
106
+ * The handler name a route decorator is attached to (best-effort). Decorators
107
+ * are siblings preceding the member in the class body, so the handler is the
108
+ * decorator's next named sibling (skipping any stacked decorators); a nested
109
+ * grammar shape is handled by an ancestor fallback.
110
+ */
111
+ function decoratedHandlerName(decorator) {
112
+ let sib = decorator.nextNamedSibling;
113
+ while (sib && sib.type === 'decorator')
114
+ sib = sib.nextNamedSibling;
115
+ const sibName = sib?.childForFieldName('name');
116
+ if (sibName)
117
+ return sibName.text;
118
+ let cur = decorator.parent;
119
+ for (let i = 0; cur && i < 3; i++) {
120
+ if (cur.type === 'method_definition' || cur.type === 'function_declaration') {
121
+ const name = cur.childForFieldName('name');
122
+ if (name)
123
+ return name.text;
124
+ }
125
+ cur = cur.parent;
126
+ }
127
+ return null;
128
+ }
129
+ /**
130
+ * Extract both HTTP surfaces from one already-parsed tree using a pack's
131
+ * httpFlow descriptor. Pure over its inputs.
132
+ */
133
+ function extractFromTree(root, hf, file, config) {
134
+ const calls = [];
135
+ const routes = [];
136
+ const clientCallees = new Set(hf.clientCallees ?? []);
137
+ const methodCallMethods = new Set(hf.clientMethodCallees?.methods ?? []);
138
+ const methodCallBases = hf.clientMethodCallees?.bases;
139
+ const routeDecorators = new Set(hf.routeDecorators ?? []);
140
+ const routerMethods = new Set(hf.routeRouterCallees?.methods ?? []);
141
+ const routerBases = hf.routeRouterCallees?.bases ?? [];
142
+ (0, parse_1.walk)(root, (node) => {
143
+ // ── decorator routes: @get('/x') ──
144
+ if (node.type === 'decorator' && routeDecorators.size) {
145
+ const call = node.namedChildren.find((c) => c?.type === 'call_expression') ?? null;
146
+ const callee = call?.childForFieldName('function');
147
+ const name = callee && callee.type === 'identifier' ? callee.text : '';
148
+ if (call && routeDecorators.has(name)) {
149
+ const path = (0, normalize_1.normalizePath)(literalText(firstNamedArg(call)), config);
150
+ const method = (0, normalize_1.normalizeMethod)(name, hf.methodAliases);
151
+ if (path && method) {
152
+ routes.push({
153
+ method,
154
+ path,
155
+ via: 'decorator',
156
+ handler: decoratedHandlerName(node),
157
+ file,
158
+ line: line(node),
159
+ });
160
+ }
161
+ }
162
+ return; // a decorator's call is bookkeeping, not a client call
163
+ }
164
+ if (node.type !== 'call_expression')
165
+ return;
166
+ const callee = node.childForFieldName('function');
167
+ if (!callee)
168
+ return;
169
+ // ── bare client call: fetch(url, opts) ──
170
+ if (callee.type === 'identifier' && clientCallees.has(callee.text)) {
171
+ const raw = literalText(firstNamedArg(node));
172
+ if (raw != null) {
173
+ calls.push({
174
+ method: fetchMethod(node, hf),
175
+ rawUrl: raw,
176
+ path: (0, normalize_1.normalizePath)(raw, config),
177
+ receiver: callee.text,
178
+ file,
179
+ line: line(node),
180
+ });
181
+ }
182
+ return;
183
+ }
184
+ // ── member call: <recv>.<verb>(url|path, ...) ──
185
+ if (callee.type === 'member_expression') {
186
+ const prop = callee.childForFieldName('property');
187
+ const obj = callee.childForFieldName('object');
188
+ const verb = prop ? prop.text : '';
189
+ const receiver = obj ? obj.text : '';
190
+ if (!HTTP_VERB_METHODS.has(verb))
191
+ return;
192
+ // router/app route declaration
193
+ if (routerMethods.has(verb) && receiverMatchesBase(receiver, routerBases)) {
194
+ const path = (0, normalize_1.normalizePath)(literalText(firstNamedArg(node)), config);
195
+ const method = (0, normalize_1.normalizeMethod)(verb, hf.methodAliases);
196
+ if (path && method) {
197
+ routes.push({
198
+ method,
199
+ path,
200
+ via: 'router-call',
201
+ handler: receiver,
202
+ file,
203
+ line: line(node),
204
+ });
205
+ }
206
+ return;
207
+ }
208
+ // client call (bases optional). With no allowlist, require a path-like
209
+ // literal first arg — the precision guard against non-HTTP .get/.delete.
210
+ if (methodCallMethods.has(verb)) {
211
+ const baseOk = !methodCallBases ||
212
+ receiverMatchesBase(receiver, methodCallBases) ||
213
+ methodCallBases.includes(receiver);
214
+ if (!baseOk)
215
+ return;
216
+ const raw = literalText(firstNamedArg(node));
217
+ // Unallowlisted receiver → require a URL-looking literal (precision guard).
218
+ if (!methodCallBases && !looksLikeUrlLiteral(raw))
219
+ return;
220
+ const method = (0, normalize_1.normalizeMethod)(verb, hf.methodAliases);
221
+ if (raw != null && method) {
222
+ calls.push({
223
+ method,
224
+ rawUrl: raw,
225
+ path: (0, normalize_1.normalizePath)(raw, config),
226
+ receiver,
227
+ file,
228
+ line: line(node),
229
+ });
230
+ }
231
+ }
232
+ }
233
+ });
234
+ return { calls, routes };
235
+ }
236
+ /**
237
+ * Extract both HTTP surfaces from one file on disk. Returns empty surfaces when
238
+ * the language has no httpFlow descriptor, and `null` when the file can't be
239
+ * parsed (engine/grammar unavailable or unreadable) — callers treat `null` as
240
+ * "skip this file", never an error.
241
+ */
242
+ async function extractFileFlow(filePath, config) {
243
+ const parsed = await (0, parse_1.parseFile)(filePath);
244
+ if (!parsed)
245
+ return null;
246
+ const hf = httpFlowFor(parsed.languageId);
247
+ if (!hf)
248
+ return { calls: [], routes: [] };
249
+ return extractFromTree(parsed.tree.rootNode, hf, filePath, config);
250
+ }
251
+ function httpFlowFor(languageId) {
252
+ return (0, languages_1.getLanguage)(languageId)?.httpFlow;
253
+ }
254
+ //# sourceMappingURL=extract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.js","sourceRoot":"","sources":["../../../src/analyzers/flow/extract.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Flow gather — walk a set of roots, extract both HTTP surfaces from every
3
+ * source file, and assemble a `FlowModel`. The served side is the UNION of what
4
+ * source extraction finds and what any configured OpenAPI spec declares (a spec
5
+ * is authoritative but often incomplete, so static extraction adds recall).
6
+ *
7
+ * File discovery goes through the canonical `walkSourceFiles` (Rule 4
8
+ * exclusions + test-file skipping built in); the scanned extensions are exactly
9
+ * those of packs that declare BOTH an httpFlow descriptor and a tree-sitter
10
+ * grammar, so a non-flow language's files are never parsed and adding a flow
11
+ * pack auto-extends the scan (Rule 6).
12
+ */
13
+ import { type FlowModel } from './model';
14
+ export interface GatherFlowOptions {
15
+ /** Directories to scan for source (frontend, backend, services…). */
16
+ readonly roots: readonly string[];
17
+ /** OpenAPI spec files whose served routes union with the extracted ones. */
18
+ readonly specs?: readonly string[];
19
+ /** Host-helper prefixes to strip during URL normalization (per-app config). */
20
+ readonly stripUrlPrefixes?: readonly string[];
21
+ }
22
+ /** Walk + extract + assemble. Files that don't parse are skipped, never fatal. */
23
+ export declare function gatherFlowModel(opts: GatherFlowOptions): Promise<FlowModel>;
24
+ //# sourceMappingURL=gather.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gather.d.ts","sourceRoot":"","sources":["../../../src/analyzers/flow/gather.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;AAIzD,MAAM,WAAW,iBAAiB;IAChC,qEAAqE;IACrE,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,+EAA+E;IAC/E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC/C;AAaD,kFAAkF;AAClF,wBAAsB,eAAe,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,SAAS,CAAC,CAgBjF"}
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ /**
3
+ * Flow gather — walk a set of roots, extract both HTTP surfaces from every
4
+ * source file, and assemble a `FlowModel`. The served side is the UNION of what
5
+ * source extraction finds and what any configured OpenAPI spec declares (a spec
6
+ * is authoritative but often incomplete, so static extraction adds recall).
7
+ *
8
+ * File discovery goes through the canonical `walkSourceFiles` (Rule 4
9
+ * exclusions + test-file skipping built in); the scanned extensions are exactly
10
+ * those of packs that declare BOTH an httpFlow descriptor and a tree-sitter
11
+ * grammar, so a non-flow language's files are never parsed and adding a flow
12
+ * pack auto-extends the scan (Rule 6).
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.gatherFlowModel = gatherFlowModel;
16
+ const path_1 = require("path");
17
+ const languages_1 = require("../../languages");
18
+ const walk_source_files_1 = require("../tools/walk-source-files");
19
+ const extract_1 = require("./extract");
20
+ const model_1 = require("./model");
21
+ const spec_source_1 = require("./spec-source");
22
+ /** Extensions of packs that can contribute flow (httpFlow + a grammar). */
23
+ function flowExtensions() {
24
+ const exts = new Set();
25
+ for (const pack of languages_1.LANGUAGES) {
26
+ if (pack.httpFlow && pack.treeSitterGrammars) {
27
+ for (const ext of Object.keys(pack.treeSitterGrammars))
28
+ exts.add(ext);
29
+ }
30
+ }
31
+ return [...exts];
32
+ }
33
+ /** Walk + extract + assemble. Files that don't parse are skipped, never fatal. */
34
+ async function gatherFlowModel(opts) {
35
+ const config = { stripUrlPrefixes: opts.stripUrlPrefixes };
36
+ const extensions = flowExtensions();
37
+ const fileFlows = [];
38
+ for (const root of opts.roots) {
39
+ for (const rel of (0, walk_source_files_1.walkSourceFiles)(root, { extensions })) {
40
+ const flow = await (0, extract_1.extractFileFlow)((0, path_1.join)(root, rel), config);
41
+ if (flow)
42
+ fileFlows.push(flow);
43
+ }
44
+ }
45
+ // Served side = extracted routes UNION spec routes (dedup is implicit: the
46
+ // join indexes routes by (method, path), so a duplicate collapses).
47
+ const specRoutes = (opts.specs ?? []).flatMap((spec) => (0, spec_source_1.loadOpenApiRoutes)(spec));
48
+ return (0, model_1.buildFlowModel)([...fileFlows, { calls: [], routes: specRoutes }]);
49
+ }
50
+ //# sourceMappingURL=gather.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gather.js","sourceRoot":"","sources":["../../../src/analyzers/flow/gather.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;AA+BH,0CAgBC;AA7CD,+BAA4B;AAC5B,+CAA4C;AAC5C,kEAA6D;AAC7D,uCAA2D;AAC3D,mCAAyD;AACzD,+CAAkD;AAYlD,2EAA2E;AAC3E,SAAS,cAAc;IACrB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,qBAAS,EAAE,CAAC;QAC7B,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC7C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC;gBAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;AACnB,CAAC;AAED,kFAAkF;AAC3E,KAAK,UAAU,eAAe,CAAC,IAAuB;IAC3D,MAAM,MAAM,GAAoB,EAAE,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,EAAE,CAAC;IAC5E,MAAM,UAAU,GAAG,cAAc,EAAE,CAAC;IACpC,MAAM,SAAS,GAAe,EAAE,CAAC;IAEjC,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QAC9B,KAAK,MAAM,GAAG,IAAI,IAAA,mCAAe,EAAC,IAAI,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;YACxD,MAAM,IAAI,GAAG,MAAM,IAAA,yBAAe,EAAC,IAAA,WAAI,EAAC,IAAI,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;YAC5D,IAAI,IAAI;gBAAE,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED,2EAA2E;IAC3E,oEAAoE;IACpE,MAAM,UAAU,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAA,+BAAiB,EAAC,IAAI,CAAC,CAAC,CAAC;IACjF,OAAO,IAAA,sBAAc,EAAC,CAAC,GAAG,SAAS,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Flow model + the join — assemble extracted client calls and route
3
+ * declarations into a `FlowModel`, then bind each call to the route it targets
4
+ * on the normalized `(method, path)` key.
5
+ *
6
+ * The join is where consumed meets served. A binding carries a confidence in
7
+ * [0, 1] + a reason, mirroring the git-aware matcher's contract: an exact key
8
+ * match on a path with real static signal is full confidence; a path that is
9
+ * all placeholder (`/{var}`) carries no signal and is low confidence even when
10
+ * it happens to match a catch-all; a dynamic or unrouted call is unresolved.
11
+ * Confidence is what a gate thresholds on — only high-confidence bindings can
12
+ * block, which is what keeps the false-positive budget intact.
13
+ *
14
+ * Pure over its inputs (the file-extraction step that produces `FileFlow`s does
15
+ * the I/O); this module just structures + joins.
16
+ */
17
+ import { type ClientCall, type FileFlow, type RouteEndpoint } from './extract';
18
+ import type { NormalizeConfig } from './normalize';
19
+ /**
20
+ * Why a call did or didn't bind to a route:
21
+ * - `exact` — matched a route on a path with real static signal;
22
+ * - `placeholder-only` — matched, but the path is all `{var}` (no signal);
23
+ * - `no-route` — path resolved to an internal route shape, but no
24
+ * served route matches it;
25
+ * - `external` — the URL is an external/absolute address we don't
26
+ * serve (path is null), so it is not an internal binding.
27
+ */
28
+ export type BindingReason = 'exact' | 'placeholder-only' | 'no-route' | 'external';
29
+ /** One client call resolved (or not) against the served routes. */
30
+ export interface FlowBinding {
31
+ readonly call: ClientCall;
32
+ readonly route: RouteEndpoint | null;
33
+ readonly confidence: number;
34
+ readonly reason: BindingReason;
35
+ }
36
+ /** The assembled flow: both surfaces plus the bindings between them. */
37
+ export interface FlowModel {
38
+ readonly calls: readonly ClientCall[];
39
+ readonly routes: readonly RouteEndpoint[];
40
+ readonly bindings: readonly FlowBinding[];
41
+ }
42
+ /**
43
+ * Bind each client call to the route it targets, on the normalized
44
+ * `(method, path)` key. Routes are indexed once; each call resolves in O(1).
45
+ */
46
+ export declare function joinFlow(calls: readonly ClientCall[], routes: readonly RouteEndpoint[]): FlowBinding[];
47
+ /** Flatten per-file surfaces into one model + its bindings. */
48
+ export declare function buildFlowModel(fileFlows: readonly FileFlow[]): FlowModel;
49
+ /**
50
+ * Extract + assemble a flow model from a set of files. `null` extractions
51
+ * (unparseable / engine unavailable) are skipped, never fatal.
52
+ */
53
+ export declare function extractFlowModel(filePaths: readonly string[], config?: NormalizeConfig): Promise<FlowModel>;
54
+ /** Summary counts for a model (drives the `flow` preview + acceptance checks). */
55
+ export interface FlowSummary {
56
+ readonly calls: number;
57
+ readonly routes: number;
58
+ readonly resolved: number;
59
+ readonly highConfidence: number;
60
+ readonly unresolved: number;
61
+ }
62
+ export declare function summarize(model: FlowModel): FlowSummary;
63
+ //# sourceMappingURL=model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../src/analyzers/flow/model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAmB,KAAK,UAAU,EAAE,KAAK,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAChG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,kBAAkB,GAAG,UAAU,GAAG,UAAU,CAAC;AAEnF,mEAAmE;AACnE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;CAChC;AAED,wEAAwE;AACxE,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,CAAC;IAC1C,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;CAC3C;AAOD;;;GAGG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,SAAS,UAAU,EAAE,EAC5B,MAAM,EAAE,SAAS,aAAa,EAAE,GAC/B,WAAW,EAAE,CAaf;AAED,+DAA+D;AAC/D,wBAAgB,cAAc,CAAC,SAAS,EAAE,SAAS,QAAQ,EAAE,GAAG,SAAS,CAIxE;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,MAAM,CAAC,EAAE,eAAe,GACvB,OAAO,CAAC,SAAS,CAAC,CAOpB;AAED,kFAAkF;AAClF,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,WAAW,CAUvD"}