@qulib/core 0.2.1 → 0.3.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 (87) hide show
  1. package/README.md +45 -3
  2. package/dist/analyze.d.ts +16 -4
  3. package/dist/analyze.d.ts.map +1 -1
  4. package/dist/analyze.js +98 -38
  5. package/dist/cli/cost-doctor.d.ts +2 -0
  6. package/dist/cli/cost-doctor.d.ts.map +1 -0
  7. package/dist/cli/cost-doctor.js +72 -0
  8. package/dist/cli/index.js +61 -0
  9. package/dist/harness/progress-log.d.ts +7 -0
  10. package/dist/harness/progress-log.d.ts.map +1 -0
  11. package/dist/harness/progress-log.js +1 -0
  12. package/dist/harness/run-options.d.ts +2 -0
  13. package/dist/harness/run-options.d.ts.map +1 -1
  14. package/dist/index.d.ts +6 -2
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +3 -0
  17. package/dist/llm/content-hash.d.ts +2 -0
  18. package/dist/llm/content-hash.d.ts.map +1 -0
  19. package/dist/llm/content-hash.js +4 -0
  20. package/dist/llm/context-builder.js +1 -1
  21. package/dist/llm/cost-intelligence.d.ts +29 -0
  22. package/dist/llm/cost-intelligence.d.ts.map +1 -0
  23. package/dist/llm/cost-intelligence.js +153 -0
  24. package/dist/llm/provider.d.ts +11 -1
  25. package/dist/llm/provider.d.ts.map +1 -1
  26. package/dist/llm/provider.js +43 -4
  27. package/dist/phases/act.d.ts.map +1 -1
  28. package/dist/phases/act.js +4 -1
  29. package/dist/phases/observe.js +1 -1
  30. package/dist/phases/think-finalize.d.ts +6 -0
  31. package/dist/phases/think-finalize.d.ts.map +1 -0
  32. package/dist/phases/think-finalize.js +164 -0
  33. package/dist/phases/think.d.ts +2 -0
  34. package/dist/phases/think.d.ts.map +1 -1
  35. package/dist/phases/think.js +16 -65
  36. package/dist/reporters/markdown-reporter.d.ts.map +1 -1
  37. package/dist/reporters/markdown-reporter.js +23 -3
  38. package/dist/schemas/config.schema.d.ts +364 -0
  39. package/dist/schemas/config.schema.d.ts.map +1 -1
  40. package/dist/schemas/config.schema.js +55 -1
  41. package/dist/schemas/cost-intelligence.schema.d.ts +229 -0
  42. package/dist/schemas/cost-intelligence.schema.d.ts.map +1 -0
  43. package/dist/schemas/cost-intelligence.schema.js +41 -0
  44. package/dist/schemas/decision-log.schema.d.ts +2 -2
  45. package/dist/schemas/gap-analysis.schema.d.ts +288 -49
  46. package/dist/schemas/gap-analysis.schema.d.ts.map +1 -1
  47. package/dist/schemas/gap-analysis.schema.js +7 -3
  48. package/dist/schemas/index.d.ts +3 -1
  49. package/dist/schemas/index.d.ts.map +1 -1
  50. package/dist/schemas/index.js +3 -1
  51. package/dist/schemas/public-surface.schema.d.ts +268 -0
  52. package/dist/schemas/public-surface.schema.d.ts.map +1 -0
  53. package/dist/schemas/public-surface.schema.js +15 -0
  54. package/dist/schemas/repo-analysis.schema.d.ts +6 -6
  55. package/dist/tools/auth-block-gap.d.ts +3 -0
  56. package/dist/tools/auth-block-gap.d.ts.map +1 -0
  57. package/dist/tools/auth-block-gap.js +19 -0
  58. package/dist/tools/auth-detector.d.ts +2 -1
  59. package/dist/tools/auth-detector.d.ts.map +1 -1
  60. package/dist/tools/auth-detector.js +28 -3
  61. package/dist/tools/auth-explorer.d.ts +4 -0
  62. package/dist/tools/auth-explorer.d.ts.map +1 -0
  63. package/dist/tools/auth-explorer.js +346 -0
  64. package/dist/tools/auth-surface-analyzer.d.ts +4 -0
  65. package/dist/tools/auth-surface-analyzer.d.ts.map +1 -0
  66. package/dist/tools/auth-surface-analyzer.js +154 -0
  67. package/dist/tools/cypress-explorer.d.ts +2 -1
  68. package/dist/tools/cypress-explorer.d.ts.map +1 -1
  69. package/dist/tools/cypress-explorer.js +1 -1
  70. package/dist/tools/explorer.interface.d.ts +2 -1
  71. package/dist/tools/explorer.interface.d.ts.map +1 -1
  72. package/dist/tools/gap-engine.d.ts +3 -1
  73. package/dist/tools/gap-engine.d.ts.map +1 -1
  74. package/dist/tools/gap-engine.js +39 -12
  75. package/dist/tools/oauth-providers.d.ts +7 -0
  76. package/dist/tools/oauth-providers.d.ts.map +1 -0
  77. package/dist/tools/oauth-providers.js +21 -0
  78. package/dist/tools/playwright-explorer.d.ts +2 -1
  79. package/dist/tools/playwright-explorer.d.ts.map +1 -1
  80. package/dist/tools/playwright-explorer.js +21 -3
  81. package/dist/tools/public-surface.d.ts +5 -0
  82. package/dist/tools/public-surface.d.ts.map +1 -0
  83. package/dist/tools/public-surface.js +13 -0
  84. package/dist/tools/user-providers.d.ts +15 -0
  85. package/dist/tools/user-providers.d.ts.map +1 -0
  86. package/dist/tools/user-providers.js +62 -0
  87. package/package.json +6 -2
@@ -0,0 +1,21 @@
1
+ export const BUILT_IN_OAUTH_PROVIDERS = [
2
+ { id: 'github', label: 'GitHub', patterns: [/\bgithub\b/i] },
3
+ { id: 'google', label: 'Google', patterns: [/\bgoogle\b/i, /accounts\.google\.com/i] },
4
+ { id: 'microsoft', label: 'Microsoft', patterns: [/microsoft/i, /login\.microsoftonline\.com/i] },
5
+ { id: 'apple', label: 'Apple', patterns: [/sign in with apple/i, /\bapple id\b/i] },
6
+ { id: 'facebook', label: 'Facebook', patterns: [/facebook/i] },
7
+ { id: 'twitter', label: 'Twitter/X', patterns: [/twitter\.com/i, /\bsign in with x\b/i] },
8
+ { id: 'linkedin', label: 'LinkedIn', patterns: [/linkedin/i] },
9
+ { id: 'auth0', label: 'Auth0', patterns: [/auth0/i] },
10
+ { id: 'okta', label: 'Okta', patterns: [/\bokta\b/i] },
11
+ { id: 'onelogin', label: 'OneLogin', patterns: [/onelogin/i] },
12
+ { id: 'duo', label: 'Duo Security', patterns: [/duo security/i] },
13
+ { id: 'ping', label: 'Ping Identity', patterns: [/pingidentity/i, /pingone/i] },
14
+ { id: 'workday', label: 'Workday', patterns: [/workday/i] },
15
+ { id: 'saml', label: 'SAML SSO', patterns: [/\bsaml\b/i] },
16
+ { id: 'clever', label: 'Clever', patterns: [/\bclever\b/i, /clever\.com/i] },
17
+ { id: 'classlink', label: 'ClassLink', patterns: [/classlink/i] },
18
+ { id: 'schoology', label: 'Schoology', patterns: [/schoology/i] },
19
+ { id: 'canvas', label: 'Canvas (Instructure)', patterns: [/\bcanvas lms\b/i, /instructure/i] },
20
+ { id: 'blackboard', label: 'Blackboard', patterns: [/blackboard/i] },
21
+ ];
@@ -1,7 +1,8 @@
1
1
  import type { AppExplorer } from './explorer.interface.js';
2
2
  import { type RouteInventory } from '../schemas/route-inventory.schema.js';
3
3
  import type { HarnessConfig } from '../schemas/config.schema.js';
4
+ import type { RunArtifactsOptions } from '../harness/run-options.js';
4
5
  export declare class PlaywrightExplorer implements AppExplorer {
5
- explore(baseUrl: string, config: HarnessConfig): Promise<RouteInventory>;
6
+ explore(baseUrl: string, config: HarnessConfig, artifacts?: RunArtifactsOptions): Promise<RouteInventory>;
6
7
  }
7
8
  //# sourceMappingURL=playwright-explorer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"playwright-explorer.d.ts","sourceRoot":"","sources":["../../src/tools/playwright-explorer.ts"],"names":[],"mappings":"AAGA,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;AAgBjE,qBAAa,kBAAmB,YAAW,WAAW;IAC9C,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;CAqJ/E"}
1
+ {"version":3,"file":"playwright-explorer.d.ts","sourceRoot":"","sources":["../../src/tools/playwright-explorer.ts"],"names":[],"mappings":"AAGA,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;AACjE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAoBrE,qBAAa,kBAAmB,YAAW,WAAW;IAC9C,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,SAAS,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,CAAC;CAsKhH"}
@@ -15,8 +15,12 @@ function isInternalHref(href, baseUrlStr) {
15
15
  return false;
16
16
  }
17
17
  }
18
+ function debugMode() {
19
+ return process.env.QULIB_DEBUG === '1';
20
+ }
18
21
  export class PlaywrightExplorer {
19
- async explore(baseUrl, config) {
22
+ async explore(baseUrl, config, artifacts) {
23
+ const progress = artifacts?.progressLog;
20
24
  const browser = await launchBrowser();
21
25
  let context;
22
26
  try {
@@ -28,7 +32,10 @@ export class PlaywrightExplorer {
28
32
  }
29
33
  if (config.auth) {
30
34
  const label = config.auth.type === 'form-login' ? config.auth.credentials.username : 'storage-state';
31
- console.error(`[qulib] authenticated as ${label}`);
35
+ progress?.info(`Authenticated context: ${label}`);
36
+ if (!progress) {
37
+ process.stderr.write(`[qulib] authenticated as ${label}\n`);
38
+ }
32
39
  }
33
40
  const visited = new Set();
34
41
  const queue = [baseUrl];
@@ -56,10 +63,15 @@ export class PlaywrightExplorer {
56
63
  }
57
64
  });
58
65
  try {
59
- await page.goto(url, {
66
+ const navResponse = await page.goto(url, {
60
67
  timeout: config.timeoutMs,
61
68
  waitUntil: 'domcontentloaded',
62
69
  });
70
+ const httpStatus = navResponse?.status() ?? 0;
71
+ if (debugMode()) {
72
+ const html = await page.content();
73
+ progress?.debug(`page HTML byteLength=${Buffer.byteLength(html, 'utf8')} url=${normalized}`);
74
+ }
63
75
  const pageTitle = await page.title();
64
76
  const formCount = await page.locator('form').count();
65
77
  const buttonLabels = await page.locator('button').allInnerTexts();
@@ -92,6 +104,9 @@ export class PlaywrightExplorer {
92
104
  const axeResults = await new AxeBuilder({ page })
93
105
  .withTags(['wcag2a', 'wcag2aa'])
94
106
  .analyze();
107
+ if (debugMode()) {
108
+ progress?.debug(`raw axe violations (pre-map) count=${axeResults.violations.length} json=${JSON.stringify(axeResults.violations)}`);
109
+ }
95
110
  a11yViolations = axeResults.violations.map((v) => ({
96
111
  id: v.id,
97
112
  impact: v.impact ?? 'unknown',
@@ -103,6 +118,7 @@ export class PlaywrightExplorer {
103
118
  consoleErrors.push(`axe-core failure: ${String(err)}`);
104
119
  }
105
120
  const path = new URL(url).pathname || '/';
121
+ progress?.info(`Crawled ${normalized} status=${httpStatus} a11yViolations=${a11yViolations.length}`);
106
122
  routes.push({
107
123
  path,
108
124
  pageTitle,
@@ -112,6 +128,7 @@ export class PlaywrightExplorer {
112
128
  consoleErrors,
113
129
  brokenLinks,
114
130
  a11yViolations,
131
+ statusCode: httpStatus,
115
132
  });
116
133
  }
117
134
  catch (err) {
@@ -123,6 +140,7 @@ export class PlaywrightExplorer {
123
140
  return url;
124
141
  }
125
142
  })();
143
+ progress?.info(`Crawled ${normalized} status=error a11yViolations=0 err=${String(err).slice(0, 120)}`);
126
144
  routes.push({
127
145
  path,
128
146
  pageTitle: '',
@@ -0,0 +1,5 @@
1
+ import type { RouteInventory } from '../schemas/route-inventory.schema.js';
2
+ import type { Gap } from '../schemas/gap-analysis.schema.js';
3
+ import type { PublicSurface } from '../schemas/public-surface.schema.js';
4
+ export declare function buildPublicSurface(pages: RouteInventory['routes'], gaps: Gap[]): PublicSurface;
5
+ //# sourceMappingURL=public-surface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-surface.d.ts","sourceRoot":"","sources":["../../src/tools/public-surface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAC3E,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,mCAAmC,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAmD,MAAM,qCAAqC,CAAC;AAE1H,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,cAAc,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,aAAa,CAY9F"}
@@ -0,0 +1,13 @@
1
+ export function buildPublicSurface(pages, gaps) {
2
+ const accessibilityViolations = [];
3
+ const brokenLinks = [];
4
+ for (const r of pages) {
5
+ for (const v of r.a11yViolations) {
6
+ accessibilityViolations.push({ ...v, path: r.path });
7
+ }
8
+ for (const b of r.brokenLinks) {
9
+ brokenLinks.push({ ...b, path: r.path });
10
+ }
11
+ }
12
+ return { pages, gaps, accessibilityViolations, brokenLinks };
13
+ }
@@ -0,0 +1,15 @@
1
+ import type { OAuthProvider } from './oauth-providers.js';
2
+ export interface SerializedProvider {
3
+ id: string;
4
+ label: string;
5
+ patterns: string[];
6
+ }
7
+ export declare function loadUserProviders(): OAuthProvider[];
8
+ export declare function addUserProvider(input: {
9
+ id: string;
10
+ label: string;
11
+ pattern: string;
12
+ }): void;
13
+ export declare function removeUserProvider(id: string): boolean;
14
+ export declare function listUserProviders(): SerializedProvider[];
15
+ //# sourceMappingURL=user-providers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-providers.d.ts","sourceRoot":"","sources":["../../src/tools/user-providers.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAI1D,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,wBAAgB,iBAAiB,IAAI,aAAa,EAAE,CAOnD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAc3F;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAStD;AAED,wBAAgB,iBAAiB,IAAI,kBAAkB,EAAE,CAExD"}
@@ -0,0 +1,62 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ const USER_PROVIDERS_PATH = join(homedir(), '.qulib', 'providers.json');
5
+ export function loadUserProviders() {
6
+ const raw = loadSerialized();
7
+ return raw.map((p) => ({
8
+ id: p.id,
9
+ label: p.label,
10
+ patterns: p.patterns.map((src) => new RegExp(src, 'i')),
11
+ }));
12
+ }
13
+ export function addUserProvider(input) {
14
+ const existing = loadSerialized();
15
+ const idx = existing.findIndex((p) => p.id === input.id);
16
+ if (idx >= 0) {
17
+ const p = existing[idx];
18
+ if (!p.patterns.includes(input.pattern)) {
19
+ p.patterns.push(input.pattern);
20
+ }
21
+ p.label = input.label;
22
+ }
23
+ else {
24
+ existing.push({ id: input.id, label: input.label, patterns: [input.pattern] });
25
+ }
26
+ ensureDir();
27
+ writeFileSync(USER_PROVIDERS_PATH, JSON.stringify(existing, null, 2), 'utf-8');
28
+ }
29
+ export function removeUserProvider(id) {
30
+ const existing = loadSerialized();
31
+ const filtered = existing.filter((p) => p.id !== id);
32
+ if (filtered.length === existing.length) {
33
+ return false;
34
+ }
35
+ ensureDir();
36
+ writeFileSync(USER_PROVIDERS_PATH, JSON.stringify(filtered, null, 2), 'utf-8');
37
+ return true;
38
+ }
39
+ export function listUserProviders() {
40
+ return loadSerialized();
41
+ }
42
+ function loadSerialized() {
43
+ if (!existsSync(USER_PROVIDERS_PATH)) {
44
+ return [];
45
+ }
46
+ try {
47
+ const parsed = JSON.parse(readFileSync(USER_PROVIDERS_PATH, 'utf-8'));
48
+ if (!Array.isArray(parsed)) {
49
+ return [];
50
+ }
51
+ return parsed;
52
+ }
53
+ catch {
54
+ return [];
55
+ }
56
+ }
57
+ function ensureDir() {
58
+ const dir = dirname(USER_PROVIDERS_PATH);
59
+ if (!existsSync(dir)) {
60
+ mkdirSync(dir, { recursive: true });
61
+ }
62
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qulib/core",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Qulib — analyze deployed web apps for honest quality gaps (CLI + programmatic API)",
5
5
  "license": "MIT",
6
6
  "author": "Tapesh Nagarwal",
@@ -37,7 +37,11 @@
37
37
  "dev": "tsx src/cli/index.ts",
38
38
  "analyze": "tsx src/cli/index.ts analyze",
39
39
  "clean": "tsx src/cli/index.ts clean",
40
- "build": "tsc"
40
+ "build": "tsc",
41
+ "test": "node --import tsx/esm --test src/llm/cost-intelligence.test.ts src/tools/gap-engine.test.ts src/tools/auth-block-gap.test.ts",
42
+ "test:integration": "node --import tsx/esm --test src/analyze.integration.test.ts",
43
+ "smoke": "tsx src/cli/index.ts analyze --url https://example.com --ephemeral",
44
+ "cost-doctor": "tsx src/cli/index.ts cost doctor"
41
45
  },
42
46
  "dependencies": {
43
47
  "@axe-core/playwright": "^4.9.0",