@qulib/core 0.1.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 (99) hide show
  1. package/README.md +146 -0
  2. package/bin/qulib.js +17 -0
  3. package/dist/adapters/adapter-factory.d.ts +5 -0
  4. package/dist/adapters/adapter-factory.d.ts.map +1 -0
  5. package/dist/adapters/adapter-factory.js +21 -0
  6. package/dist/adapters/adapter.interface.d.ts +7 -0
  7. package/dist/adapters/adapter.interface.d.ts.map +1 -0
  8. package/dist/adapters/adapter.interface.js +1 -0
  9. package/dist/adapters/api-adapter.d.ts +8 -0
  10. package/dist/adapters/api-adapter.d.ts.map +1 -0
  11. package/dist/adapters/api-adapter.js +9 -0
  12. package/dist/adapters/cypress-component-adapter.d.ts +8 -0
  13. package/dist/adapters/cypress-component-adapter.d.ts.map +1 -0
  14. package/dist/adapters/cypress-component-adapter.js +9 -0
  15. package/dist/adapters/cypress-e2e-adapter.d.ts +8 -0
  16. package/dist/adapters/cypress-e2e-adapter.d.ts.map +1 -0
  17. package/dist/adapters/cypress-e2e-adapter.js +9 -0
  18. package/dist/adapters/playwright-adapter.d.ts +8 -0
  19. package/dist/adapters/playwright-adapter.d.ts.map +1 -0
  20. package/dist/adapters/playwright-adapter.js +9 -0
  21. package/dist/analyze.d.ts +20 -0
  22. package/dist/analyze.d.ts.map +1 -0
  23. package/dist/analyze.js +21 -0
  24. package/dist/cli/index.d.ts +3 -0
  25. package/dist/cli/index.d.ts.map +1 -0
  26. package/dist/cli/index.js +102 -0
  27. package/dist/harness/decision-logger.d.ts +7 -0
  28. package/dist/harness/decision-logger.d.ts.map +1 -0
  29. package/dist/harness/decision-logger.js +68 -0
  30. package/dist/harness/run-options.d.ts +6 -0
  31. package/dist/harness/run-options.d.ts.map +1 -0
  32. package/dist/harness/run-options.js +1 -0
  33. package/dist/harness/state-manager.d.ts +6 -0
  34. package/dist/harness/state-manager.d.ts.map +1 -0
  35. package/dist/harness/state-manager.js +64 -0
  36. package/dist/index.d.ts +4 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +1 -0
  39. package/dist/llm/context-builder.d.ts +3 -0
  40. package/dist/llm/context-builder.d.ts.map +1 -0
  41. package/dist/llm/context-builder.js +33 -0
  42. package/dist/llm/provider.d.ts +4 -0
  43. package/dist/llm/provider.d.ts.map +1 -0
  44. package/dist/llm/provider.js +56 -0
  45. package/dist/phases/act.d.ts +5 -0
  46. package/dist/phases/act.d.ts.map +1 -0
  47. package/dist/phases/act.js +41 -0
  48. package/dist/phases/observe.d.ts +10 -0
  49. package/dist/phases/observe.d.ts.map +1 -0
  50. package/dist/phases/observe.js +48 -0
  51. package/dist/phases/think.d.ts +6 -0
  52. package/dist/phases/think.d.ts.map +1 -0
  53. package/dist/phases/think.js +85 -0
  54. package/dist/reporters/json-reporter.d.ts +3 -0
  55. package/dist/reporters/json-reporter.d.ts.map +1 -0
  56. package/dist/reporters/json-reporter.js +8 -0
  57. package/dist/reporters/markdown-reporter.d.ts +3 -0
  58. package/dist/reporters/markdown-reporter.d.ts.map +1 -0
  59. package/dist/reporters/markdown-reporter.js +42 -0
  60. package/dist/schemas/config.schema.d.ts +327 -0
  61. package/dist/schemas/config.schema.d.ts.map +1 -0
  62. package/dist/schemas/config.schema.js +39 -0
  63. package/dist/schemas/decision-log.schema.d.ts +22 -0
  64. package/dist/schemas/decision-log.schema.d.ts.map +1 -0
  65. package/dist/schemas/decision-log.schema.js +8 -0
  66. package/dist/schemas/gap-analysis.schema.d.ts +363 -0
  67. package/dist/schemas/gap-analysis.schema.d.ts.map +1 -0
  68. package/dist/schemas/gap-analysis.schema.js +60 -0
  69. package/dist/schemas/index.d.ts +6 -0
  70. package/dist/schemas/index.d.ts.map +1 -0
  71. package/dist/schemas/index.js +5 -0
  72. package/dist/schemas/repo-analysis.schema.d.ts +165 -0
  73. package/dist/schemas/repo-analysis.schema.d.ts.map +1 -0
  74. package/dist/schemas/repo-analysis.schema.js +29 -0
  75. package/dist/schemas/route-inventory.schema.d.ts +241 -0
  76. package/dist/schemas/route-inventory.schema.d.ts.map +1 -0
  77. package/dist/schemas/route-inventory.schema.js +30 -0
  78. package/dist/tools/auth.d.ts +4 -0
  79. package/dist/tools/auth.d.ts.map +1 -0
  80. package/dist/tools/auth.js +35 -0
  81. package/dist/tools/cypress-explorer.d.ts +7 -0
  82. package/dist/tools/cypress-explorer.d.ts.map +1 -0
  83. package/dist/tools/cypress-explorer.js +5 -0
  84. package/dist/tools/explorer-factory.d.ts +4 -0
  85. package/dist/tools/explorer-factory.d.ts.map +1 -0
  86. package/dist/tools/explorer-factory.js +12 -0
  87. package/dist/tools/explorer.interface.d.ts +6 -0
  88. package/dist/tools/explorer.interface.d.ts.map +1 -0
  89. package/dist/tools/explorer.interface.js +1 -0
  90. package/dist/tools/gap-engine.d.ts +6 -0
  91. package/dist/tools/gap-engine.d.ts.map +1 -0
  92. package/dist/tools/gap-engine.js +101 -0
  93. package/dist/tools/playwright-explorer.d.ts +7 -0
  94. package/dist/tools/playwright-explorer.d.ts.map +1 -0
  95. package/dist/tools/playwright-explorer.js +150 -0
  96. package/dist/tools/repo-scanner.d.ts +3 -0
  97. package/dist/tools/repo-scanner.d.ts.map +1 -0
  98. package/dist/tools/repo-scanner.js +147 -0
  99. package/package.json +54 -0
@@ -0,0 +1,241 @@
1
+ import { z } from 'zod';
2
+ export declare const A11yViolationSchema: z.ZodObject<{
3
+ id: z.ZodString;
4
+ impact: z.ZodString;
5
+ helpUrl: z.ZodString;
6
+ nodeCount: z.ZodNumber;
7
+ }, "strip", z.ZodTypeAny, {
8
+ id: string;
9
+ impact: string;
10
+ helpUrl: string;
11
+ nodeCount: number;
12
+ }, {
13
+ id: string;
14
+ impact: string;
15
+ helpUrl: string;
16
+ nodeCount: number;
17
+ }>;
18
+ export declare const BrokenLinkSchema: z.ZodObject<{
19
+ url: z.ZodString;
20
+ status: z.ZodNullable<z.ZodNumber>;
21
+ reason: z.ZodOptional<z.ZodString>;
22
+ }, "strip", z.ZodTypeAny, {
23
+ status: number | null;
24
+ url: string;
25
+ reason?: string | undefined;
26
+ }, {
27
+ status: number | null;
28
+ url: string;
29
+ reason?: string | undefined;
30
+ }>;
31
+ export declare const RouteSchema: z.ZodObject<{
32
+ path: z.ZodString;
33
+ pageTitle: z.ZodString;
34
+ links: z.ZodArray<z.ZodString, "many">;
35
+ formCount: z.ZodNumber;
36
+ buttonLabels: z.ZodArray<z.ZodString, "many">;
37
+ consoleErrors: z.ZodArray<z.ZodString, "many">;
38
+ brokenLinks: z.ZodArray<z.ZodObject<{
39
+ url: z.ZodString;
40
+ status: z.ZodNullable<z.ZodNumber>;
41
+ reason: z.ZodOptional<z.ZodString>;
42
+ }, "strip", z.ZodTypeAny, {
43
+ status: number | null;
44
+ url: string;
45
+ reason?: string | undefined;
46
+ }, {
47
+ status: number | null;
48
+ url: string;
49
+ reason?: string | undefined;
50
+ }>, "many">;
51
+ a11yViolations: z.ZodArray<z.ZodObject<{
52
+ id: z.ZodString;
53
+ impact: z.ZodString;
54
+ helpUrl: z.ZodString;
55
+ nodeCount: z.ZodNumber;
56
+ }, "strip", z.ZodTypeAny, {
57
+ id: string;
58
+ impact: string;
59
+ helpUrl: string;
60
+ nodeCount: number;
61
+ }, {
62
+ id: string;
63
+ impact: string;
64
+ helpUrl: string;
65
+ nodeCount: number;
66
+ }>, "many">;
67
+ statusCode: z.ZodOptional<z.ZodNumber>;
68
+ }, "strip", z.ZodTypeAny, {
69
+ path: string;
70
+ pageTitle: string;
71
+ links: string[];
72
+ formCount: number;
73
+ buttonLabels: string[];
74
+ consoleErrors: string[];
75
+ brokenLinks: {
76
+ status: number | null;
77
+ url: string;
78
+ reason?: string | undefined;
79
+ }[];
80
+ a11yViolations: {
81
+ id: string;
82
+ impact: string;
83
+ helpUrl: string;
84
+ nodeCount: number;
85
+ }[];
86
+ statusCode?: number | undefined;
87
+ }, {
88
+ path: string;
89
+ pageTitle: string;
90
+ links: string[];
91
+ formCount: number;
92
+ buttonLabels: string[];
93
+ consoleErrors: string[];
94
+ brokenLinks: {
95
+ status: number | null;
96
+ url: string;
97
+ reason?: string | undefined;
98
+ }[];
99
+ a11yViolations: {
100
+ id: string;
101
+ impact: string;
102
+ helpUrl: string;
103
+ nodeCount: number;
104
+ }[];
105
+ statusCode?: number | undefined;
106
+ }>;
107
+ export declare const RouteInventorySchema: z.ZodObject<{
108
+ scannedAt: z.ZodString;
109
+ baseUrl: z.ZodString;
110
+ routes: z.ZodArray<z.ZodObject<{
111
+ path: z.ZodString;
112
+ pageTitle: z.ZodString;
113
+ links: z.ZodArray<z.ZodString, "many">;
114
+ formCount: z.ZodNumber;
115
+ buttonLabels: z.ZodArray<z.ZodString, "many">;
116
+ consoleErrors: z.ZodArray<z.ZodString, "many">;
117
+ brokenLinks: z.ZodArray<z.ZodObject<{
118
+ url: z.ZodString;
119
+ status: z.ZodNullable<z.ZodNumber>;
120
+ reason: z.ZodOptional<z.ZodString>;
121
+ }, "strip", z.ZodTypeAny, {
122
+ status: number | null;
123
+ url: string;
124
+ reason?: string | undefined;
125
+ }, {
126
+ status: number | null;
127
+ url: string;
128
+ reason?: string | undefined;
129
+ }>, "many">;
130
+ a11yViolations: z.ZodArray<z.ZodObject<{
131
+ id: z.ZodString;
132
+ impact: z.ZodString;
133
+ helpUrl: z.ZodString;
134
+ nodeCount: z.ZodNumber;
135
+ }, "strip", z.ZodTypeAny, {
136
+ id: string;
137
+ impact: string;
138
+ helpUrl: string;
139
+ nodeCount: number;
140
+ }, {
141
+ id: string;
142
+ impact: string;
143
+ helpUrl: string;
144
+ nodeCount: number;
145
+ }>, "many">;
146
+ statusCode: z.ZodOptional<z.ZodNumber>;
147
+ }, "strip", z.ZodTypeAny, {
148
+ path: string;
149
+ pageTitle: string;
150
+ links: string[];
151
+ formCount: number;
152
+ buttonLabels: string[];
153
+ consoleErrors: string[];
154
+ brokenLinks: {
155
+ status: number | null;
156
+ url: string;
157
+ reason?: string | undefined;
158
+ }[];
159
+ a11yViolations: {
160
+ id: string;
161
+ impact: string;
162
+ helpUrl: string;
163
+ nodeCount: number;
164
+ }[];
165
+ statusCode?: number | undefined;
166
+ }, {
167
+ path: string;
168
+ pageTitle: string;
169
+ links: string[];
170
+ formCount: number;
171
+ buttonLabels: string[];
172
+ consoleErrors: string[];
173
+ brokenLinks: {
174
+ status: number | null;
175
+ url: string;
176
+ reason?: string | undefined;
177
+ }[];
178
+ a11yViolations: {
179
+ id: string;
180
+ impact: string;
181
+ helpUrl: string;
182
+ nodeCount: number;
183
+ }[];
184
+ statusCode?: number | undefined;
185
+ }>, "many">;
186
+ pagesSkipped: z.ZodNumber;
187
+ budgetExceeded: z.ZodBoolean;
188
+ }, "strip", z.ZodTypeAny, {
189
+ scannedAt: string;
190
+ baseUrl: string;
191
+ routes: {
192
+ path: string;
193
+ pageTitle: string;
194
+ links: string[];
195
+ formCount: number;
196
+ buttonLabels: string[];
197
+ consoleErrors: string[];
198
+ brokenLinks: {
199
+ status: number | null;
200
+ url: string;
201
+ reason?: string | undefined;
202
+ }[];
203
+ a11yViolations: {
204
+ id: string;
205
+ impact: string;
206
+ helpUrl: string;
207
+ nodeCount: number;
208
+ }[];
209
+ statusCode?: number | undefined;
210
+ }[];
211
+ pagesSkipped: number;
212
+ budgetExceeded: boolean;
213
+ }, {
214
+ scannedAt: string;
215
+ baseUrl: string;
216
+ routes: {
217
+ path: string;
218
+ pageTitle: string;
219
+ links: string[];
220
+ formCount: number;
221
+ buttonLabels: string[];
222
+ consoleErrors: string[];
223
+ brokenLinks: {
224
+ status: number | null;
225
+ url: string;
226
+ reason?: string | undefined;
227
+ }[];
228
+ a11yViolations: {
229
+ id: string;
230
+ impact: string;
231
+ helpUrl: string;
232
+ nodeCount: number;
233
+ }[];
234
+ statusCode?: number | undefined;
235
+ }[];
236
+ pagesSkipped: number;
237
+ budgetExceeded: boolean;
238
+ }>;
239
+ export type RouteInventory = z.infer<typeof RouteInventorySchema>;
240
+ export type Route = z.infer<typeof RouteSchema>;
241
+ //# sourceMappingURL=route-inventory.schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-inventory.schema.d.ts","sourceRoot":"","sources":["../../src/schemas/route-inventory.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;EAK9B,CAAC;AAEH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;EAI3B,CAAC;AAEH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAUtB,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAM/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAClE,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC"}
@@ -0,0 +1,30 @@
1
+ import { z } from 'zod';
2
+ export const A11yViolationSchema = z.object({
3
+ id: z.string(),
4
+ impact: z.string(),
5
+ helpUrl: z.string(),
6
+ nodeCount: z.number().int(),
7
+ });
8
+ export const BrokenLinkSchema = z.object({
9
+ url: z.string(),
10
+ status: z.number().nullable(),
11
+ reason: z.string().optional(),
12
+ });
13
+ export const RouteSchema = z.object({
14
+ path: z.string(),
15
+ pageTitle: z.string(),
16
+ links: z.array(z.string()),
17
+ formCount: z.number().int(),
18
+ buttonLabels: z.array(z.string()),
19
+ consoleErrors: z.array(z.string()),
20
+ brokenLinks: z.array(BrokenLinkSchema),
21
+ a11yViolations: z.array(A11yViolationSchema),
22
+ statusCode: z.number().int().optional(),
23
+ });
24
+ export const RouteInventorySchema = z.object({
25
+ scannedAt: z.string().datetime(),
26
+ baseUrl: z.string().url(),
27
+ routes: z.array(RouteSchema),
28
+ pagesSkipped: z.number().int(),
29
+ budgetExceeded: z.boolean(),
30
+ });
@@ -0,0 +1,4 @@
1
+ import type { Browser, BrowserContext } from '@playwright/test';
2
+ import type { AuthConfig } from '../schemas/config.schema.js';
3
+ export declare function createAuthenticatedContext(browser: Browser, auth: AuthConfig | undefined, timeoutMs: number): Promise<BrowserContext>;
4
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/tools/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEhE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAE9D,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,UAAU,GAAG,SAAS,EAC5B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,CAAC,CAsCzB"}
@@ -0,0 +1,35 @@
1
+ import { resolve } from 'node:path';
2
+ export async function createAuthenticatedContext(browser, auth, timeoutMs) {
3
+ if (!auth) {
4
+ return browser.newContext();
5
+ }
6
+ if (auth.type === 'storage-state') {
7
+ const storagePath = resolve(process.cwd(), auth.path);
8
+ return browser.newContext({ storageState: storagePath });
9
+ }
10
+ const context = await browser.newContext();
11
+ const page = await context.newPage();
12
+ try {
13
+ await page.goto(auth.loginUrl, { timeout: timeoutMs, waitUntil: 'domcontentloaded' });
14
+ await page.fill(auth.selectors.username, auth.credentials.username);
15
+ await page.fill(auth.selectors.password, auth.credentials.password);
16
+ await page.click(auth.selectors.submit);
17
+ const urlFragment = auth.successIndicator.urlContains;
18
+ if (urlFragment) {
19
+ await page.waitForURL((url) => url.toString().includes(urlFragment), {
20
+ timeout: timeoutMs,
21
+ });
22
+ }
23
+ const visibleSelector = auth.successIndicator.selectorVisible;
24
+ if (visibleSelector) {
25
+ await page.waitForSelector(visibleSelector, {
26
+ timeout: timeoutMs,
27
+ state: 'visible',
28
+ });
29
+ }
30
+ }
31
+ finally {
32
+ await page.close();
33
+ }
34
+ return context;
35
+ }
@@ -0,0 +1,7 @@
1
+ import type { AppExplorer } from './explorer.interface.js';
2
+ import type { HarnessConfig } from '../schemas/config.schema.js';
3
+ import type { RouteInventory } from '../schemas/route-inventory.schema.js';
4
+ export declare class CypressExplorer implements AppExplorer {
5
+ explore(baseUrl: string, config: HarnessConfig): Promise<RouteInventory>;
6
+ }
7
+ //# sourceMappingURL=cypress-explorer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cypress-explorer.d.ts","sourceRoot":"","sources":["../../src/tools/cypress-explorer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAE3E,qBAAa,eAAgB,YAAW,WAAW;IAC3C,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;CAG/E"}
@@ -0,0 +1,5 @@
1
+ export class CypressExplorer {
2
+ async explore(baseUrl, config) {
3
+ throw new Error('Not implemented');
4
+ }
5
+ }
@@ -0,0 +1,4 @@
1
+ import type { ExplorerType } from '../schemas/config.schema.js';
2
+ import type { AppExplorer } from './explorer.interface.js';
3
+ export declare function createExplorer(type: ExplorerType): AppExplorer;
4
+ //# sourceMappingURL=explorer-factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explorer-factory.d.ts","sourceRoot":"","sources":["../../src/tools/explorer-factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAI3D,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,WAAW,CAS9D"}
@@ -0,0 +1,12 @@
1
+ import { PlaywrightExplorer } from './playwright-explorer.js';
2
+ import { CypressExplorer } from './cypress-explorer.js';
3
+ export function createExplorer(type) {
4
+ switch (type) {
5
+ case 'playwright':
6
+ return new PlaywrightExplorer();
7
+ case 'cypress':
8
+ return new CypressExplorer();
9
+ default:
10
+ throw new Error(`Unknown explorer type: ${type}`);
11
+ }
12
+ }
@@ -0,0 +1,6 @@
1
+ import type { HarnessConfig } from '../schemas/config.schema.js';
2
+ import type { RouteInventory } from '../schemas/route-inventory.schema.js';
3
+ export interface AppExplorer {
4
+ explore(baseUrl: string, config: HarnessConfig): Promise<RouteInventory>;
5
+ }
6
+ //# sourceMappingURL=explorer.interface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explorer.interface.d.ts","sourceRoot":"","sources":["../../src/tools/explorer.interface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAE3E,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CAC1E"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import { type GapAnalysis } from '../schemas/gap-analysis.schema.js';
2
+ import type { RouteInventory } from '../schemas/route-inventory.schema.js';
3
+ import type { RepoAnalysis } from '../schemas/repo-analysis.schema.js';
4
+ import type { HarnessConfig } from '../schemas/config.schema.js';
5
+ export declare function analyzeGaps(routes: RouteInventory, repo: RepoAnalysis | null, mode: 'url-only' | 'url-repo', config: HarnessConfig): Omit<GapAnalysis, 'scenarios' | 'generatedTests'>;
6
+ //# sourceMappingURL=gap-engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gap-engine.d.ts","sourceRoot":"","sources":["../../src/tools/gap-engine.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,KAAK,WAAW,EAAY,MAAM,mCAAmC,CAAC;AAC1F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAC3E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAEjE,wBAAgB,WAAW,CACzB,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,YAAY,GAAG,IAAI,EACzB,IAAI,EAAE,UAAU,GAAG,UAAU,EAC7B,MAAM,EAAE,aAAa,GACpB,IAAI,CAAC,WAAW,EAAE,WAAW,GAAG,gBAAgB,CAAC,CA0GnD"}
@@ -0,0 +1,101 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { GapSchema } from '../schemas/gap-analysis.schema.js';
3
+ export function analyzeGaps(routes, repo, mode, config) {
4
+ const coveredPaths = new Set();
5
+ if (repo) {
6
+ for (const testFile of repo.testFiles) {
7
+ for (const path of testFile.coveredPaths) {
8
+ coveredPaths.add(path);
9
+ }
10
+ }
11
+ }
12
+ const gaps = [];
13
+ const addGap = (gap) => {
14
+ const validated = GapSchema.parse(gap);
15
+ gaps.push(validated);
16
+ };
17
+ let hasNavigationFailures = false;
18
+ for (const route of routes.routes) {
19
+ if (repo && !coveredPaths.has(route.path)) {
20
+ const highRisk = /checkout|payment|auth|login|order/i.test(route.path);
21
+ addGap({
22
+ id: randomUUID(),
23
+ path: route.path,
24
+ severity: highRisk ? 'high' : 'medium',
25
+ reason: `Route is not covered by existing tests: ${route.path}`,
26
+ category: 'untested-route',
27
+ });
28
+ }
29
+ const navErrors = route.consoleErrors.filter((e) => e.startsWith('Navigation error:'));
30
+ if (navErrors.length > 0) {
31
+ hasNavigationFailures = true;
32
+ addGap({
33
+ id: randomUUID(),
34
+ path: route.path,
35
+ severity: 'high',
36
+ reason: `Navigation failed: ${navErrors.join('; ')}`,
37
+ category: 'console-error',
38
+ });
39
+ }
40
+ else if (route.consoleErrors.length > 0) {
41
+ addGap({
42
+ id: randomUUID(),
43
+ path: route.path,
44
+ severity: 'high',
45
+ reason: `Console errors detected (${route.consoleErrors.length})`,
46
+ category: 'console-error',
47
+ });
48
+ }
49
+ if (route.brokenLinks.length > 0) {
50
+ addGap({
51
+ id: randomUUID(),
52
+ path: route.path,
53
+ severity: 'medium',
54
+ reason: `Broken or invalid links detected (${route.brokenLinks.length})`,
55
+ category: 'broken-link',
56
+ });
57
+ }
58
+ for (const violation of route.a11yViolations) {
59
+ const impact = violation.impact.toLowerCase();
60
+ const severity = impact === 'critical' || impact === 'serious'
61
+ ? 'high'
62
+ : impact === 'moderate'
63
+ ? 'medium'
64
+ : 'low';
65
+ addGap({
66
+ id: randomUUID(),
67
+ path: route.path,
68
+ severity,
69
+ reason: `A11y violation ${violation.id} (${violation.impact}): ${violation.helpUrl}`,
70
+ category: 'a11y',
71
+ });
72
+ }
73
+ }
74
+ const highCount = gaps.filter((g) => g.severity === 'high').length;
75
+ const mediumCount = gaps.filter((g) => g.severity === 'medium').length;
76
+ const lowCount = gaps.filter((g) => g.severity === 'low').length;
77
+ let releaseConfidence = Math.max(0, 100 - highCount * 20 - mediumCount * 8 - lowCount * 3);
78
+ const pagesScanned = routes.routes.length;
79
+ if (pagesScanned < config.minPagesForConfidence) {
80
+ releaseConfidence = Math.min(releaseConfidence, 40);
81
+ }
82
+ let coverageWarning;
83
+ if (routes.budgetExceeded) {
84
+ coverageWarning = 'budget-exceeded';
85
+ }
86
+ else if (hasNavigationFailures) {
87
+ coverageWarning = 'navigation-failures';
88
+ }
89
+ else if (pagesScanned < config.minPagesForConfidence) {
90
+ coverageWarning = 'low-coverage';
91
+ }
92
+ return {
93
+ analyzedAt: new Date().toISOString(),
94
+ mode,
95
+ releaseConfidence,
96
+ coveragePagesScanned: pagesScanned,
97
+ coverageBudgetExceeded: routes.budgetExceeded,
98
+ coverageWarning,
99
+ gaps,
100
+ };
101
+ }
@@ -0,0 +1,7 @@
1
+ import type { AppExplorer } from './explorer.interface.js';
2
+ import { type RouteInventory } from '../schemas/route-inventory.schema.js';
3
+ import type { HarnessConfig } from '../schemas/config.schema.js';
4
+ export declare class PlaywrightExplorer implements AppExplorer {
5
+ explore(baseUrl: string, config: HarnessConfig): Promise<RouteInventory>;
6
+ }
7
+ //# sourceMappingURL=playwright-explorer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playwright-explorer.d.ts","sourceRoot":"","sources":["../../src/tools/playwright-explorer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAAwB,KAAK,cAAc,EAAc,MAAM,sCAAsC,CAAC;AAC7G,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAEjE,qBAAa,kBAAmB,YAAW,WAAW;IAC9C,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;CA6J/E"}
@@ -0,0 +1,150 @@
1
+ import { chromium } from '@playwright/test';
2
+ import { AxeBuilder } from '@axe-core/playwright';
3
+ import { createAuthenticatedContext } from './auth.js';
4
+ import { RouteInventorySchema } from '../schemas/route-inventory.schema.js';
5
+ export class PlaywrightExplorer {
6
+ async explore(baseUrl, config) {
7
+ const browser = await chromium.launch({ headless: true });
8
+ let context;
9
+ try {
10
+ context = await createAuthenticatedContext(browser, config.auth, config.timeoutMs);
11
+ }
12
+ catch (err) {
13
+ await browser.close();
14
+ throw new Error(`Authentication failed: ${String(err)}. Check your auth config and credentials.`);
15
+ }
16
+ if (config.auth) {
17
+ const label = config.auth.type === 'form-login' ? config.auth.credentials.username : 'storage-state';
18
+ console.error(`[qulib] authenticated as ${label}`);
19
+ }
20
+ const visited = new Set();
21
+ const queue = [baseUrl];
22
+ const routes = [];
23
+ let budgetExceeded = false;
24
+ try {
25
+ while (queue.length > 0) {
26
+ if (visited.size >= config.maxPagesToScan) {
27
+ budgetExceeded = queue.length > 0;
28
+ break;
29
+ }
30
+ const url = queue.shift();
31
+ if (!url) {
32
+ continue;
33
+ }
34
+ const normalized = url.split('?')[0].split('#')[0];
35
+ if (visited.has(normalized))
36
+ continue;
37
+ visited.add(normalized);
38
+ const page = await context.newPage();
39
+ const consoleErrors = [];
40
+ page.on('console', (msg) => {
41
+ if (msg.type() === 'error') {
42
+ consoleErrors.push(msg.text());
43
+ }
44
+ });
45
+ try {
46
+ await page.goto(url, {
47
+ timeout: config.timeoutMs,
48
+ waitUntil: 'domcontentloaded',
49
+ });
50
+ const pageTitle = await page.title();
51
+ const formCount = await page.locator('form').count();
52
+ const buttonLabels = await page.locator('button').allInnerTexts();
53
+ const hrefs = await page.evaluate(() => Array.from(document.querySelectorAll('a[href]'))
54
+ .map((a) => a.href)
55
+ .filter(Boolean));
56
+ const base = new URL(baseUrl);
57
+ const internalLinks = hrefs
58
+ .filter((href) => {
59
+ try {
60
+ const u = new URL(href);
61
+ return u.origin === base.origin;
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ })
67
+ .map((href) => href.split('?')[0].split('#')[0]);
68
+ const uniqueInternal = [...new Set(internalLinks)];
69
+ for (const link of uniqueInternal) {
70
+ if (!visited.has(link) && !queue.includes(link)) {
71
+ queue.push(link);
72
+ }
73
+ }
74
+ const brokenLinks = [];
75
+ for (const link of uniqueInternal.slice(0, 20)) {
76
+ try {
77
+ const response = await page.request.head(link, { timeout: 5000 });
78
+ if (response.status() >= 400) {
79
+ brokenLinks.push({ url: link, status: response.status() });
80
+ }
81
+ }
82
+ catch (err) {
83
+ brokenLinks.push({ url: link, status: null, reason: String(err) });
84
+ }
85
+ }
86
+ let a11yViolations = [];
87
+ try {
88
+ const axeResults = await new AxeBuilder({ page })
89
+ .withTags(['wcag2a', 'wcag2aa'])
90
+ .analyze();
91
+ a11yViolations = axeResults.violations.map((v) => ({
92
+ id: v.id,
93
+ impact: v.impact ?? 'unknown',
94
+ helpUrl: v.helpUrl,
95
+ nodeCount: v.nodes.length,
96
+ }));
97
+ }
98
+ catch (err) {
99
+ consoleErrors.push(`axe-core failure: ${String(err)}`);
100
+ }
101
+ const path = new URL(url).pathname || '/';
102
+ routes.push({
103
+ path,
104
+ pageTitle,
105
+ links: uniqueInternal,
106
+ formCount,
107
+ buttonLabels: buttonLabels.map((b) => b.trim()).filter(Boolean),
108
+ consoleErrors,
109
+ brokenLinks,
110
+ a11yViolations,
111
+ });
112
+ }
113
+ catch (err) {
114
+ const path = (() => {
115
+ try {
116
+ return new URL(url).pathname || '/';
117
+ }
118
+ catch {
119
+ return url;
120
+ }
121
+ })();
122
+ routes.push({
123
+ path,
124
+ pageTitle: '',
125
+ links: [],
126
+ formCount: 0,
127
+ buttonLabels: [],
128
+ consoleErrors: [`Navigation error: ${String(err)}`],
129
+ brokenLinks: [],
130
+ a11yViolations: [],
131
+ });
132
+ }
133
+ finally {
134
+ await page.close();
135
+ }
136
+ }
137
+ }
138
+ finally {
139
+ await context.close();
140
+ await browser.close();
141
+ }
142
+ return RouteInventorySchema.parse({
143
+ scannedAt: new Date().toISOString(),
144
+ baseUrl,
145
+ routes,
146
+ pagesSkipped: budgetExceeded ? queue.length : 0,
147
+ budgetExceeded,
148
+ });
149
+ }
150
+ }
@@ -0,0 +1,3 @@
1
+ import { type RepoAnalysis } from '../schemas/repo-analysis.schema.js';
2
+ export declare function scanRepo(repoPath: string): Promise<RepoAnalysis>;
3
+ //# sourceMappingURL=repo-scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo-scanner.d.ts","sourceRoot":"","sources":["../../src/tools/repo-scanner.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAiC3F,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CA4HtE"}