@qulib/core 0.7.0 → 0.8.2

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 (34) hide show
  1. package/dist/__tests__/fixtures/api-fixture-repo/app/api/orders/route.d.ts +7 -0
  2. package/dist/__tests__/fixtures/api-fixture-repo/app/api/orders/route.d.ts.map +1 -0
  3. package/dist/__tests__/fixtures/api-fixture-repo/app/api/orders/route.js +7 -0
  4. package/dist/__tests__/fixtures/api-fixture-repo/app/api/users/route.d.ts +10 -0
  5. package/dist/__tests__/fixtures/api-fixture-repo/app/api/users/route.d.ts.map +1 -0
  6. package/dist/__tests__/fixtures/api-fixture-repo/app/api/users/route.js +9 -0
  7. package/dist/__tests__/fixtures/api-fixture-repo/pages/api/health.d.ts +9 -0
  8. package/dist/__tests__/fixtures/api-fixture-repo/pages/api/health.d.ts.map +1 -0
  9. package/dist/__tests__/fixtures/api-fixture-repo/pages/api/health.js +10 -0
  10. package/dist/adapters/api-adapter.d.ts +26 -0
  11. package/dist/adapters/api-adapter.d.ts.map +1 -1
  12. package/dist/adapters/api-adapter.js +156 -2
  13. package/dist/adapters/playwright-adapter.d.ts.map +1 -1
  14. package/dist/adapters/playwright-adapter.js +71 -2
  15. package/dist/index.d.ts +4 -0
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +2 -0
  18. package/dist/schemas/automation-maturity.schema.d.ts +8 -8
  19. package/dist/schemas/automation-maturity.schema.d.ts.map +1 -1
  20. package/dist/schemas/automation-maturity.schema.js +1 -0
  21. package/dist/schemas/gap-analysis.schema.d.ts +8 -8
  22. package/dist/schemas/gap-analysis.schema.js +1 -1
  23. package/dist/schemas/public-surface.schema.d.ts +5 -5
  24. package/dist/schemas/repo-analysis.schema.d.ts +7 -7
  25. package/dist/tools/repo/api-surface.d.ts +59 -0
  26. package/dist/tools/repo/api-surface.d.ts.map +1 -0
  27. package/dist/tools/repo/api-surface.js +414 -0
  28. package/dist/tools/scoring/api-coverage.d.ts +74 -0
  29. package/dist/tools/scoring/api-coverage.d.ts.map +1 -0
  30. package/dist/tools/scoring/api-coverage.js +158 -0
  31. package/dist/tools/scoring/automation-maturity.d.ts +11 -1
  32. package/dist/tools/scoring/automation-maturity.d.ts.map +1 -1
  33. package/dist/tools/scoring/automation-maturity.js +43 -9
  34. package/package.json +4 -2
@@ -0,0 +1,7 @@
1
+ export declare function DELETE(request: {
2
+ url: string;
3
+ }): Promise<{
4
+ deleted: boolean;
5
+ id: string | null;
6
+ }>;
7
+ //# sourceMappingURL=route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../src/__tests__/fixtures/api-fixture-repo/app/api/orders/route.ts"],"names":[],"mappings":"AAGA,wBAAsB,MAAM,CAAC,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE;;;GAIpD"}
@@ -0,0 +1,7 @@
1
+ // Fixture: Next.js App Router API route for orders (DELETE only — high severity untested)
2
+ // This file is a static analysis fixture only — not compiled.
3
+ export async function DELETE(request) {
4
+ const url = new URL(request.url);
5
+ const id = url.searchParams.get('id');
6
+ return { deleted: true, id };
7
+ }
@@ -0,0 +1,10 @@
1
+ export declare function GET(): Promise<{
2
+ users: never[];
3
+ }>;
4
+ export declare function POST(request: {
5
+ json: () => Promise<unknown>;
6
+ }): Promise<{
7
+ created: boolean;
8
+ user: unknown;
9
+ }>;
10
+ //# sourceMappingURL=route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../src/__tests__/fixtures/api-fixture-repo/app/api/users/route.ts"],"names":[],"mappings":"AAGA,wBAAsB,GAAG;;GAExB;AAED,wBAAsB,IAAI,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE;;;GAGnE"}
@@ -0,0 +1,9 @@
1
+ // Fixture: Next.js App Router API route for users
2
+ // This file is a static analysis fixture only — not compiled.
3
+ export async function GET() {
4
+ return { users: [] };
5
+ }
6
+ export async function POST(request) {
7
+ const body = await request.json();
8
+ return { created: true, user: body };
9
+ }
@@ -0,0 +1,9 @@
1
+ export default function handler(req: {
2
+ method?: string;
3
+ }, res: {
4
+ status: (code: number) => {
5
+ json: (data: unknown) => void;
6
+ end: () => void;
7
+ };
8
+ }): void;
9
+ //# sourceMappingURL=health.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../../../../../src/__tests__/fixtures/api-fixture-repo/pages/api/health.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,OAAO,UAAU,OAAO,CAC7B,GAAG,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,EACxB,GAAG,EAAE;IAAE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;QAAC,GAAG,EAAE,MAAM,IAAI,CAAA;KAAE,CAAA;CAAE,QAOtF"}
@@ -0,0 +1,10 @@
1
+ // Fixture: Next.js Pages API route
2
+ // This file is a static analysis fixture only — not compiled.
3
+ export default function handler(req, res) {
4
+ if (req.method === 'GET') {
5
+ res.status(200).json({ status: 'ok' });
6
+ }
7
+ else {
8
+ res.status(405).end();
9
+ }
10
+ }
@@ -1,8 +1,34 @@
1
1
  import type { TestAdapter } from './adapter.interface.js';
2
2
  import type { NeutralScenario, GeneratedTest } from '../schemas/gap-analysis.schema.js';
3
+ import type { ApiSurface } from '../tools/repo/api-surface.js';
4
+ /**
5
+ * TestAdapter implementation for API testing via supertest.
6
+ *
7
+ * `render` / `renderAll`: convert gap-analysis NeutralScenarios that carry
8
+ * `api-call` steps into supertest specs. Used by the standard adapter pipeline.
9
+ *
10
+ * `scaffoldApiTests`: separate entry point for the repo-first API toolshed flow.
11
+ * Accepts discovered endpoints (ApiSurface) and generates a ready-to-run
12
+ * supertest test file — NOT URL-based.
13
+ */
3
14
  export declare class ApiAdapter implements TestAdapter {
4
15
  readonly adapterType = "api";
5
16
  render(scenario: NeutralScenario): GeneratedTest;
6
17
  renderAll(scenarios: NeutralScenario[]): GeneratedTest[];
18
+ /**
19
+ * Generate a supertest-based test file from discovered API endpoints.
20
+ * This is the repo-first entry point — does NOT require a running URL.
21
+ *
22
+ * Endpoints are grouped into a single test file. Each endpoint gets one
23
+ * `it` block that:
24
+ * - Makes the correct HTTP method call
25
+ * - Asserts status < 500 (smoke-level assertion, safely runnable against a live app)
26
+ * - POST/PUT/PATCH endpoints include a TODO for request body
27
+ *
28
+ * The file is NOT associated with a NeutralScenario; it uses a fixed scenarioId.
29
+ */
30
+ scaffoldApiTests(apiSurface: ApiSurface, options?: {
31
+ appImportPath?: string;
32
+ }): GeneratedTest;
7
33
  }
8
34
  //# sourceMappingURL=api-adapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"api-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/api-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAExF,qBAAa,UAAW,YAAW,WAAW;IAC5C,QAAQ,CAAC,WAAW,SAAS;IAE7B,MAAM,CAAC,QAAQ,EAAE,eAAe,GAAG,aAAa;IAIhD,SAAS,CAAC,SAAS,EAAE,eAAe,EAAE,GAAG,aAAa,EAAE;CAGzD"}
1
+ {"version":3,"file":"api-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/api-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AACxF,OAAO,KAAK,EAAsB,UAAU,EAAE,MAAM,8BAA8B,CAAC;AASnF;;;;;;;;;GASG;AACH,qBAAa,UAAW,YAAW,WAAW;IAC5C,QAAQ,CAAC,WAAW,SAAS;IAE7B,MAAM,CAAC,QAAQ,EAAE,eAAe,GAAG,aAAa;IAuDhD,SAAS,CAAC,SAAS,EAAE,eAAe,EAAE,GAAG,aAAa,EAAE;IAIxD;;;;;;;;;;;OAWG;IACH,gBAAgB,CACd,UAAU,EAAE,UAAU,EACtB,OAAO,GAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAO,GACvC,aAAa;CAoDjB"}
@@ -1,9 +1,163 @@
1
+ function slugify(title) {
2
+ return title
3
+ .toLowerCase()
4
+ .replace(/[^a-z0-9]+/g, '-')
5
+ .replace(/^-|-$/g, '');
6
+ }
7
+ /**
8
+ * TestAdapter implementation for API testing via supertest.
9
+ *
10
+ * `render` / `renderAll`: convert gap-analysis NeutralScenarios that carry
11
+ * `api-call` steps into supertest specs. Used by the standard adapter pipeline.
12
+ *
13
+ * `scaffoldApiTests`: separate entry point for the repo-first API toolshed flow.
14
+ * Accepts discovered endpoints (ApiSurface) and generates a ready-to-run
15
+ * supertest test file — NOT URL-based.
16
+ */
1
17
  export class ApiAdapter {
2
18
  adapterType = 'api';
3
19
  render(scenario) {
4
- throw new Error('Not implemented');
20
+ const slug = slugify(scenario.title);
21
+ const filename = `${slug}.api.test.ts`;
22
+ const stepLines = scenario.steps
23
+ .map((step) => {
24
+ if (step.action === 'api-call') {
25
+ const path = step.target ?? step.value ?? '/';
26
+ return [
27
+ ` // ${step.description}`,
28
+ ` const res = await request(app).get(${JSON.stringify(path)});`,
29
+ ` expect(res.status).toBe(200);`,
30
+ ].join('\n');
31
+ }
32
+ if (step.action === 'navigate') {
33
+ const path = step.target ?? step.value ?? '/';
34
+ return [
35
+ ` // ${step.description}`,
36
+ ` const res = await request(app).get(${JSON.stringify(path)});`,
37
+ ` expect(res.status).toBeLessThan(500);`,
38
+ ].join('\n');
39
+ }
40
+ return ` // TODO (${step.action}): ${step.description}`;
41
+ })
42
+ .join('\n');
43
+ const code = [
44
+ `// ${scenario.description}`,
45
+ `// qulib-generated — scenario: ${scenario.id}`,
46
+ ``,
47
+ `import request from 'supertest';`,
48
+ `import { describe, it, expect } from 'vitest';`,
49
+ ``,
50
+ `// TODO: import or create your Express/Fastify/Hono app here`,
51
+ `// import { app } from '../src/app.js';`,
52
+ `declare const app: unknown;`,
53
+ ``,
54
+ `describe(${JSON.stringify(scenario.title)}, () => {`,
55
+ ` it(${JSON.stringify(scenario.description)}, async () => {`,
56
+ stepLines || ` // no api-call steps — add assertions for: ${scenario.targetPath}`,
57
+ ` });`,
58
+ `});`,
59
+ ``,
60
+ ].join('\n');
61
+ return {
62
+ scenarioId: scenario.id,
63
+ adapter: 'api',
64
+ filename,
65
+ code,
66
+ source: 'template',
67
+ outputPath: `tests/api/${filename}`,
68
+ };
5
69
  }
6
70
  renderAll(scenarios) {
7
- throw new Error('Not implemented');
71
+ return scenarios.map((s) => this.render(s));
72
+ }
73
+ /**
74
+ * Generate a supertest-based test file from discovered API endpoints.
75
+ * This is the repo-first entry point — does NOT require a running URL.
76
+ *
77
+ * Endpoints are grouped into a single test file. Each endpoint gets one
78
+ * `it` block that:
79
+ * - Makes the correct HTTP method call
80
+ * - Asserts status < 500 (smoke-level assertion, safely runnable against a live app)
81
+ * - POST/PUT/PATCH endpoints include a TODO for request body
82
+ *
83
+ * The file is NOT associated with a NeutralScenario; it uses a fixed scenarioId.
84
+ */
85
+ scaffoldApiTests(apiSurface, options = {}) {
86
+ const appImport = options.appImportPath ?? '../src/app.js';
87
+ const endpoints = apiSurface.endpoints;
88
+ if (endpoints.length === 0) {
89
+ const code = [
90
+ `// qulib-generated API scaffold — no endpoints discovered`,
91
+ `// qulib-generated — repo: ${apiSurface.repoPath}`,
92
+ ``,
93
+ `// No API endpoints were discovered in this repository.`,
94
+ `// If your app has REST endpoints, ensure they are declared in a supported`,
95
+ `// framework (Next.js route.ts, Express, Fastify, NestJS) or an OpenAPI spec.`,
96
+ ``,
97
+ ].join('\n');
98
+ return {
99
+ scenarioId: 'qulib-api-scaffold',
100
+ adapter: 'api',
101
+ filename: 'api-scaffold.test.ts',
102
+ code,
103
+ source: 'template',
104
+ outputPath: 'tests/api/api-scaffold.test.ts',
105
+ };
106
+ }
107
+ const itBlocks = endpoints.map((ep) => renderEndpointTest(ep)).join('\n\n');
108
+ const code = [
109
+ `// qulib-generated API scaffold — ${endpoints.length} endpoint(s) discovered`,
110
+ `// qulib-generated — repo: ${apiSurface.repoPath}`,
111
+ `// Discovery tier breakdown: ${describeDiscoveryTiers(endpoints)}`,
112
+ ``,
113
+ `import request from 'supertest';`,
114
+ `import { describe, it, expect, beforeAll, afterAll } from 'vitest';`,
115
+ ``,
116
+ `// TODO: replace with your actual app export`,
117
+ `import { app } from ${JSON.stringify(appImport)};`,
118
+ ``,
119
+ `describe('API surface smoke tests (qulib-generated)', () => {`,
120
+ itBlocks,
121
+ `});`,
122
+ ``,
123
+ ].join('\n');
124
+ return {
125
+ scenarioId: 'qulib-api-scaffold',
126
+ adapter: 'api',
127
+ filename: 'api-scaffold.test.ts',
128
+ code,
129
+ source: 'template',
130
+ outputPath: 'tests/api/api-scaffold.test.ts',
131
+ };
8
132
  }
9
133
  }
134
+ // ---------------------------------------------------------------------------
135
+ // Internal helpers
136
+ // ---------------------------------------------------------------------------
137
+ function renderEndpointTest(ep) {
138
+ const method = ep.method === 'unknown' ? 'GET' : ep.method;
139
+ const methodLower = method.toLowerCase();
140
+ const hasBody = method === 'POST' || method === 'PUT' || method === 'PATCH';
141
+ const sourceLine = ` // Source: ${ep.sourceFile} (${ep.sourceTier}, confidence: ${ep.confidence})`;
142
+ const itTitle = `${method} ${ep.path}`;
143
+ const requestLine = hasBody
144
+ ? ` const res = await request(app).${methodLower}(${JSON.stringify(ep.path)});\n // TODO: add request body — e.g. .send({ ... })`
145
+ : ` const res = await request(app).${methodLower}(${JSON.stringify(ep.path)});`;
146
+ const summaryLine = ep.summary ? ` // ${ep.summary}\n` : '';
147
+ return [
148
+ summaryLine + sourceLine,
149
+ ` it(${JSON.stringify(itTitle)}, async () => {`,
150
+ requestLine,
151
+ ` expect(res.status).toBeLessThan(500);`,
152
+ ` });`,
153
+ ].join('\n');
154
+ }
155
+ function describeDiscoveryTiers(endpoints) {
156
+ const counts = { openapi: 0, framework: 0, heuristic: 0 };
157
+ for (const ep of endpoints)
158
+ counts[ep.sourceTier]++;
159
+ return Object.entries(counts)
160
+ .filter(([, v]) => v > 0)
161
+ .map(([k, v]) => `${v} ${k}`)
162
+ .join(', ');
163
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"playwright-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/playwright-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAExF,qBAAa,iBAAkB,YAAW,WAAW;IACnD,QAAQ,CAAC,WAAW,gBAAgB;IAEpC,MAAM,CAAC,QAAQ,EAAE,eAAe,GAAG,aAAa;IAIhD,SAAS,CAAC,SAAS,EAAE,eAAe,EAAE,GAAG,aAAa,EAAE;CAGzD"}
1
+ {"version":3,"file":"playwright-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/playwright-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAY,MAAM,mCAAmC,CAAC;AAiDlG,qBAAa,iBAAkB,YAAW,WAAW;IACnD,QAAQ,CAAC,WAAW,gBAAgB;IAEpC,MAAM,CAAC,QAAQ,EAAE,eAAe,GAAG,aAAa;IA8BhD,SAAS,CAAC,SAAS,EAAE,eAAe,EAAE,GAAG,aAAa,EAAE;CAGzD"}
@@ -1,9 +1,78 @@
1
+ function slugify(title) {
2
+ return title
3
+ .toLowerCase()
4
+ .replace(/[^a-z0-9]+/g, '-')
5
+ .replace(/^-|-$/g, '');
6
+ }
7
+ function renderStep(step) {
8
+ const t = step.target != null ? JSON.stringify(step.target) : null;
9
+ const v = step.value != null ? JSON.stringify(step.value) : null;
10
+ switch (step.action) {
11
+ case 'navigate':
12
+ return ` await page.goto(${JSON.stringify(step.target ?? step.value ?? '/')});`;
13
+ case 'click':
14
+ return t ? ` await page.locator(${t}).click();` : ` // click: ${step.description}`;
15
+ case 'type':
16
+ return t && v ? ` await page.locator(${t}).fill(${v});` : ` // type: ${step.description}`;
17
+ case 'assert-visible':
18
+ return t
19
+ ? ` await expect(page.locator(${t})).toBeVisible();`
20
+ : ` await expect(page.locator('body')).toBeVisible();`;
21
+ case 'assert-hidden':
22
+ return t
23
+ ? ` await expect(page.locator(${t})).toBeHidden();`
24
+ : ` // assert-hidden: ${step.description}`;
25
+ case 'assert-text':
26
+ if (t && v)
27
+ return ` await expect(page.locator(${t})).toContainText(${v});`;
28
+ if (t)
29
+ return ` await expect(page.locator(${t})).not.toBeEmpty();`;
30
+ return ` // assert-text: ${step.description}`;
31
+ case 'assert-disabled':
32
+ return t
33
+ ? ` await expect(page.locator(${t})).toBeDisabled();`
34
+ : ` // assert-disabled: ${step.description}`;
35
+ case 'assert-count':
36
+ return t
37
+ ? ` expect(await page.locator(${t}).count()).toBeGreaterThanOrEqual(${parseInt(step.value ?? '1', 10)});`
38
+ : ` // assert-count: ${step.description}`;
39
+ case 'wait':
40
+ return ` await page.waitForTimeout(${parseInt(step.value ?? '1000', 10)});`;
41
+ case 'api-call':
42
+ return ` expect((await page.request.get(${JSON.stringify(step.target ?? step.value ?? '/')})).status()).toBe(200);`;
43
+ default:
44
+ return ` // TODO: ${step.description}`;
45
+ }
46
+ }
1
47
  export class PlaywrightAdapter {
2
48
  adapterType = 'playwright';
3
49
  render(scenario) {
4
- throw new Error('Not implemented');
50
+ const slug = slugify(scenario.title);
51
+ const filename = `${slug}.spec.ts`;
52
+ const stepLines = scenario.steps.map(renderStep).join('\n');
53
+ const code = [
54
+ `// ${scenario.description}`,
55
+ `// qulib-generated — scenario: ${scenario.id}`,
56
+ ``,
57
+ `import { test, expect } from '@playwright/test';`,
58
+ ``,
59
+ `test.describe(${JSON.stringify(scenario.title)}, () => {`,
60
+ ` test(${JSON.stringify(scenario.description)}, async ({ page }) => {`,
61
+ stepLines || ` // no steps — add assertions for: ${scenario.targetPath}`,
62
+ ` });`,
63
+ `});`,
64
+ ``,
65
+ ].join('\n');
66
+ return {
67
+ scenarioId: scenario.id,
68
+ adapter: 'playwright',
69
+ filename,
70
+ code,
71
+ source: 'template',
72
+ outputPath: `tests/${filename}`,
73
+ };
5
74
  }
6
75
  renderAll(scenarios) {
7
- throw new Error('Not implemented');
76
+ return scenarios.map((s) => this.render(s));
8
77
  }
9
78
  }
package/dist/index.d.ts CHANGED
@@ -6,7 +6,11 @@ export type { StorageStateInvalidReason, StorageStateValidationResult, } from '.
6
6
  export { exploreAuth } from './tools/auth/explore.js';
7
7
  export { addUserProvider, removeUserProvider, listUserProviders } from './tools/auth/custom-providers.js';
8
8
  export { scanRepo } from './tools/repo/scan.js';
9
+ export { discoverApiSurface, discoverApiSurfaceWithRepo } from './tools/repo/api-surface.js';
10
+ export type { ApiSurface, DiscoveredEndpoint, DiscoverApiSurfaceOptions } from './tools/repo/api-surface.js';
9
11
  export { computeAutomationMaturity } from './tools/scoring/automation-maturity.js';
12
+ export { computeApiCoverage } from './tools/scoring/api-coverage.js';
13
+ export type { ApiCoverageResult, ApiEndpointCoverage } from './tools/scoring/api-coverage.js';
10
14
  export { scaffoldTests } from './scaffold-tests.js';
11
15
  export type { ScaffoldOptions, ScaffoldResult, ProjectConfig } from './scaffold-tests.js';
12
16
  export { createProvider } from './llm/provider-registry.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,YAAY,EACV,YAAY,EACZ,kBAAkB,EAClB,SAAS,EACT,cAAc,EACd,uBAAuB,GACxB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,UAAU,EACV,oBAAoB,EACpB,4BAA4B,EAC5B,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAC1G,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,yBAAyB,EAAE,MAAM,wCAAwC,CAAC;AACnF,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC1F,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,gCAAgC,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACvF,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACjF,YAAY,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,YAAY,EACV,aAAa,EACb,cAAc,EACd,kBAAkB,GACnB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC9E,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,YAAY,EACV,aAAa,EACb,UAAU,EACV,cAAc,EACd,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,qBAAqB,EACrB,aAAa,EACb,kBAAkB,EAClB,2BAA2B,EAC3B,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,YAAY,EACV,YAAY,EACZ,kBAAkB,EAClB,SAAS,EACT,cAAc,EACd,uBAAuB,GACxB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,UAAU,EACV,oBAAoB,EACpB,4BAA4B,EAC5B,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAC1G,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,kBAAkB,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAC;AAC7F,YAAY,EAAE,UAAU,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AAC7G,OAAO,EAAE,yBAAyB,EAAE,MAAM,wCAAwC,CAAC;AACnF,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,YAAY,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAC9F,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC1F,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,gCAAgC,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACvF,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACjF,YAAY,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,YAAY,EACV,aAAa,EACb,cAAc,EACd,kBAAkB,GACnB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC9E,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,YAAY,EACV,aAAa,EACb,UAAU,EACV,cAAc,EACd,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,qBAAqB,EACrB,aAAa,EACb,kBAAkB,EAClB,2BAA2B,EAC3B,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC"}
package/dist/index.js CHANGED
@@ -4,7 +4,9 @@ export { detectAuth, validateStorageState, evaluateStorageStateValidity, preflig
4
4
  export { exploreAuth } from './tools/auth/explore.js';
5
5
  export { addUserProvider, removeUserProvider, listUserProviders } from './tools/auth/custom-providers.js';
6
6
  export { scanRepo } from './tools/repo/scan.js';
7
+ export { discoverApiSurface, discoverApiSurfaceWithRepo } from './tools/repo/api-surface.js';
7
8
  export { computeAutomationMaturity } from './tools/scoring/automation-maturity.js';
9
+ export { computeApiCoverage } from './tools/scoring/api-coverage.js';
8
10
  export { scaffoldTests } from './scaffold-tests.js';
9
11
  export { createProvider } from './llm/provider-registry.js';
10
12
  export { resolveMaxOutputTokensPerLlmCall } from './schemas/config.schema.js';
@@ -20,7 +20,7 @@ export declare const AutomationMaturityApplicabilitySchema: z.ZodEnum<["applicab
20
20
  * Existing consumers that don't read them keep working; honest reports populate them.
21
21
  */
22
22
  export declare const AutomationMaturityDimensionSchema: z.ZodObject<{
23
- dimension: z.ZodEnum<["test-coverage-breadth", "framework-adoption", "test-id-hygiene", "ci-integration", "auth-test-coverage", "component-test-ratio"]>;
23
+ dimension: z.ZodEnum<["test-coverage-breadth", "framework-adoption", "test-id-hygiene", "ci-integration", "auth-test-coverage", "component-test-ratio", "api-test-coverage"]>;
24
24
  score: z.ZodNumber;
25
25
  weight: z.ZodNumber;
26
26
  evidence: z.ZodArray<z.ZodString, "many">;
@@ -30,7 +30,7 @@ export declare const AutomationMaturityDimensionSchema: z.ZodObject<{
30
30
  guidance: z.ZodOptional<z.ZodString>;
31
31
  }, "strip", z.ZodTypeAny, {
32
32
  recommendations: string[];
33
- dimension: "test-coverage-breadth" | "framework-adoption" | "test-id-hygiene" | "ci-integration" | "auth-test-coverage" | "component-test-ratio";
33
+ dimension: "test-coverage-breadth" | "framework-adoption" | "test-id-hygiene" | "ci-integration" | "auth-test-coverage" | "component-test-ratio" | "api-test-coverage";
34
34
  score: number;
35
35
  weight: number;
36
36
  evidence: string[];
@@ -39,7 +39,7 @@ export declare const AutomationMaturityDimensionSchema: z.ZodObject<{
39
39
  guidance?: string | undefined;
40
40
  }, {
41
41
  recommendations: string[];
42
- dimension: "test-coverage-breadth" | "framework-adoption" | "test-id-hygiene" | "ci-integration" | "auth-test-coverage" | "component-test-ratio";
42
+ dimension: "test-coverage-breadth" | "framework-adoption" | "test-id-hygiene" | "ci-integration" | "auth-test-coverage" | "component-test-ratio" | "api-test-coverage";
43
43
  score: number;
44
44
  weight: number;
45
45
  evidence: string[];
@@ -54,7 +54,7 @@ export declare const AutomationMaturitySchema: z.ZodObject<{
54
54
  level: z.ZodNumber;
55
55
  label: z.ZodString;
56
56
  dimensions: z.ZodArray<z.ZodObject<{
57
- dimension: z.ZodEnum<["test-coverage-breadth", "framework-adoption", "test-id-hygiene", "ci-integration", "auth-test-coverage", "component-test-ratio"]>;
57
+ dimension: z.ZodEnum<["test-coverage-breadth", "framework-adoption", "test-id-hygiene", "ci-integration", "auth-test-coverage", "component-test-ratio", "api-test-coverage"]>;
58
58
  score: z.ZodNumber;
59
59
  weight: z.ZodNumber;
60
60
  evidence: z.ZodArray<z.ZodString, "many">;
@@ -64,7 +64,7 @@ export declare const AutomationMaturitySchema: z.ZodObject<{
64
64
  guidance: z.ZodOptional<z.ZodString>;
65
65
  }, "strip", z.ZodTypeAny, {
66
66
  recommendations: string[];
67
- dimension: "test-coverage-breadth" | "framework-adoption" | "test-id-hygiene" | "ci-integration" | "auth-test-coverage" | "component-test-ratio";
67
+ dimension: "test-coverage-breadth" | "framework-adoption" | "test-id-hygiene" | "ci-integration" | "auth-test-coverage" | "component-test-ratio" | "api-test-coverage";
68
68
  score: number;
69
69
  weight: number;
70
70
  evidence: string[];
@@ -73,7 +73,7 @@ export declare const AutomationMaturitySchema: z.ZodObject<{
73
73
  guidance?: string | undefined;
74
74
  }, {
75
75
  recommendations: string[];
76
- dimension: "test-coverage-breadth" | "framework-adoption" | "test-id-hygiene" | "ci-integration" | "auth-test-coverage" | "component-test-ratio";
76
+ dimension: "test-coverage-breadth" | "framework-adoption" | "test-id-hygiene" | "ci-integration" | "auth-test-coverage" | "component-test-ratio" | "api-test-coverage";
77
77
  score: number;
78
78
  weight: number;
79
79
  evidence: string[];
@@ -91,7 +91,7 @@ export declare const AutomationMaturitySchema: z.ZodObject<{
91
91
  overallScore: number;
92
92
  dimensions: {
93
93
  recommendations: string[];
94
- dimension: "test-coverage-breadth" | "framework-adoption" | "test-id-hygiene" | "ci-integration" | "auth-test-coverage" | "component-test-ratio";
94
+ dimension: "test-coverage-breadth" | "framework-adoption" | "test-id-hygiene" | "ci-integration" | "auth-test-coverage" | "component-test-ratio" | "api-test-coverage";
95
95
  score: number;
96
96
  weight: number;
97
97
  evidence: string[];
@@ -109,7 +109,7 @@ export declare const AutomationMaturitySchema: z.ZodObject<{
109
109
  overallScore: number;
110
110
  dimensions: {
111
111
  recommendations: string[];
112
- dimension: "test-coverage-breadth" | "framework-adoption" | "test-id-hygiene" | "ci-integration" | "auth-test-coverage" | "component-test-ratio";
112
+ dimension: "test-coverage-breadth" | "framework-adoption" | "test-id-hygiene" | "ci-integration" | "auth-test-coverage" | "component-test-ratio" | "api-test-coverage";
113
113
  score: number;
114
114
  weight: number;
115
115
  evidence: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"automation-maturity.schema.d.ts","sourceRoot":"","sources":["../../src/schemas/automation-maturity.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,qCAAqC,wDAIhD,CAAC;AAEH;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgB5C,CAAC;AAEH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EASnC,CAAC;AAEH,MAAM,MAAM,+BAA+B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qCAAqC,CAAC,CAAC;AACpG,MAAM,MAAM,2BAA2B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iCAAiC,CAAC,CAAC;AAC5F,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
1
+ {"version":3,"file":"automation-maturity.schema.d.ts","sourceRoot":"","sources":["../../src/schemas/automation-maturity.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,qCAAqC,wDAIhD,CAAC;AAEH;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiB5C,CAAC;AAEH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EASnC,CAAC;AAEH,MAAM,MAAM,+BAA+B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qCAAqC,CAAC,CAAC;AACpG,MAAM,MAAM,2BAA2B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iCAAiC,CAAC,CAAC;AAC5F,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
@@ -31,6 +31,7 @@ export const AutomationMaturityDimensionSchema = z.object({
31
31
  'ci-integration',
32
32
  'auth-test-coverage',
33
33
  'component-test-ratio',
34
+ 'api-test-coverage',
34
35
  ]),
35
36
  score: z.number().min(0).max(100),
36
37
  weight: z.number().min(0).max(1),
@@ -4,7 +4,7 @@ export declare const GapSchema: z.ZodObject<{
4
4
  path: z.ZodString;
5
5
  severity: z.ZodEnum<["critical", "high", "medium", "low"]>;
6
6
  reason: z.ZodString;
7
- category: z.ZodEnum<["untested-route", "a11y", "console-error", "broken-link", "auth-surface", "coverage"]>;
7
+ category: z.ZodEnum<["untested-route", "a11y", "console-error", "broken-link", "auth-surface", "coverage", "untested-api-endpoint"]>;
8
8
  description: z.ZodOptional<z.ZodString>;
9
9
  recommendation: z.ZodOptional<z.ZodString>;
10
10
  }, "strip", z.ZodTypeAny, {
@@ -12,7 +12,7 @@ export declare const GapSchema: z.ZodObject<{
12
12
  id: string;
13
13
  severity: "critical" | "high" | "medium" | "low";
14
14
  reason: string;
15
- category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage";
15
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
16
16
  recommendation?: string | undefined;
17
17
  description?: string | undefined;
18
18
  }, {
@@ -20,7 +20,7 @@ export declare const GapSchema: z.ZodObject<{
20
20
  id: string;
21
21
  severity: "critical" | "high" | "medium" | "low";
22
22
  reason: string;
23
- category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage";
23
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
24
24
  recommendation?: string | undefined;
25
25
  description?: string | undefined;
26
26
  }>;
@@ -163,7 +163,7 @@ export declare const GapAnalysisSchema: z.ZodObject<{
163
163
  path: z.ZodString;
164
164
  severity: z.ZodEnum<["critical", "high", "medium", "low"]>;
165
165
  reason: z.ZodString;
166
- category: z.ZodEnum<["untested-route", "a11y", "console-error", "broken-link", "auth-surface", "coverage"]>;
166
+ category: z.ZodEnum<["untested-route", "a11y", "console-error", "broken-link", "auth-surface", "coverage", "untested-api-endpoint"]>;
167
167
  description: z.ZodOptional<z.ZodString>;
168
168
  recommendation: z.ZodOptional<z.ZodString>;
169
169
  }, "strip", z.ZodTypeAny, {
@@ -171,7 +171,7 @@ export declare const GapAnalysisSchema: z.ZodObject<{
171
171
  id: string;
172
172
  severity: "critical" | "high" | "medium" | "low";
173
173
  reason: string;
174
- category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage";
174
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
175
175
  recommendation?: string | undefined;
176
176
  description?: string | undefined;
177
177
  }, {
@@ -179,7 +179,7 @@ export declare const GapAnalysisSchema: z.ZodObject<{
179
179
  id: string;
180
180
  severity: "critical" | "high" | "medium" | "low";
181
181
  reason: string;
182
- category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage";
182
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
183
183
  recommendation?: string | undefined;
184
184
  description?: string | undefined;
185
185
  }>, "many">;
@@ -445,7 +445,7 @@ export declare const GapAnalysisSchema: z.ZodObject<{
445
445
  id: string;
446
446
  severity: "critical" | "high" | "medium" | "low";
447
447
  reason: string;
448
- category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage";
448
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
449
449
  recommendation?: string | undefined;
450
450
  description?: string | undefined;
451
451
  }[];
@@ -524,7 +524,7 @@ export declare const GapAnalysisSchema: z.ZodObject<{
524
524
  id: string;
525
525
  severity: "critical" | "high" | "medium" | "low";
526
526
  reason: string;
527
- category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage";
527
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
528
528
  recommendation?: string | undefined;
529
529
  description?: string | undefined;
530
530
  }[];
@@ -5,7 +5,7 @@ export const GapSchema = z.object({
5
5
  path: z.string(),
6
6
  severity: z.enum(['critical', 'high', 'medium', 'low']),
7
7
  reason: z.string(),
8
- category: z.enum(['untested-route', 'a11y', 'console-error', 'broken-link', 'auth-surface', 'coverage']),
8
+ category: z.enum(['untested-route', 'a11y', 'console-error', 'broken-link', 'auth-surface', 'coverage', 'untested-api-endpoint']),
9
9
  description: z.string().optional(),
10
10
  recommendation: z.string().optional(),
11
11
  });
@@ -118,7 +118,7 @@ export declare const PublicSurfaceSchema: z.ZodObject<{
118
118
  path: z.ZodString;
119
119
  severity: z.ZodEnum<["critical", "high", "medium", "low"]>;
120
120
  reason: z.ZodString;
121
- category: z.ZodEnum<["untested-route", "a11y", "console-error", "broken-link", "auth-surface", "coverage"]>;
121
+ category: z.ZodEnum<["untested-route", "a11y", "console-error", "broken-link", "auth-surface", "coverage", "untested-api-endpoint"]>;
122
122
  description: z.ZodOptional<z.ZodString>;
123
123
  recommendation: z.ZodOptional<z.ZodString>;
124
124
  }, "strip", z.ZodTypeAny, {
@@ -126,7 +126,7 @@ export declare const PublicSurfaceSchema: z.ZodObject<{
126
126
  id: string;
127
127
  severity: "critical" | "high" | "medium" | "low";
128
128
  reason: string;
129
- category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage";
129
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
130
130
  recommendation?: string | undefined;
131
131
  description?: string | undefined;
132
132
  }, {
@@ -134,7 +134,7 @@ export declare const PublicSurfaceSchema: z.ZodObject<{
134
134
  id: string;
135
135
  severity: "critical" | "high" | "medium" | "low";
136
136
  reason: string;
137
- category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage";
137
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
138
138
  recommendation?: string | undefined;
139
139
  description?: string | undefined;
140
140
  }>, "many">;
@@ -181,7 +181,7 @@ export declare const PublicSurfaceSchema: z.ZodObject<{
181
181
  id: string;
182
182
  severity: "critical" | "high" | "medium" | "low";
183
183
  reason: string;
184
- category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage";
184
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
185
185
  recommendation?: string | undefined;
186
186
  description?: string | undefined;
187
187
  }[];
@@ -224,7 +224,7 @@ export declare const PublicSurfaceSchema: z.ZodObject<{
224
224
  id: string;
225
225
  severity: "critical" | "high" | "medium" | "low";
226
226
  reason: string;
227
- category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage";
227
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
228
228
  recommendation?: string | undefined;
229
229
  description?: string | undefined;
230
230
  }[];