@qulib/mcp 0.4.2 → 0.4.3

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.
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ const requirePkg = createRequire(import.meta.url);
16
16
  const pkg = requirePkg('../package.json');
17
17
  import { analyzeApp, detectAuth, exploreAuth, scanRepo, computeAutomationMaturity, } from '@qulib/core';
18
18
  import { z } from 'zod';
19
- import { buildCompactAnalyzePayload } from './compact-analyze-payload.js';
19
+ import { summarizeAnalyzeResult } from './summarize-analyze-result.js';
20
20
  import { log } from './logger.js';
21
21
  function toolError(code, message, detail) {
22
22
  return {
@@ -202,7 +202,7 @@ mcpServer.registerTool('analyze_app', {
202
202
  progressLog: mcpProgressLog,
203
203
  telemetry: telemetrySink,
204
204
  });
205
- const payload = buildCompactAnalyzePayload(result, input.includeFullReport === true);
205
+ const payload = summarizeAnalyzeResult(result, input.includeFullReport === true);
206
206
  return {
207
207
  content: [
208
208
  {
@@ -0,0 +1,164 @@
1
+ import type { AnalyzeResult } from '@qulib/core';
2
+ export declare function summarizeAnalyzeResult(result: AnalyzeResult, includeFullReport: boolean): AnalyzeResult | {
3
+ includeFullReport: boolean;
4
+ note: string;
5
+ detectedAuth?: {
6
+ type: "unknown" | "form-login" | "oauth" | "magic-link" | "none";
7
+ loginUrl: string | null;
8
+ provider: string | null;
9
+ hasAuth: boolean;
10
+ observedSelectors: {
11
+ usernameSelector: string | null;
12
+ passwordSelector: string | null;
13
+ submitSelector: string | null;
14
+ } | null;
15
+ oauthButtons: {
16
+ text: string;
17
+ provider: string;
18
+ }[];
19
+ recommendation: string;
20
+ authOptions?: {
21
+ type: "unknown" | "form-login" | "oauth" | "oauth-unknown" | "form-multi" | "magic-link";
22
+ label: string;
23
+ id: string;
24
+ provider: string | null;
25
+ source: "built-in" | "user-local" | "heuristic";
26
+ automatable: boolean;
27
+ confidence: "high" | "medium" | "low";
28
+ requirements: {
29
+ method: "storage-state";
30
+ instruction: string;
31
+ } | {
32
+ method: "credentials";
33
+ fields: {
34
+ type: "password" | "text" | "email" | "select" | "checkbox";
35
+ name: string;
36
+ label: string;
37
+ observedOptions: string[];
38
+ }[];
39
+ } | {
40
+ method: "unknown";
41
+ instruction: string;
42
+ };
43
+ }[] | undefined;
44
+ } | undefined;
45
+ repoInventorySummary: {
46
+ framework?: {
47
+ primary: "unknown" | "nextjs-app-router" | "nextjs-pages-router" | "express" | "remix" | "nuxt" | "sveltekit" | "astro" | "vite";
48
+ confidence: "high" | "medium" | "low";
49
+ testFrameworks: ("playwright" | "cypress-e2e" | "cypress-component" | "jest" | "vitest" | "other")[];
50
+ evidenceCount: number;
51
+ } | undefined;
52
+ repoPath: string;
53
+ scannedAt: string;
54
+ routeCount: number;
55
+ testFileCount: number;
56
+ missingTestIdCount: number;
57
+ interactiveTsxFilesScanned: number | null;
58
+ cypressDetected: boolean;
59
+ } | null;
60
+ decisionLogPreview: {
61
+ timestamp: string;
62
+ reason: string;
63
+ phase: "observe" | "think" | "act" | "harness";
64
+ decision: string;
65
+ metadata?: Record<string, unknown> | undefined;
66
+ }[];
67
+ automationMaturitySummary?: {
68
+ overallScore: number;
69
+ level: number;
70
+ label: string;
71
+ topRecommendations: string[];
72
+ } | undefined;
73
+ summary: {
74
+ status: import("@qulib/core").AnalyzeStatus;
75
+ coverageScore: number | null;
76
+ releaseConfidence: number | null;
77
+ mode: "url-only" | "url-repo" | "auth-required";
78
+ coveragePagesScanned: number;
79
+ coverageBudgetExceeded: boolean;
80
+ coverageWarning: "auth-required" | "budget-exceeded" | "low-coverage" | "navigation-failures" | null;
81
+ gapCount: number;
82
+ scenarioCount: number;
83
+ generatedTestCount: number;
84
+ publicSurface: {
85
+ pageCount: number;
86
+ gapCount: number;
87
+ accessibilityViolationCount: number;
88
+ brokenLinkCount: number;
89
+ } | null;
90
+ };
91
+ topGaps: {
92
+ path: string;
93
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage";
94
+ severity: "critical" | "high" | "medium" | "low";
95
+ reason: string;
96
+ }[];
97
+ costIntelligenceSummary: {
98
+ maxOutputTokensPerLlmCall: number;
99
+ usageDataQuality: "none" | "actual" | "estimated" | "mixed";
100
+ totalInputTokens: number;
101
+ totalOutputTokens: number;
102
+ budgetWarningCount: number;
103
+ maturityLevel: number;
104
+ maturityLabel: string;
105
+ } | null;
106
+ costIntelligence: {
107
+ maxOutputTokensPerLlmCall: number;
108
+ budgetRole: "max-output-tokens-per-llm-call";
109
+ records: {
110
+ provider: string;
111
+ model: string;
112
+ inputTokens: number;
113
+ outputTokens: number;
114
+ operationType: "scenario-generation";
115
+ timestamp: string;
116
+ dataQuality: "none" | "actual" | "estimated" | "mixed";
117
+ estimatedCostUsd?: number | undefined;
118
+ promptHash?: string | undefined;
119
+ resultHash?: string | undefined;
120
+ notes?: string | undefined;
121
+ }[];
122
+ budgetWarnings: string[];
123
+ usageSummary: {
124
+ dataQuality: "none" | "actual" | "estimated" | "mixed";
125
+ totalInputTokens: number;
126
+ totalOutputTokens: number;
127
+ };
128
+ repeatedOperations: {
129
+ recommendation: string;
130
+ promptHash: string;
131
+ count: number;
132
+ }[];
133
+ deterministicMaturity: {
134
+ label: string;
135
+ level: number;
136
+ rationale: string;
137
+ ceilingNote?: string | undefined;
138
+ };
139
+ conversionRecommendations: string[];
140
+ } | null;
141
+ nextDeterministicChecks: string[];
142
+ gapAnalysisPreview: {
143
+ analyzedAt: string;
144
+ gapsSample: {
145
+ path: string;
146
+ id: string;
147
+ severity: "critical" | "high" | "medium" | "low";
148
+ reason: string;
149
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage";
150
+ recommendation?: string | undefined;
151
+ description?: string | undefined;
152
+ }[];
153
+ scenariosOmitted: number;
154
+ generatedTestsOmitted: number;
155
+ };
156
+ routeInventorySummary: {
157
+ scannedAt: string;
158
+ baseUrl: string;
159
+ routeCount: number;
160
+ pagesSkipped: number;
161
+ budgetExceeded: boolean;
162
+ };
163
+ };
164
+ //# sourceMappingURL=summarize-analyze-result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summarize-analyze-result.d.ts","sourceRoot":"","sources":["../src/summarize-analyze-result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAqBjD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,aAAa,EAAE,iBAAiB,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4BAuGu+d,CAAC;sBAA4C,CAAC;sBAA4C,CAAC;iBAAuC,CAAC;;;;;;;;;;;;;;;;;uBAAghB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;EADltf"}
@@ -0,0 +1,115 @@
1
+ const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
2
+ function topGapsBySeverity(gaps, limit) {
3
+ return [...gaps].sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]).slice(0, limit);
4
+ }
5
+ function nextDeterministicChecks(gaps, conversion) {
6
+ const out = [];
7
+ const byCat = new Map();
8
+ for (const g of gaps) {
9
+ byCat.set(g.category, (byCat.get(g.category) ?? 0) + 1);
10
+ }
11
+ for (const [cat, n] of [...byCat.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3)) {
12
+ out.push(`Add or tighten deterministic coverage for **${cat}** (${n} gap(s) in this scan).`);
13
+ }
14
+ out.push(...conversion.slice(0, 2));
15
+ return out.slice(0, 5);
16
+ }
17
+ export function summarizeAnalyzeResult(result, includeFullReport) {
18
+ if (includeFullReport) {
19
+ return result;
20
+ }
21
+ const g = result.gapAnalysis;
22
+ const ci = g.costIntelligence;
23
+ const top = topGapsBySeverity(result.gaps, 5);
24
+ const costSummary = ci
25
+ ? {
26
+ maxOutputTokensPerLlmCall: ci.maxOutputTokensPerLlmCall,
27
+ usageDataQuality: ci.usageSummary.dataQuality,
28
+ totalInputTokens: ci.usageSummary.totalInputTokens,
29
+ totalOutputTokens: ci.usageSummary.totalOutputTokens,
30
+ budgetWarningCount: ci.budgetWarnings.length,
31
+ maturityLevel: ci.deterministicMaturity.level,
32
+ maturityLabel: ci.deterministicMaturity.label,
33
+ }
34
+ : null;
35
+ const ps = result.publicSurface;
36
+ const repo = result.repoInventory;
37
+ const repoInventorySummary = repo
38
+ ? {
39
+ repoPath: repo.repoPath,
40
+ scannedAt: repo.scannedAt,
41
+ routeCount: repo.routes.length,
42
+ testFileCount: repo.testFiles.length,
43
+ missingTestIdCount: repo.missingTestIds.length,
44
+ interactiveTsxFilesScanned: repo.interactiveTsxFilesScanned ?? null,
45
+ cypressDetected: repo.cypressStructure.detected,
46
+ ...(repo.framework && {
47
+ framework: {
48
+ primary: repo.framework.primary,
49
+ confidence: repo.framework.confidence,
50
+ testFrameworks: repo.framework.testFrameworks,
51
+ evidenceCount: repo.framework.evidence.length,
52
+ },
53
+ }),
54
+ }
55
+ : null;
56
+ return {
57
+ summary: {
58
+ status: result.status,
59
+ coverageScore: result.coverageScore,
60
+ releaseConfidence: g.releaseConfidence,
61
+ mode: g.mode,
62
+ coveragePagesScanned: g.coveragePagesScanned,
63
+ coverageBudgetExceeded: g.coverageBudgetExceeded,
64
+ coverageWarning: g.coverageWarning ?? null,
65
+ gapCount: g.gaps.length,
66
+ scenarioCount: g.scenarios.length,
67
+ generatedTestCount: g.generatedTests.length,
68
+ publicSurface: ps === null
69
+ ? null
70
+ : {
71
+ pageCount: ps.pages.length,
72
+ gapCount: ps.gaps.length,
73
+ accessibilityViolationCount: ps.accessibilityViolations.length,
74
+ brokenLinkCount: ps.brokenLinks.length,
75
+ },
76
+ },
77
+ topGaps: top.map((x) => ({
78
+ path: x.path,
79
+ category: x.category,
80
+ severity: x.severity,
81
+ reason: x.reason,
82
+ })),
83
+ costIntelligenceSummary: costSummary,
84
+ costIntelligence: ci ?? null,
85
+ nextDeterministicChecks: ci
86
+ ? nextDeterministicChecks(result.gaps, ci.conversionRecommendations)
87
+ : nextDeterministicChecks(result.gaps, []),
88
+ gapAnalysisPreview: {
89
+ analyzedAt: g.analyzedAt,
90
+ gapsSample: g.gaps.slice(0, 8),
91
+ scenariosOmitted: g.scenarios.length,
92
+ generatedTestsOmitted: g.generatedTests.length,
93
+ },
94
+ routeInventorySummary: {
95
+ scannedAt: result.routeInventory.scannedAt,
96
+ baseUrl: result.routeInventory.baseUrl,
97
+ routeCount: result.routeInventory.routes.length,
98
+ pagesSkipped: result.routeInventory.pagesSkipped,
99
+ budgetExceeded: result.routeInventory.budgetExceeded,
100
+ },
101
+ ...(repo?.automationMaturity && {
102
+ automationMaturitySummary: {
103
+ overallScore: repo.automationMaturity.overallScore,
104
+ level: repo.automationMaturity.level,
105
+ label: repo.automationMaturity.label,
106
+ topRecommendations: repo.automationMaturity.topRecommendations,
107
+ },
108
+ }),
109
+ repoInventorySummary,
110
+ decisionLogPreview: result.decisionLog.slice(-8),
111
+ ...(result.detectedAuth !== undefined && { detectedAuth: result.detectedAuth }),
112
+ includeFullReport: false,
113
+ note: 'Summary-first payload. Pass includeFullReport: true for the full gapAnalysis (all scenarios, generated tests) and the full repoInventory (test files, missing test IDs).',
114
+ };
115
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qulib/mcp",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "MCP server for Qulib — AI-callable QA gap analysis",
5
5
  "license": "MIT",
6
6
  "author": "Tapesh Nagarwal",
@@ -29,11 +29,11 @@
29
29
  "scripts": {
30
30
  "build": "npm --prefix ../.. run build -w @qulib/core && tsc && chmod +x dist/index.js",
31
31
  "dev": "tsx src/index.ts",
32
- "test": "node --import tsx/esm --test src/compact-analyze-payload.test.ts"
32
+ "test": "node --import tsx/esm --test src/__tests__/summarize-analyze-result.test.ts"
33
33
  },
34
34
  "dependencies": {
35
35
  "@modelcontextprotocol/sdk": "^1.0.0",
36
- "@qulib/core": "0.4.2",
36
+ "@qulib/core": "0.4.3",
37
37
  "zod": "^3.23.0"
38
38
  },
39
39
  "devDependencies": {