@lyse-labs/lyse 0.1.0-alpha.2 → 0.2.0-alpha.1

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 (140) hide show
  1. package/LICENSE +0 -5
  2. package/dist/cli.js +67 -11
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/__tests__/add-ci-gate.test.d.ts +1 -0
  5. package/dist/commands/__tests__/add-ci-gate.test.js +149 -0
  6. package/dist/commands/__tests__/add-ci-gate.test.js.map +1 -0
  7. package/dist/commands/add-ci-gate.d.ts +26 -0
  8. package/dist/commands/add-ci-gate.js +429 -0
  9. package/dist/commands/add-ci-gate.js.map +1 -0
  10. package/dist/commands/audit-pipeline.js +1 -1
  11. package/dist/commands/audit-pipeline.js.map +1 -1
  12. package/dist/commands/fix.d.ts +8 -8
  13. package/dist/commands/init.d.ts +3 -3
  14. package/dist/commands/init.js +1 -1
  15. package/dist/commands/init.js.map +1 -1
  16. package/dist/commands/mcp-setup.js +1 -1
  17. package/dist/commands/mcp-setup.js.map +1 -1
  18. package/dist/config/schema.d.ts +14 -14
  19. package/dist/llm/__tests__/layer4-stage.test.d.ts +1 -0
  20. package/dist/llm/__tests__/layer4-stage.test.js +157 -0
  21. package/dist/llm/__tests__/layer4-stage.test.js.map +1 -0
  22. package/dist/llm/__tests__/validator.test.d.ts +1 -0
  23. package/dist/llm/__tests__/validator.test.js +156 -0
  24. package/dist/llm/__tests__/validator.test.js.map +1 -0
  25. package/dist/llm/connectors/__tests__/anthropic-adapter.test.d.ts +1 -0
  26. package/dist/llm/connectors/__tests__/anthropic-adapter.test.js +99 -0
  27. package/dist/llm/connectors/__tests__/anthropic-adapter.test.js.map +1 -0
  28. package/dist/llm/connectors/__tests__/cache.test.d.ts +1 -0
  29. package/dist/llm/connectors/__tests__/cache.test.js +50 -0
  30. package/dist/llm/connectors/__tests__/cache.test.js.map +1 -0
  31. package/dist/llm/connectors/__tests__/noop-adapter.test.d.ts +1 -0
  32. package/dist/llm/connectors/__tests__/noop-adapter.test.js +21 -0
  33. package/dist/llm/connectors/__tests__/noop-adapter.test.js.map +1 -0
  34. package/dist/llm/connectors/__tests__/openai-compatible-adapter.test.d.ts +1 -0
  35. package/dist/llm/connectors/__tests__/openai-compatible-adapter.test.js +82 -0
  36. package/dist/llm/connectors/__tests__/openai-compatible-adapter.test.js.map +1 -0
  37. package/dist/llm/connectors/__tests__/resolver.test.d.ts +1 -0
  38. package/dist/llm/connectors/__tests__/resolver.test.js +140 -0
  39. package/dist/llm/connectors/__tests__/resolver.test.js.map +1 -0
  40. package/dist/llm/connectors/anthropic-adapter.d.ts +12 -0
  41. package/dist/llm/connectors/anthropic-adapter.js +57 -0
  42. package/dist/llm/connectors/anthropic-adapter.js.map +1 -0
  43. package/dist/llm/connectors/cache.d.ts +15 -0
  44. package/dist/llm/connectors/cache.js +57 -0
  45. package/dist/llm/connectors/cache.js.map +1 -0
  46. package/dist/llm/connectors/noop-adapter.d.ts +4 -0
  47. package/dist/llm/connectors/noop-adapter.js +12 -0
  48. package/dist/llm/connectors/noop-adapter.js.map +1 -0
  49. package/dist/llm/connectors/openai-compatible-adapter.d.ts +13 -0
  50. package/dist/llm/connectors/openai-compatible-adapter.js +47 -0
  51. package/dist/llm/connectors/openai-compatible-adapter.js.map +1 -0
  52. package/dist/llm/connectors/resolver.d.ts +9 -0
  53. package/dist/llm/connectors/resolver.js +162 -0
  54. package/dist/llm/connectors/resolver.js.map +1 -0
  55. package/dist/llm/connectors/types.d.ts +25 -0
  56. package/dist/llm/connectors/types.js +13 -0
  57. package/dist/llm/connectors/types.js.map +1 -0
  58. package/dist/llm/layer4-stage.d.ts +8 -6
  59. package/dist/llm/layer4-stage.js +145 -9
  60. package/dist/llm/layer4-stage.js.map +1 -1
  61. package/dist/llm/rubric-stub.d.ts +8 -0
  62. package/dist/llm/rubric-stub.js +4 -0
  63. package/dist/llm/rubric-stub.js.map +1 -0
  64. package/dist/llm/validator.d.ts +18 -0
  65. package/dist/llm/validator.js +81 -0
  66. package/dist/llm/validator.js.map +1 -0
  67. package/dist/parsers/ai-tokens.d.ts +10 -0
  68. package/dist/parsers/ai-tokens.js +156 -0
  69. package/dist/parsers/ai-tokens.js.map +1 -0
  70. package/dist/reliability/catalogue/__tests__/sub-axes.test.js +13 -3
  71. package/dist/reliability/catalogue/__tests__/sub-axes.test.js.map +1 -1
  72. package/dist/reliability/catalogue/sub-axes.js +16 -0
  73. package/dist/reliability/catalogue/sub-axes.js.map +1 -1
  74. package/dist/reliability/types.d.ts +1 -1
  75. package/dist/rule-runner.js +1 -1
  76. package/dist/rule-runner.js.map +1 -1
  77. package/dist/rules/ai-governance-ai-content-live-region.d.ts +11 -0
  78. package/dist/rules/ai-governance-ai-content-live-region.js +304 -0
  79. package/dist/rules/ai-governance-ai-content-live-region.js.map +1 -0
  80. package/dist/rules/ai-governance-ai-loading-error-states.d.ts +9 -0
  81. package/dist/rules/ai-governance-ai-loading-error-states.js +214 -0
  82. package/dist/rules/ai-governance-ai-loading-error-states.js.map +1 -0
  83. package/dist/rules/ai-governance-ai-marker-anti-patterns.d.ts +15 -0
  84. package/dist/rules/ai-governance-ai-marker-anti-patterns.js +178 -0
  85. package/dist/rules/ai-governance-ai-marker-anti-patterns.js.map +1 -0
  86. package/dist/rules/ai-governance-ai-marker-component-present.d.ts +28 -0
  87. package/dist/rules/ai-governance-ai-marker-component-present.js +320 -0
  88. package/dist/rules/ai-governance-ai-marker-component-present.js.map +1 -0
  89. package/dist/rules/ai-governance-ai-token-requires-marker.d.ts +17 -0
  90. package/dist/rules/ai-governance-ai-token-requires-marker.js +206 -0
  91. package/dist/rules/ai-governance-ai-token-requires-marker.js.map +1 -0
  92. package/dist/rules/ai-governance-ai-tokens-reserved.d.ts +8 -0
  93. package/dist/rules/ai-governance-ai-tokens-reserved.js +85 -0
  94. package/dist/rules/ai-governance-ai-tokens-reserved.js.map +1 -0
  95. package/dist/rules/ai-governance-disclaimer-present.d.ts +18 -0
  96. package/dist/rules/ai-governance-disclaimer-present.js +210 -0
  97. package/dist/rules/ai-governance-disclaimer-present.js.map +1 -0
  98. package/dist/rules/ai-governance-explainability-affordance.d.ts +14 -0
  99. package/dist/rules/ai-governance-explainability-affordance.js +196 -0
  100. package/dist/rules/ai-governance-explainability-affordance.js.map +1 -0
  101. package/dist/rules/ai-governance-feedback-control-present.d.ts +19 -0
  102. package/dist/rules/ai-governance-feedback-control-present.js +223 -0
  103. package/dist/rules/ai-governance-feedback-control-present.js.map +1 -0
  104. package/dist/rules/ai-governance-human-control-affordances.d.ts +16 -0
  105. package/dist/rules/ai-governance-human-control-affordances.js +228 -0
  106. package/dist/rules/ai-governance-human-control-affordances.js.map +1 -0
  107. package/dist/rules/ai-governance-value-gate-doc-present.d.ts +18 -0
  108. package/dist/rules/ai-governance-value-gate-doc-present.js +206 -0
  109. package/dist/rules/ai-governance-value-gate-doc-present.js.map +1 -0
  110. package/dist/rules/ai-surface-agent-instruction-files.d.ts +33 -0
  111. package/dist/rules/ai-surface-agent-instruction-files.js +310 -0
  112. package/dist/rules/ai-surface-agent-instruction-files.js.map +1 -0
  113. package/dist/rules/ai-surface-llms-txt-structure.d.ts +18 -0
  114. package/dist/rules/ai-surface-llms-txt-structure.js +200 -0
  115. package/dist/rules/ai-surface-llms-txt-structure.js.map +1 -0
  116. package/dist/rules/ai-surface-mcp-config-present.d.ts +23 -0
  117. package/dist/rules/ai-surface-mcp-config-present.js +193 -0
  118. package/dist/rules/ai-surface-mcp-config-present.js.map +1 -0
  119. package/dist/rules/ai-surface-shadcn-registry-valid.d.ts +22 -0
  120. package/dist/rules/ai-surface-shadcn-registry-valid.js +247 -0
  121. package/dist/rules/ai-surface-shadcn-registry-valid.js.map +1 -0
  122. package/dist/rules/components-contracts-strictness.d.ts +48 -0
  123. package/dist/rules/components-contracts-strictness.js +372 -0
  124. package/dist/rules/components-contracts-strictness.js.map +1 -0
  125. package/dist/rules/registry.js +32 -0
  126. package/dist/rules/registry.js.map +1 -1
  127. package/dist/rules/tokens-dtcg-conformance.d.ts +55 -19
  128. package/dist/rules/tokens-dtcg-conformance.js +320 -82
  129. package/dist/rules/tokens-dtcg-conformance.js.map +1 -1
  130. package/dist/scorer.js +4 -3
  131. package/dist/scorer.js.map +1 -1
  132. package/dist/types.d.ts +2 -2
  133. package/dist/types.js.map +1 -1
  134. package/package.json +41 -16
  135. package/rules-manifest.json +431 -6
  136. package/schemas/v1/lyse-rules.json +1 -1
  137. package/dist/commands/ci-setup.d.ts +0 -9
  138. package/dist/commands/ci-setup.js +0 -42
  139. package/dist/commands/ci-setup.js.map +0 -1
  140. package/dist/commands/templates/lyse-workflow.yml.template +0 -30
@@ -36,13 +36,13 @@ export declare const LyseConfigSchema: z.ZodObject<{
36
36
  tolerance: z.ZodOptional<z.ZodNumber>;
37
37
  disable: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
38
38
  }, "strip", z.ZodTypeAny, {
39
+ disable?: string[] | undefined;
39
40
  severity?: "error" | "warning" | "info" | "off" | undefined;
40
41
  tolerance?: number | undefined;
41
- disable?: string[] | undefined;
42
42
  }, {
43
+ disable?: string[] | undefined;
43
44
  severity?: "error" | "warning" | "info" | "off" | undefined;
44
45
  tolerance?: number | undefined;
45
- disable?: string[] | undefined;
46
46
  }>]>>>;
47
47
  llm: z.ZodOptional<z.ZodObject<{
48
48
  provider: z.ZodOptional<z.ZodEnum<["anthropic", "openai", "openai-compatible", "mcp", "none", "auto"]>>;
@@ -71,15 +71,10 @@ export declare const LyseConfigSchema: z.ZodObject<{
71
71
  }>>;
72
72
  }, "strip", z.ZodTypeAny, {
73
73
  rules?: Record<string, "off" | {
74
+ disable?: string[] | undefined;
74
75
  severity?: "error" | "warning" | "info" | "off" | undefined;
75
76
  tolerance?: number | undefined;
76
- disable?: string[] | undefined;
77
77
  }> | undefined;
78
- designSystem?: {
79
- componentsModule?: string | undefined;
80
- elements?: Record<string, string> | undefined;
81
- excludePaths?: string[] | undefined;
82
- } | undefined;
83
78
  llm?: {
84
79
  staticOnly?: boolean | undefined;
85
80
  provider?: "anthropic" | "openai" | "openai-compatible" | "mcp" | "none" | "auto" | undefined;
@@ -89,17 +84,17 @@ export declare const LyseConfigSchema: z.ZodObject<{
89
84
  costCapUsd?: number | undefined;
90
85
  cacheMaxAgeDays?: number | undefined;
91
86
  } | undefined;
87
+ designSystem?: {
88
+ componentsModule?: string | undefined;
89
+ elements?: Record<string, string> | undefined;
90
+ excludePaths?: string[] | undefined;
91
+ } | undefined;
92
92
  }, {
93
93
  rules?: Record<string, "off" | {
94
+ disable?: string[] | undefined;
94
95
  severity?: "error" | "warning" | "info" | "off" | undefined;
95
96
  tolerance?: number | undefined;
96
- disable?: string[] | undefined;
97
97
  }> | undefined;
98
- designSystem?: {
99
- componentsModule?: string | undefined;
100
- elements?: Record<string, string> | undefined;
101
- excludePaths?: string[] | undefined;
102
- } | null | undefined;
103
98
  llm?: {
104
99
  staticOnly?: boolean | undefined;
105
100
  provider?: "anthropic" | "openai" | "openai-compatible" | "mcp" | "none" | "auto" | undefined;
@@ -109,6 +104,11 @@ export declare const LyseConfigSchema: z.ZodObject<{
109
104
  costCapUsd?: number | undefined;
110
105
  cacheMaxAgeDays?: number | undefined;
111
106
  } | undefined;
107
+ designSystem?: {
108
+ componentsModule?: string | undefined;
109
+ elements?: Record<string, string> | undefined;
110
+ excludePaths?: string[] | undefined;
111
+ } | null | undefined;
112
112
  }>;
113
113
  export type LyseConfigValidated = z.infer<typeof LyseConfigSchema>;
114
114
  /**
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,157 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { mkdtempSync, writeFileSync, mkdirSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { runLayer4Stage } from "../layer4-stage.js";
6
+ function makeRepoRoot(files) {
7
+ const dir = mkdtempSync(join(tmpdir(), "lyse-layer4-test-"));
8
+ if (files) {
9
+ for (const [rel, content] of Object.entries(files)) {
10
+ const abs = join(dir, rel);
11
+ mkdirSync(join(abs, ".."), { recursive: true });
12
+ writeFileSync(abs, content);
13
+ }
14
+ }
15
+ return dir;
16
+ }
17
+ function mockConnector(responseText, extra) {
18
+ return {
19
+ complete: async () => ({
20
+ text: responseText,
21
+ usdSpent: 0.002,
22
+ modelUsed: "claude-sonnet-4-6",
23
+ llmQuality: "higher",
24
+ cacheHit: false,
25
+ ...extra,
26
+ }),
27
+ };
28
+ }
29
+ const MIN_CONFIG = {};
30
+ const ONE_DIMENSION = [{
31
+ key: "ai-error-state",
32
+ axis: "ai-governance",
33
+ ruleId: "ai-governance/ai-loading-error-states",
34
+ prompt: "Check that AI components have error states.",
35
+ }];
36
+ describe("runLayer4Stage — static-only paths", () => {
37
+ it("returns staticOnly:true when flags.staticOnly is set", async () => {
38
+ const repoRoot = makeRepoRoot();
39
+ const result = await runLayer4Stage({ repoRoot, config: MIN_CONFIG, flags: { staticOnly: true }, staticFindings: [] });
40
+ expect(result.meta.staticOnly).toBe(true);
41
+ expect(result.augmentedFindings).toHaveLength(0);
42
+ });
43
+ it("returns staticOnly:true when config.llm.staticOnly is set", async () => {
44
+ const repoRoot = makeRepoRoot();
45
+ const config = { llm: { staticOnly: true } };
46
+ const result = await runLayer4Stage({ repoRoot, config, flags: undefined, staticFindings: [] });
47
+ expect(result.meta.staticOnly).toBe(true);
48
+ expect(result.augmentedFindings).toHaveLength(0);
49
+ });
50
+ it("returns empty meta when rubric dimensions is empty (no staticOnly flag)", async () => {
51
+ const repoRoot = makeRepoRoot();
52
+ const connector = mockConnector("{}");
53
+ const result = await runLayer4Stage({ repoRoot, config: MIN_CONFIG, flags: undefined, staticFindings: [] }, { connector, rubricDimensions: [] });
54
+ expect(result.meta.staticOnly).toBeUndefined();
55
+ expect(result.augmentedFindings).toHaveLength(0);
56
+ });
57
+ });
58
+ describe("runLayer4Stage — happy path", () => {
59
+ it("produces augmented findings when connector returns valid JSON", async () => {
60
+ const repoRoot = makeRepoRoot({ "src/Chat.tsx": "export function Chat() { return null; }" });
61
+ const responseJson = JSON.stringify({
62
+ findings: [{
63
+ ruleId: "ai-governance/ai-loading-error-states",
64
+ axis: "ai-governance",
65
+ severity: "warning",
66
+ file: "src/Chat.tsx",
67
+ line: 1,
68
+ column: 1,
69
+ snippet: "export function Chat()",
70
+ message: "Missing AI error state",
71
+ }],
72
+ });
73
+ const connector = mockConnector(responseJson);
74
+ const result = await runLayer4Stage({ repoRoot, config: MIN_CONFIG, flags: undefined, staticFindings: [] }, { connector, rubricDimensions: ONE_DIMENSION });
75
+ expect(result.augmentedFindings).toHaveLength(1);
76
+ expect(result.augmentedFindings[0].ruleId).toBe("ai-governance/ai-loading-error-states");
77
+ expect(result.meta.staticOnly).toBeUndefined();
78
+ expect(result.meta.modelUsed).toBe("claude-sonnet-4-6");
79
+ expect(result.meta.usdSpent).toBe(0.002);
80
+ expect(result.meta.droppedHallucinations).toBe(0);
81
+ expect(result.meta.llmQuality).toBe("higher");
82
+ });
83
+ it("sets cacheHit in meta when connector returns cacheHit:true", async () => {
84
+ const repoRoot = makeRepoRoot({ "Foo.tsx": "const x = 1;" });
85
+ const responseJson = JSON.stringify({ findings: [] });
86
+ const connector = mockConnector(responseJson, { cacheHit: true, usdSpent: 0 });
87
+ const result = await runLayer4Stage({ repoRoot, config: MIN_CONFIG, flags: undefined, staticFindings: [] }, { connector, rubricDimensions: ONE_DIMENSION });
88
+ expect(result.meta.cacheHit).toBe(true);
89
+ expect(result.meta.usdSpent).toBe(0);
90
+ });
91
+ it("populates droppedHallucinations for findings with missing files", async () => {
92
+ const repoRoot = makeRepoRoot();
93
+ const responseJson = JSON.stringify({
94
+ findings: [{
95
+ ruleId: "ai-governance/ai-loading-error-states",
96
+ axis: "ai-governance",
97
+ severity: "warning",
98
+ file: "src/GhostFile.tsx",
99
+ line: 1,
100
+ column: 1,
101
+ snippet: "export function Ghost()",
102
+ message: "Hallucination",
103
+ }],
104
+ });
105
+ const connector = mockConnector(responseJson);
106
+ const result = await runLayer4Stage({ repoRoot, config: MIN_CONFIG, flags: undefined, staticFindings: [] }, { connector, rubricDimensions: ONE_DIMENSION });
107
+ expect(result.augmentedFindings).toHaveLength(0);
108
+ expect(result.meta.droppedHallucinations).toBe(1);
109
+ });
110
+ it("handles JSON wrapped in markdown code fence", async () => {
111
+ const repoRoot = makeRepoRoot({ "Btn.tsx": "export const Btn = () => null;" });
112
+ const responseJson = "```json\n" + JSON.stringify({
113
+ findings: [{
114
+ ruleId: "ai-governance/ai-loading-error-states",
115
+ axis: "ai-governance",
116
+ severity: "info",
117
+ file: "Btn.tsx",
118
+ line: 1,
119
+ column: 1,
120
+ snippet: "export const Btn = () => null;",
121
+ message: "Btn found",
122
+ }],
123
+ }) + "\n```";
124
+ const connector = mockConnector(responseJson);
125
+ const result = await runLayer4Stage({ repoRoot, config: MIN_CONFIG, flags: undefined, staticFindings: [] }, { connector, rubricDimensions: ONE_DIMENSION });
126
+ expect(result.augmentedFindings).toHaveLength(1);
127
+ });
128
+ });
129
+ describe("runLayer4Stage — error handling", () => {
130
+ it("returns meta.error and empty findings when connector throws", async () => {
131
+ const repoRoot = makeRepoRoot();
132
+ const errorConnector = {
133
+ complete: async () => { throw new Error("network failure"); },
134
+ };
135
+ const result = await runLayer4Stage({ repoRoot, config: MIN_CONFIG, flags: undefined, staticFindings: [] }, { connector: errorConnector, rubricDimensions: ONE_DIMENSION });
136
+ expect(result.augmentedFindings).toHaveLength(0);
137
+ expect(result.meta.error).toBeDefined();
138
+ expect(result.meta.error.kind).toBe("ConnectorError");
139
+ expect(result.meta.error.message).toContain("network failure");
140
+ });
141
+ it("returns meta.error and empty findings when JSON is malformed", async () => {
142
+ const repoRoot = makeRepoRoot();
143
+ const connector = mockConnector("not json at all {{{");
144
+ const result = await runLayer4Stage({ repoRoot, config: MIN_CONFIG, flags: undefined, staticFindings: [] }, { connector, rubricDimensions: ONE_DIMENSION });
145
+ expect(result.augmentedFindings).toHaveLength(0);
146
+ expect(result.meta.error).toBeDefined();
147
+ expect(result.meta.error.kind).toBe("ParseError");
148
+ });
149
+ it("returns empty meta when connector returns empty text (noop/over-budget)", async () => {
150
+ const repoRoot = makeRepoRoot();
151
+ const noopConnector = mockConnector("", { usdSpent: 0, modelUsed: "none", llmQuality: "lower" });
152
+ const result = await runLayer4Stage({ repoRoot, config: MIN_CONFIG, flags: undefined, staticFindings: [] }, { connector: noopConnector, rubricDimensions: ONE_DIMENSION });
153
+ expect(result.augmentedFindings).toHaveLength(0);
154
+ expect(result.meta.staticOnly).toBeUndefined();
155
+ });
156
+ });
157
+ //# sourceMappingURL=layer4-stage.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layer4-stage.test.js","sourceRoot":"","sources":["../../../src/llm/__tests__/layer4-stage.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,156 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { mkdtempSync, writeFileSync, mkdirSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { validateProposedFindings } from "../validator.js";
6
+ function makeRepoRoot() {
7
+ const dir = mkdtempSync(join(tmpdir(), "lyse-validator-test-"));
8
+ return dir;
9
+ }
10
+ describe("validateProposedFindings", () => {
11
+ it("returns empty result for empty input", async () => {
12
+ const repoRoot = makeRepoRoot();
13
+ const result = await validateProposedFindings([], repoRoot);
14
+ expect(result.findings).toEqual([]);
15
+ expect(result.droppedHallucinations).toBe(0);
16
+ });
17
+ it("keeps finding when file exists and snippet appears", async () => {
18
+ const repoRoot = makeRepoRoot();
19
+ const srcDir = join(repoRoot, "src");
20
+ mkdirSync(srcDir);
21
+ writeFileSync(join(srcDir, "Button.tsx"), "export function Button() { return null; }");
22
+ const proposed = [{
23
+ ruleId: "ai-governance/ai-loading-error-states",
24
+ axis: "ai-governance",
25
+ severity: "warning",
26
+ file: "src/Button.tsx",
27
+ line: 1,
28
+ column: 1,
29
+ snippet: "export function Button()",
30
+ message: "Missing AI error state",
31
+ }];
32
+ const result = await validateProposedFindings(proposed, repoRoot);
33
+ expect(result.findings).toHaveLength(1);
34
+ expect(result.findings[0].ruleId).toBe("ai-governance/ai-loading-error-states");
35
+ expect(result.droppedHallucinations).toBe(0);
36
+ });
37
+ it("drops finding when file does not exist", async () => {
38
+ const repoRoot = makeRepoRoot();
39
+ const proposed = [{
40
+ ruleId: "ai-governance/ai-loading-error-states",
41
+ axis: "ai-governance",
42
+ severity: "warning",
43
+ file: "src/Nonexistent.tsx",
44
+ line: 1,
45
+ column: 1,
46
+ snippet: "some code",
47
+ message: "Missing error state",
48
+ }];
49
+ const result = await validateProposedFindings(proposed, repoRoot);
50
+ expect(result.findings).toHaveLength(0);
51
+ expect(result.droppedHallucinations).toBe(1);
52
+ });
53
+ it("drops finding when snippet not found in file", async () => {
54
+ const repoRoot = makeRepoRoot();
55
+ const srcDir = join(repoRoot, "src");
56
+ mkdirSync(srcDir);
57
+ writeFileSync(join(srcDir, "Real.tsx"), "export function Real() { return null; }");
58
+ const proposed = [{
59
+ ruleId: "ai-governance/ai-loading-error-states",
60
+ axis: "ai-governance",
61
+ severity: "warning",
62
+ file: "src/Real.tsx",
63
+ line: 1,
64
+ column: 1,
65
+ snippet: "this snippet does not exist in the file",
66
+ message: "Missing error state",
67
+ }];
68
+ const result = await validateProposedFindings(proposed, repoRoot);
69
+ expect(result.findings).toHaveLength(0);
70
+ expect(result.droppedHallucinations).toBe(1);
71
+ });
72
+ it("counts multiple drops independently", async () => {
73
+ const repoRoot = makeRepoRoot();
74
+ const srcDir = join(repoRoot, "src");
75
+ mkdirSync(srcDir);
76
+ writeFileSync(join(srcDir, "Real.tsx"), "export function Real() { return null; }");
77
+ const proposed = [
78
+ {
79
+ ruleId: "ai-governance/ai-loading-error-states",
80
+ axis: "ai-governance",
81
+ severity: "warning",
82
+ file: "src/Real.tsx",
83
+ line: 1,
84
+ column: 1,
85
+ snippet: "export function Real()",
86
+ message: "ok",
87
+ },
88
+ {
89
+ ruleId: "ai-governance/ai-loading-error-states",
90
+ axis: "ai-governance",
91
+ severity: "warning",
92
+ file: "src/Ghost.tsx",
93
+ line: 1,
94
+ column: 1,
95
+ snippet: "phantom code",
96
+ message: "hallucination 1",
97
+ },
98
+ {
99
+ ruleId: "ai-governance/ai-loading-error-states",
100
+ axis: "ai-governance",
101
+ severity: "warning",
102
+ file: "src/Real.tsx",
103
+ line: 1,
104
+ column: 1,
105
+ snippet: "wrong snippet here",
106
+ message: "hallucination 2",
107
+ },
108
+ ];
109
+ const result = await validateProposedFindings(proposed, repoRoot);
110
+ expect(result.findings).toHaveLength(1);
111
+ expect(result.droppedHallucinations).toBe(2);
112
+ });
113
+ it("converts validated ProposedFinding to proper Finding shape", async () => {
114
+ const repoRoot = makeRepoRoot();
115
+ writeFileSync(join(repoRoot, "Foo.tsx"), "const x = 1;");
116
+ const proposed = [{
117
+ ruleId: "ai-governance/ai-marker-component-present",
118
+ axis: "ai-governance",
119
+ severity: "error",
120
+ file: "Foo.tsx",
121
+ line: 3,
122
+ column: 5,
123
+ snippet: "const x = 1;",
124
+ message: "Missing marker",
125
+ suggestion: "Add AiMarker component",
126
+ }];
127
+ const result = await validateProposedFindings(proposed, repoRoot);
128
+ expect(result.findings[0]).toMatchObject({
129
+ ruleId: "ai-governance/ai-marker-component-present",
130
+ axis: "ai-governance",
131
+ severity: "error",
132
+ location: { file: "Foo.tsx", line: 3, column: 5 },
133
+ message: "Missing marker",
134
+ suggestion: "Add AiMarker component",
135
+ });
136
+ });
137
+ it("drops finding with path traversal attempt", async () => {
138
+ const repoRoot = makeRepoRoot();
139
+ const outsideFile = join(tmpdir(), "secret.txt");
140
+ writeFileSync(outsideFile, "SECRET_KEY=abc123");
141
+ const proposed = [{
142
+ ruleId: "ai-governance/ai-loading-error-states",
143
+ axis: "ai-governance",
144
+ severity: "warning",
145
+ file: "../../secret.txt",
146
+ line: 1,
147
+ column: 1,
148
+ snippet: "SECRET_KEY=abc123",
149
+ message: "Traversal attempt",
150
+ }];
151
+ const result = await validateProposedFindings(proposed, repoRoot);
152
+ expect(result.findings).toHaveLength(0);
153
+ expect(result.droppedHallucinations).toBe(1);
154
+ });
155
+ });
156
+ //# sourceMappingURL=validator.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.test.js","sourceRoot":"","sources":["../../../src/llm/__tests__/validator.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { AnthropicAdapter } from "../anthropic-adapter.js";
3
+ function mockAnthropicFetch(responseText, model) {
4
+ const headers = new Headers({ "content-type": "application/json" });
5
+ return vi.fn().mockResolvedValue({
6
+ ok: true,
7
+ status: 200,
8
+ headers,
9
+ json: async () => ({
10
+ id: "msg_01",
11
+ type: "message",
12
+ role: "assistant",
13
+ content: [{ type: "text", text: responseText }],
14
+ model,
15
+ stop_reason: "end_turn",
16
+ usage: { input_tokens: 100, output_tokens: 50 },
17
+ }),
18
+ });
19
+ }
20
+ describe("AnthropicAdapter", () => {
21
+ it("returns text from a mock response", async () => {
22
+ const fetch = mockAnthropicFetch("audit result", "claude-sonnet-4-6");
23
+ const adapter = new AnthropicAdapter({
24
+ apiKey: "sk-ant-test",
25
+ model: "claude-sonnet-4-6",
26
+ fetchFn: fetch,
27
+ });
28
+ const result = await adapter.complete([{ role: "user", content: "run audit" }]);
29
+ expect(result.text).toBe("audit result");
30
+ expect(result.modelUsed).toBe("claude-sonnet-4-6");
31
+ expect(result.cacheHit).toBe(false);
32
+ expect(result.llmQuality).toBe("higher");
33
+ });
34
+ it("does NOT log or include the API key in any observable way", async () => {
35
+ const consoleSpy = vi.spyOn(console, "log");
36
+ const consoleErrorSpy = vi.spyOn(console, "error");
37
+ const consoleWarnSpy = vi.spyOn(console, "warn");
38
+ const stderrSpy = vi.spyOn(process.stderr, "write");
39
+ const fetch = mockAnthropicFetch("ok", "claude-haiku-3");
40
+ const adapter = new AnthropicAdapter({
41
+ apiKey: "sk-ant-real-secret",
42
+ model: "claude-haiku-3",
43
+ fetchFn: fetch,
44
+ });
45
+ await adapter.complete([{ role: "user", content: "x" }]);
46
+ const allOutput = [
47
+ ...consoleSpy.mock.calls,
48
+ ...consoleErrorSpy.mock.calls,
49
+ ...consoleWarnSpy.mock.calls,
50
+ ...stderrSpy.mock.calls,
51
+ ]
52
+ .flat()
53
+ .join(" ");
54
+ expect(allOutput).not.toContain("sk-ant-real-secret");
55
+ consoleSpy.mockRestore();
56
+ consoleErrorSpy.mockRestore();
57
+ consoleWarnSpy.mockRestore();
58
+ stderrSpy.mockRestore();
59
+ });
60
+ it("computes usdSpent from usage (non-zero)", async () => {
61
+ const fetch = mockAnthropicFetch("ok", "claude-sonnet-4-6");
62
+ const adapter = new AnthropicAdapter({
63
+ apiKey: "sk-ant-test",
64
+ model: "claude-sonnet-4-6",
65
+ fetchFn: fetch,
66
+ });
67
+ const result = await adapter.complete([{ role: "user", content: "x" }]);
68
+ expect(result.usdSpent).toBeGreaterThan(0);
69
+ });
70
+ it("haiku call costs less than sonnet call for same token usage", async () => {
71
+ const fetchHaiku = mockAnthropicFetch("ok", "claude-haiku-3-5");
72
+ const fetchSonnet = mockAnthropicFetch("ok", "claude-sonnet-4-6");
73
+ const haikuAdapter = new AnthropicAdapter({
74
+ apiKey: "sk-ant-test",
75
+ model: "claude-haiku-3-5",
76
+ fetchFn: fetchHaiku,
77
+ });
78
+ const sonnetAdapter = new AnthropicAdapter({
79
+ apiKey: "sk-ant-test",
80
+ model: "claude-sonnet-4-6",
81
+ fetchFn: fetchSonnet,
82
+ });
83
+ const msgs = [{ role: "user", content: "x" }];
84
+ const haikuResult = await haikuAdapter.complete(msgs);
85
+ const sonnetResult = await sonnetAdapter.complete(msgs);
86
+ expect(haikuResult.usdSpent).toBeLessThan(sonnetResult.usdSpent);
87
+ });
88
+ it("rejects when the transport throws an error", async () => {
89
+ const fetch = vi.fn().mockRejectedValue(new Error("network failure"));
90
+ const adapter = new AnthropicAdapter({
91
+ apiKey: "sk-ant-test",
92
+ model: "claude-sonnet-4-6",
93
+ fetchFn: fetch,
94
+ });
95
+ // The SDK may wrap the error (e.g. "Connection error."); assert it still rejects.
96
+ await expect(adapter.complete([{ role: "user", content: "x" }])).rejects.toThrow();
97
+ });
98
+ });
99
+ //# sourceMappingURL=anthropic-adapter.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic-adapter.test.js","sourceRoot":"","sources":["../../../../src/llm/connectors/__tests__/anthropic-adapter.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,SAAS,kBAAkB,CAAC,YAAoB,EAAE,KAAa;IAC7D,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;IACpE,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC;QAC/B,EAAE,EAAE,IAAI;QACR,MAAM,EAAE,GAAG;QACX,OAAO;QACP,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;YACjB,EAAE,EAAE,QAAQ;YACZ,IAAI,EAAE,SAAS;YACf,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;YAC/C,KAAK;YACL,WAAW,EAAE,UAAU;YACvB,KAAK,EAAE,EAAE,YAAY,EAAE,GAAG,EAAE,aAAa,EAAE,EAAE,EAAE;SAChD,CAAC;KACH,CAAC,CAAC;AACL,CAAC;AAED,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,mCAAmC,EAAE,KAAK,IAAI,EAAE;QACjD,MAAM,KAAK,GAAG,kBAAkB,CAAC,cAAc,EAAE,mBAAmB,CAAC,CAAC;QACtE,MAAM,OAAO,GAAG,IAAI,gBAAgB,CAAC;YACnC,MAAM,EAAE,aAAa;YACrB,KAAK,EAAE,mBAAmB;YAC1B,OAAO,EAAE,KAA2C;SACrD,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;QAChF,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACzC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;QACnD,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;QACzE,MAAM,UAAU,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAC5C,MAAM,eAAe,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACnD,MAAM,cAAc,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACjD,MAAM,SAAS,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACpD,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;QACzD,MAAM,OAAO,GAAG,IAAI,gBAAgB,CAAC;YACnC,MAAM,EAAE,oBAAoB;YAC5B,KAAK,EAAE,gBAAgB;YACvB,OAAO,EAAE,KAA2C;SACrD,CAAC,CAAC;QACH,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;QACzD,MAAM,SAAS,GAAG;YAChB,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK;YACxB,GAAG,eAAe,CAAC,IAAI,CAAC,KAAK;YAC7B,GAAG,cAAc,CAAC,IAAI,CAAC,KAAK;YAC5B,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK;SACxB;aACE,IAAI,EAAE;aACN,IAAI,CAAC,GAAG,CAAC,CAAC;QACb,MAAM,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;QACtD,UAAU,CAAC,WAAW,EAAE,CAAC;QACzB,eAAe,CAAC,WAAW,EAAE,CAAC;QAC9B,cAAc,CAAC,WAAW,EAAE,CAAC;QAC7B,SAAS,CAAC,WAAW,EAAE,CAAC;IAC1B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;QACvD,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;QAC5D,MAAM,OAAO,GAAG,IAAI,gBAAgB,CAAC;YACnC,MAAM,EAAE,aAAa;YACrB,KAAK,EAAE,mBAAmB;YAC1B,OAAO,EAAE,KAA2C;SACrD,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;QACxE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,KAAK,IAAI,EAAE;QAC3E,MAAM,UAAU,GAAG,kBAAkB,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC;QAChE,MAAM,WAAW,GAAG,kBAAkB,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;QAElE,MAAM,YAAY,GAAG,IAAI,gBAAgB,CAAC;YACxC,MAAM,EAAE,aAAa;YACrB,KAAK,EAAE,kBAAkB;YACzB,OAAO,EAAE,UAAgD;SAC1D,CAAC,CAAC;QACH,MAAM,aAAa,GAAG,IAAI,gBAAgB,CAAC;YACzC,MAAM,EAAE,aAAa;YACrB,KAAK,EAAE,mBAAmB;YAC1B,OAAO,EAAE,WAAiD;SAC3D,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;QACvD,MAAM,WAAW,GAAG,MAAM,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACtD,MAAM,YAAY,GAAG,MAAM,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAExD,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;QAC1D,MAAM,KAAK,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC;QACtE,MAAM,OAAO,GAAG,IAAI,gBAAgB,CAAC;YACnC,MAAM,EAAE,aAAa;YACrB,KAAK,EAAE,mBAAmB;YAC1B,OAAO,EAAE,KAA2C;SACrD,CAAC,CAAC;QACH,kFAAkF;QAClF,MAAM,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;IACrF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,50 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { mkdtempSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { ResponseCache } from "../cache.js";
6
+ const MESSAGES = [{ role: "user", content: "hello" }];
7
+ const MODEL = "gpt-4o-mini";
8
+ const RESULT = {
9
+ text: "world",
10
+ usdSpent: 0.001,
11
+ modelUsed: MODEL,
12
+ llmQuality: "higher",
13
+ cacheHit: false,
14
+ };
15
+ function makeCache(maxAgeDays) {
16
+ const dir = mkdtempSync(join(tmpdir(), "lyse-cache-"));
17
+ return new ResponseCache({ cacheDir: dir, maxAgeDays });
18
+ }
19
+ describe("ResponseCache", () => {
20
+ it("returns null on cache miss", async () => {
21
+ const cache = makeCache(7);
22
+ const hit = await cache.get(MODEL, MESSAGES);
23
+ expect(hit).toBeNull();
24
+ });
25
+ it("returns a hit after set, with cacheHit: true and usdSpent: 0", async () => {
26
+ const cache = makeCache(7);
27
+ await cache.set(MODEL, MESSAGES, RESULT);
28
+ const hit = await cache.get(MODEL, MESSAGES);
29
+ expect(hit).not.toBeNull();
30
+ expect(hit.cacheHit).toBe(true);
31
+ expect(hit.usdSpent).toBe(0);
32
+ expect(hit.text).toBe("world");
33
+ expect(hit.modelUsed).toBe(MODEL);
34
+ });
35
+ it("returns null for an expired entry", async () => {
36
+ const cache = makeCache(0);
37
+ await cache.set(MODEL, MESSAGES, RESULT);
38
+ const hit = await cache.get(MODEL, MESSAGES);
39
+ expect(hit).toBeNull();
40
+ });
41
+ it("produces identical cache keys regardless of message array reference", async () => {
42
+ const cache = makeCache(7);
43
+ const msgs1 = [{ role: "user", content: "hello" }];
44
+ const msgs2 = [{ role: "user", content: "hello" }];
45
+ await cache.set(MODEL, msgs1, RESULT);
46
+ const hit = await cache.get(MODEL, msgs2);
47
+ expect(hit).not.toBeNull();
48
+ });
49
+ });
50
+ //# sourceMappingURL=cache.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.test.js","sourceRoot":"","sources":["../../../../src/llm/connectors/__tests__/cache.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { NoopAdapter } from "../noop-adapter.js";
3
+ describe("NoopAdapter", () => {
4
+ it("returns empty text, zero cost, cacheHit false", async () => {
5
+ const client = new NoopAdapter();
6
+ const result = await client.complete([{ role: "user", content: "audit this repo" }]);
7
+ expect(result.text).toBe("");
8
+ expect(result.usdSpent).toBe(0);
9
+ expect(result.cacheHit).toBe(false);
10
+ expect(result.modelUsed).toBe("none");
11
+ expect(result.llmQuality).toBe("lower");
12
+ });
13
+ it("ignores options and always returns the same shape", async () => {
14
+ const client = new NoopAdapter();
15
+ const r1 = await client.complete([], { estimateUsd: 99 });
16
+ const r2 = await client.complete([{ role: "system", content: "x" }]);
17
+ expect(r1.usdSpent).toBe(0);
18
+ expect(r2.usdSpent).toBe(0);
19
+ });
20
+ });
21
+ //# sourceMappingURL=noop-adapter.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"noop-adapter.test.js","sourceRoot":"","sources":["../../../../src/llm/connectors/__tests__/noop-adapter.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC7D,MAAM,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAC;QACrF,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC7B,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAChC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;QACjE,MAAM,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;QACjC,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC;QAC1D,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;QACrE,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC5B,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}