@bryan-thompson/inspector-assessment-client 1.6.0 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{OAuthCallback-ZcXdfhZQ.js → OAuthCallback-cGhwkoyY.js} +1 -1
- package/dist/assets/{OAuthDebugCallback-xt1SlIHS.js → OAuthDebugCallback-2rmUqser.js} +1 -1
- package/dist/assets/{index-B3lTiDVe.js → index-BnFixpvH.js} +4 -4
- package/dist/index.html +1 -1
- package/lib/lib/assessmentTypes.d.ts +670 -0
- package/lib/lib/assessmentTypes.d.ts.map +1 -0
- package/lib/lib/assessmentTypes.js +220 -0
- package/lib/lib/aupPatterns.d.ts +63 -0
- package/lib/lib/aupPatterns.d.ts.map +1 -0
- package/lib/lib/aupPatterns.js +344 -0
- package/lib/lib/prohibitedLibraries.d.ts +76 -0
- package/lib/lib/prohibitedLibraries.d.ts.map +1 -0
- package/lib/lib/prohibitedLibraries.js +364 -0
- package/lib/lib/securityPatterns.d.ts +64 -0
- package/lib/lib/securityPatterns.d.ts.map +1 -0
- package/lib/lib/securityPatterns.js +453 -0
- package/lib/services/assessment/AssessmentOrchestrator.d.ts +88 -0
- package/lib/services/assessment/AssessmentOrchestrator.d.ts.map +1 -0
- package/lib/services/assessment/AssessmentOrchestrator.js +418 -0
- package/lib/services/assessment/ResponseValidator.d.ts +69 -0
- package/lib/services/assessment/ResponseValidator.d.ts.map +1 -0
- package/lib/services/assessment/ResponseValidator.js +1038 -0
- package/lib/services/assessment/TestDataGenerator.d.ts +86 -0
- package/lib/services/assessment/TestDataGenerator.d.ts.map +1 -0
- package/lib/services/assessment/TestDataGenerator.js +669 -0
- package/lib/services/assessment/TestScenarioEngine.d.ts +91 -0
- package/lib/services/assessment/TestScenarioEngine.d.ts.map +1 -0
- package/lib/services/assessment/TestScenarioEngine.js +505 -0
- package/lib/services/assessment/ToolClassifier.d.ts +61 -0
- package/lib/services/assessment/ToolClassifier.d.ts.map +1 -0
- package/lib/services/assessment/ToolClassifier.js +349 -0
- package/lib/services/assessment/lib/claudeCodeBridge.d.ts +160 -0
- package/lib/services/assessment/lib/claudeCodeBridge.d.ts.map +1 -0
- package/lib/services/assessment/lib/claudeCodeBridge.js +357 -0
- package/lib/services/assessment/modules/AUPComplianceAssessor.d.ts +100 -0
- package/lib/services/assessment/modules/AUPComplianceAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/AUPComplianceAssessor.js +474 -0
- package/lib/services/assessment/modules/BaseAssessor.d.ts +71 -0
- package/lib/services/assessment/modules/BaseAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/BaseAssessor.js +171 -0
- package/lib/services/assessment/modules/DocumentationAssessor.d.ts +45 -0
- package/lib/services/assessment/modules/DocumentationAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/DocumentationAssessor.js +355 -0
- package/lib/services/assessment/modules/ErrorHandlingAssessor.d.ts +25 -0
- package/lib/services/assessment/modules/ErrorHandlingAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/ErrorHandlingAssessor.js +564 -0
- package/lib/services/assessment/modules/FunctionalityAssessor.d.ts +20 -0
- package/lib/services/assessment/modules/FunctionalityAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/FunctionalityAssessor.js +253 -0
- package/lib/services/assessment/modules/MCPSpecComplianceAssessor.d.ts +70 -0
- package/lib/services/assessment/modules/MCPSpecComplianceAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/MCPSpecComplianceAssessor.js +508 -0
- package/lib/services/assessment/modules/ManifestValidationAssessor.d.ts +70 -0
- package/lib/services/assessment/modules/ManifestValidationAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/ManifestValidationAssessor.js +430 -0
- package/lib/services/assessment/modules/PortabilityAssessor.d.ts +43 -0
- package/lib/services/assessment/modules/PortabilityAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/PortabilityAssessor.js +347 -0
- package/lib/services/assessment/modules/ProhibitedLibrariesAssessor.d.ts +41 -0
- package/lib/services/assessment/modules/ProhibitedLibrariesAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/ProhibitedLibrariesAssessor.js +256 -0
- package/lib/services/assessment/modules/SecurityAssessor.d.ts +176 -0
- package/lib/services/assessment/modules/SecurityAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/SecurityAssessor.js +1333 -0
- package/lib/services/assessment/modules/ToolAnnotationAssessor.d.ts +96 -0
- package/lib/services/assessment/modules/ToolAnnotationAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/ToolAnnotationAssessor.js +593 -0
- package/lib/services/assessment/modules/UsabilityAssessor.d.ts +21 -0
- package/lib/services/assessment/modules/UsabilityAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/UsabilityAssessor.js +241 -0
- package/lib/services/assessment/modules/index.d.ts +33 -0
- package/lib/services/assessment/modules/index.d.ts.map +1 -0
- package/lib/services/assessment/modules/index.js +35 -0
- package/package.json +15 -3
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Assessor Class
|
|
3
|
+
* Provides common functionality for all assessment modules
|
|
4
|
+
*/
|
|
5
|
+
export class BaseAssessor {
|
|
6
|
+
config;
|
|
7
|
+
testCount = 0;
|
|
8
|
+
constructor(config) {
|
|
9
|
+
this.config = config;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Common method to determine status based on pass rate
|
|
13
|
+
*/
|
|
14
|
+
determineStatus(passed, total, threshold = 0.8) {
|
|
15
|
+
if (total === 0)
|
|
16
|
+
return "NEED_MORE_INFO";
|
|
17
|
+
const passRate = passed / total;
|
|
18
|
+
if (passRate >= threshold)
|
|
19
|
+
return "PASS";
|
|
20
|
+
if (passRate >= threshold * 0.5)
|
|
21
|
+
return "NEED_MORE_INFO";
|
|
22
|
+
return "FAIL";
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Log assessment progress
|
|
26
|
+
*/
|
|
27
|
+
log(message) {
|
|
28
|
+
console.log(`[${this.constructor.name}] ${message}`);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Log error
|
|
32
|
+
*/
|
|
33
|
+
logError(message, error) {
|
|
34
|
+
console.error(`[${this.constructor.name}] ${message}`, error);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Get test count for this assessor
|
|
38
|
+
*/
|
|
39
|
+
getTestCount() {
|
|
40
|
+
return this.testCount;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Reset test count
|
|
44
|
+
*/
|
|
45
|
+
resetTestCount() {
|
|
46
|
+
this.testCount = 0;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Check if a feature is enabled in configuration
|
|
50
|
+
*/
|
|
51
|
+
isFeatureEnabled(feature) {
|
|
52
|
+
return this.config.assessmentCategories?.[feature] ?? false;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Sleep for specified milliseconds (useful for rate limiting)
|
|
56
|
+
*/
|
|
57
|
+
async sleep(ms) {
|
|
58
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Execute with timeout
|
|
62
|
+
*/
|
|
63
|
+
async executeWithTimeout(promise, timeoutMs = this.config.testTimeout) {
|
|
64
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
65
|
+
setTimeout(() => reject(new Error(`Operation timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
66
|
+
});
|
|
67
|
+
return Promise.race([promise, timeoutPromise]);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Safe JSON parse with error handling
|
|
71
|
+
*/
|
|
72
|
+
safeJsonParse(text) {
|
|
73
|
+
try {
|
|
74
|
+
return JSON.parse(text);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
this.logError(`Failed to parse JSON: ${text}`, error);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Extract error message from various error types
|
|
83
|
+
*/
|
|
84
|
+
extractErrorMessage(error) {
|
|
85
|
+
if (typeof error === "string")
|
|
86
|
+
return error;
|
|
87
|
+
if (error?.message)
|
|
88
|
+
return error.message;
|
|
89
|
+
if (error?.error)
|
|
90
|
+
return this.extractErrorMessage(error.error);
|
|
91
|
+
if (error?.content) {
|
|
92
|
+
if (Array.isArray(error.content)) {
|
|
93
|
+
return error.content
|
|
94
|
+
.map((c) => c.text || c.content || "")
|
|
95
|
+
.join(" ");
|
|
96
|
+
}
|
|
97
|
+
return error.content;
|
|
98
|
+
}
|
|
99
|
+
return JSON.stringify(error);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Check if a response indicates an error
|
|
103
|
+
* Handles various MCP response formats
|
|
104
|
+
*
|
|
105
|
+
* @param response - The response to check
|
|
106
|
+
* @param strictMode - If true, only check explicit error indicators (default: false)
|
|
107
|
+
*/
|
|
108
|
+
isErrorResponse(response, strictMode = false) {
|
|
109
|
+
if (!response)
|
|
110
|
+
return false;
|
|
111
|
+
// Check explicit error flag first (always check these)
|
|
112
|
+
if (response.isError === true || response.error !== undefined) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
// In strict mode, only rely on explicit error indicators
|
|
116
|
+
// Used by FunctionalityAssessor where we expect valid responses
|
|
117
|
+
if (strictMode) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
// In non-strict mode, also check content for error patterns
|
|
121
|
+
// Used by ErrorHandlingAssessor where we're deliberately triggering errors
|
|
122
|
+
if (response.content) {
|
|
123
|
+
if (typeof response.content === "string") {
|
|
124
|
+
const lower = response.content.toLowerCase();
|
|
125
|
+
// Only flag if error appears at start or with strong indicators
|
|
126
|
+
return (lower.startsWith("error:") ||
|
|
127
|
+
lower.startsWith("error ") ||
|
|
128
|
+
lower.includes("error occurred") ||
|
|
129
|
+
lower.includes("failed to") ||
|
|
130
|
+
lower.includes("exception:"));
|
|
131
|
+
}
|
|
132
|
+
else if (Array.isArray(response.content)) {
|
|
133
|
+
// Check if any text content starts with error indicators
|
|
134
|
+
return response.content.some((c) => {
|
|
135
|
+
if (c.type !== "text" || !c.text)
|
|
136
|
+
return false;
|
|
137
|
+
const lower = c.text.toLowerCase();
|
|
138
|
+
return (lower.startsWith("error:") ||
|
|
139
|
+
lower.startsWith("error ") ||
|
|
140
|
+
lower.includes("error occurred") ||
|
|
141
|
+
lower.includes("failed to") ||
|
|
142
|
+
lower.includes("exception:"));
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Extract error information from a response
|
|
150
|
+
*/
|
|
151
|
+
extractErrorInfo(response) {
|
|
152
|
+
if (!response)
|
|
153
|
+
return {};
|
|
154
|
+
// Extract text from content array if present
|
|
155
|
+
let contentText;
|
|
156
|
+
if (Array.isArray(response.content)) {
|
|
157
|
+
const textContent = response.content.find((c) => c.type === "text");
|
|
158
|
+
contentText = textContent?.text;
|
|
159
|
+
}
|
|
160
|
+
else if (typeof response.content === "string") {
|
|
161
|
+
contentText = response.content;
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
code: response.errorCode || response.code || response.error?.code,
|
|
165
|
+
message: response.errorMessage ||
|
|
166
|
+
response.message ||
|
|
167
|
+
response.error?.message ||
|
|
168
|
+
contentText,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documentation Assessor Module
|
|
3
|
+
* Evaluates documentation quality and completeness
|
|
4
|
+
*/
|
|
5
|
+
import { DocumentationAssessment } from "../../../lib/assessmentTypes.js";
|
|
6
|
+
import { BaseAssessor } from "./BaseAssessor.js";
|
|
7
|
+
import { AssessmentContext } from "../AssessmentOrchestrator.js";
|
|
8
|
+
export declare class DocumentationAssessor extends BaseAssessor {
|
|
9
|
+
assess(context: AssessmentContext): Promise<DocumentationAssessment>;
|
|
10
|
+
private analyzeDocumentation;
|
|
11
|
+
/**
|
|
12
|
+
* Extract functional example prompts from documentation.
|
|
13
|
+
* Only counts user-facing examples, not configuration or installation code.
|
|
14
|
+
*/
|
|
15
|
+
private extractFunctionalExamples;
|
|
16
|
+
/**
|
|
17
|
+
* Check if a code block is non-functional (config, install, implementation).
|
|
18
|
+
*/
|
|
19
|
+
private isNonFunctionalCodeBlock;
|
|
20
|
+
/**
|
|
21
|
+
* Score functional example quality (returns true if meets minimum threshold).
|
|
22
|
+
*/
|
|
23
|
+
private scoreFunctionalExample;
|
|
24
|
+
/**
|
|
25
|
+
* Get line number for a position in content.
|
|
26
|
+
*/
|
|
27
|
+
private getLineNumber;
|
|
28
|
+
/**
|
|
29
|
+
* Remove duplicate examples based on similarity.
|
|
30
|
+
*/
|
|
31
|
+
private deduplicateExamples;
|
|
32
|
+
/**
|
|
33
|
+
* @deprecated Use extractFunctionalExamples() instead.
|
|
34
|
+
* This method counts ALL code blocks including configs and install commands.
|
|
35
|
+
*/
|
|
36
|
+
private extractCodeExamples;
|
|
37
|
+
private checkInstallInstructions;
|
|
38
|
+
private checkUsageGuide;
|
|
39
|
+
private checkAPIReference;
|
|
40
|
+
private extractSection;
|
|
41
|
+
private determineDocumentationStatus;
|
|
42
|
+
private generateExplanation;
|
|
43
|
+
private generateRecommendations;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=DocumentationAssessor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DocumentationAssessor.d.ts","sourceRoot":"","sources":["../../../../src/services/assessment/modules/DocumentationAssessor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,uBAAuB,EAIxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAE9D,qBAAa,qBAAsB,SAAQ,YAAY;IAC/C,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,uBAAuB,CAAC;IAkB1E,OAAO,CAAC,oBAAoB;IAsF5B;;;OAGG;IACH,OAAO,CAAC,yBAAyB;IAuEjC;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAiBhC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAmC9B;;OAEG;IACH,OAAO,CAAC,aAAa;IAKrB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAqB3B;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAyC3B,OAAO,CAAC,wBAAwB;IAchC,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,iBAAiB;IAezB,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,4BAA4B;IAmBpC,OAAO,CAAC,mBAAmB;IAyB3B,OAAO,CAAC,uBAAuB;CA+BhC"}
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documentation Assessor Module
|
|
3
|
+
* Evaluates documentation quality and completeness
|
|
4
|
+
*/
|
|
5
|
+
import { BaseAssessor } from "./BaseAssessor.js";
|
|
6
|
+
export class DocumentationAssessor extends BaseAssessor {
|
|
7
|
+
async assess(context) {
|
|
8
|
+
this.log("Starting documentation assessment");
|
|
9
|
+
const readmeContent = context.readmeContent || "";
|
|
10
|
+
const metrics = this.analyzeDocumentation(readmeContent, context.tools);
|
|
11
|
+
const status = this.determineDocumentationStatus(metrics);
|
|
12
|
+
const explanation = this.generateExplanation(metrics);
|
|
13
|
+
const recommendations = this.generateRecommendations(metrics);
|
|
14
|
+
return {
|
|
15
|
+
metrics,
|
|
16
|
+
status,
|
|
17
|
+
explanation,
|
|
18
|
+
recommendations,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
analyzeDocumentation(content, tools) {
|
|
22
|
+
const hasReadme = content.length > 0;
|
|
23
|
+
// Use new functional examples method that filters out configs/installs
|
|
24
|
+
const functionalExamples = this.extractFunctionalExamples(content);
|
|
25
|
+
// Keep old method for backward compatibility (extractedExamples field)
|
|
26
|
+
const allCodeExamples = this.extractCodeExamples(content);
|
|
27
|
+
const hasInstallInstructions = this.checkInstallInstructions(content);
|
|
28
|
+
const hasUsageGuide = this.checkUsageGuide(content);
|
|
29
|
+
const hasAPIReference = this.checkAPIReference(content);
|
|
30
|
+
// Check which tools are documented
|
|
31
|
+
const missingExamples = [];
|
|
32
|
+
let documentedToolsCount = 0;
|
|
33
|
+
if (tools && tools.length > 0) {
|
|
34
|
+
// Check each tool for documentation
|
|
35
|
+
for (const tool of tools) {
|
|
36
|
+
const toolName = tool.name;
|
|
37
|
+
const hasDescription = tool.description && tool.description.trim().length > 0;
|
|
38
|
+
// Check if tool is mentioned in headings (any level) or code examples
|
|
39
|
+
const headingRegex = new RegExp(`^#{1,6}\\s+${toolName}`, "mi");
|
|
40
|
+
const mentionRegex = new RegExp(`\\b${toolName}\\b`, "i");
|
|
41
|
+
const hasHeading = headingRegex.test(content);
|
|
42
|
+
const hasMention = mentionRegex.test(content);
|
|
43
|
+
// Count as documented if mentioned in README
|
|
44
|
+
if (hasHeading || hasMention) {
|
|
45
|
+
documentedToolsCount++;
|
|
46
|
+
}
|
|
47
|
+
// Tool is missing if it has no description AND not documented in README
|
|
48
|
+
if (!hasDescription && !hasHeading && !hasMention) {
|
|
49
|
+
missingExamples.push(toolName);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
// Fallback to generic example checking if no tools provided
|
|
55
|
+
if (functionalExamples.length < 1)
|
|
56
|
+
missingExamples.push("Basic usage example");
|
|
57
|
+
if (!functionalExamples.some((e) => e.description?.includes("error")) &&
|
|
58
|
+
!allCodeExamples.some((e) => e.description?.includes("error"))) {
|
|
59
|
+
missingExamples.push("Error handling example");
|
|
60
|
+
}
|
|
61
|
+
if (!functionalExamples.some((e) => e.description?.includes("config")) &&
|
|
62
|
+
!allCodeExamples.some((e) => e.description?.includes("config"))) {
|
|
63
|
+
missingExamples.push("Configuration example");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Required examples: 3 minimum (Anthropic's standard)
|
|
67
|
+
const requiredExamples = 3;
|
|
68
|
+
// Count functional examples only (not configs/installs)
|
|
69
|
+
// For servers with tools, also count documented tools
|
|
70
|
+
const functionalExampleCount = functionalExamples.length +
|
|
71
|
+
(tools && tools.length > 0 ? documentedToolsCount : 0);
|
|
72
|
+
return {
|
|
73
|
+
hasReadme,
|
|
74
|
+
exampleCount: functionalExampleCount, // Use functional examples instead of all code blocks
|
|
75
|
+
requiredExamples,
|
|
76
|
+
missingExamples,
|
|
77
|
+
hasInstallInstructions,
|
|
78
|
+
hasUsageGuide,
|
|
79
|
+
hasAPIReference,
|
|
80
|
+
extractedExamples: allCodeExamples, // Keep for backward compatibility
|
|
81
|
+
installInstructions: hasInstallInstructions
|
|
82
|
+
? this.extractSection(content, "install")
|
|
83
|
+
: undefined,
|
|
84
|
+
usageInstructions: hasUsageGuide
|
|
85
|
+
? this.extractSection(content, "usage")
|
|
86
|
+
: undefined,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Extract functional example prompts from documentation.
|
|
91
|
+
* Only counts user-facing examples, not configuration or installation code.
|
|
92
|
+
*/
|
|
93
|
+
extractFunctionalExamples(content) {
|
|
94
|
+
const functionalExamples = [];
|
|
95
|
+
// Pattern 1: Look for standalone lines with tool triggers (use context7, use library, with X)
|
|
96
|
+
const standalonePromptRegex = /^[ \t]*([A-Z][^\n]{10,300}?(?:use (?:context7|library|@?[\w-]+\/[\w-]+)|with \S+)[^\n]*?)[ \t]*$/gim;
|
|
97
|
+
let standaloneMatch;
|
|
98
|
+
while ((standaloneMatch = standalonePromptRegex.exec(content)) !== null) {
|
|
99
|
+
const prompt = standaloneMatch[1].trim();
|
|
100
|
+
// Filter out non-functional examples
|
|
101
|
+
if (!this.isNonFunctionalCodeBlock(prompt) &&
|
|
102
|
+
this.scoreFunctionalExample(prompt)) {
|
|
103
|
+
functionalExamples.push({
|
|
104
|
+
code: prompt,
|
|
105
|
+
language: "prompt",
|
|
106
|
+
description: "Functional example prompt",
|
|
107
|
+
lineNumber: this.getLineNumber(content, standaloneMatch.index),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Pattern 2: Look for bulleted examples
|
|
112
|
+
const bulletedExampleRegex = /^[ \t]*[-*][ \t]+([A-Z][^\n]{10,300}?)[ \t]*$/gim;
|
|
113
|
+
let bulletMatch;
|
|
114
|
+
while ((bulletMatch = bulletedExampleRegex.exec(content)) !== null) {
|
|
115
|
+
const prompt = bulletMatch[1].trim();
|
|
116
|
+
// Must have tool trigger or action verb + technical term
|
|
117
|
+
if (!this.isNonFunctionalCodeBlock(prompt) &&
|
|
118
|
+
this.scoreFunctionalExample(prompt)) {
|
|
119
|
+
functionalExamples.push({
|
|
120
|
+
code: prompt,
|
|
121
|
+
language: "prompt",
|
|
122
|
+
description: "Bulleted example",
|
|
123
|
+
lineNumber: this.getLineNumber(content, bulletMatch.index),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Pattern 3: Look for inline examples with labels
|
|
128
|
+
const inlineExampleRegex = /(?:Try|Example|Usage):\s*["`']([^"`']{10,300}?)["`']/gi;
|
|
129
|
+
let inlineMatch;
|
|
130
|
+
while ((inlineMatch = inlineExampleRegex.exec(content)) !== null) {
|
|
131
|
+
const prompt = inlineMatch[1].trim();
|
|
132
|
+
if (!this.isNonFunctionalCodeBlock(prompt) &&
|
|
133
|
+
this.scoreFunctionalExample(prompt)) {
|
|
134
|
+
functionalExamples.push({
|
|
135
|
+
code: prompt,
|
|
136
|
+
language: "prompt",
|
|
137
|
+
description: "Inline example",
|
|
138
|
+
lineNumber: this.getLineNumber(content, inlineMatch.index),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Remove duplicates based on similar content
|
|
143
|
+
return this.deduplicateExamples(functionalExamples);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Check if a code block is non-functional (config, install, implementation).
|
|
147
|
+
*/
|
|
148
|
+
isNonFunctionalCodeBlock(text) {
|
|
149
|
+
const excludePatterns = [
|
|
150
|
+
/^\s*{\s*["']mcpServers["']/i, // JSON config
|
|
151
|
+
/^\s*{\s*["']command["']/i, // MCP config
|
|
152
|
+
/^\s*(npx|npm|yarn|pnpm|docker|git)\s+/i, // Install commands
|
|
153
|
+
/^\s*FROM\s+\w+:/i, // Dockerfile
|
|
154
|
+
/^\s*import\s+.*\s+from/i, // Import statements
|
|
155
|
+
/^\s*\[.*\]\s*=\s*["']/i, // TOML/config assignments
|
|
156
|
+
/^\s*<\w+>/i, // HTML/XML tags
|
|
157
|
+
/^\s*(export|const|let|var|function)\s+/i, // Code declarations
|
|
158
|
+
/^\s*\/\//i, // Code comments
|
|
159
|
+
/^\s*#\s*\w+/i, // Shell comments or headers
|
|
160
|
+
];
|
|
161
|
+
return excludePatterns.some((pattern) => pattern.test(text));
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Score functional example quality (returns true if meets minimum threshold).
|
|
165
|
+
*/
|
|
166
|
+
scoreFunctionalExample(text) {
|
|
167
|
+
let score = 0;
|
|
168
|
+
// Has clear action verb (create, configure, implement, show, etc.)
|
|
169
|
+
if (/\b(create|configure|implement|show|generate|build|write|add|get|set|use|run|start)\b/i.test(text)) {
|
|
170
|
+
score += 2;
|
|
171
|
+
}
|
|
172
|
+
// Includes tool trigger ("use context7", "with library", "use library")
|
|
173
|
+
if (/\b(?:use|with)\s+(?:context7|library|@?\w+\/\w+)\b/i.test(text)) {
|
|
174
|
+
score += 2;
|
|
175
|
+
}
|
|
176
|
+
// Is a complete sentence (starts with capital, ends with punctuation or is substantial)
|
|
177
|
+
if (/^[A-Z].{10,}/.test(text)) {
|
|
178
|
+
score += 1;
|
|
179
|
+
}
|
|
180
|
+
// Has technical context (mentions framework/library/technology)
|
|
181
|
+
if (/\b(Next\.js|React|Vue|Angular|PostgreSQL|MySQL|MongoDB|Redis|AWS|Cloudflare|API|HTTP|REST|GraphQL|TypeScript|JavaScript|Python|Docker|Kubernetes|Supabase|Firebase|Auth|JWT|OAuth)\b/i.test(text)) {
|
|
182
|
+
score += 1;
|
|
183
|
+
}
|
|
184
|
+
// Minimum score: 4 out of 6 points
|
|
185
|
+
return score >= 4;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Get line number for a position in content.
|
|
189
|
+
*/
|
|
190
|
+
getLineNumber(content, position) {
|
|
191
|
+
const beforeMatch = content.substring(0, position);
|
|
192
|
+
return beforeMatch.split("\n").length;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Remove duplicate examples based on similarity.
|
|
196
|
+
*/
|
|
197
|
+
deduplicateExamples(examples) {
|
|
198
|
+
const seen = new Set();
|
|
199
|
+
const unique = [];
|
|
200
|
+
for (const example of examples) {
|
|
201
|
+
// Create a normalized version for comparison
|
|
202
|
+
const normalized = example.code
|
|
203
|
+
.toLowerCase()
|
|
204
|
+
.replace(/[^\w\s]/g, "") // Remove punctuation
|
|
205
|
+
.replace(/\s+/g, " ") // Normalize multiple spaces to single space
|
|
206
|
+
.trim();
|
|
207
|
+
if (!seen.has(normalized)) {
|
|
208
|
+
seen.add(normalized);
|
|
209
|
+
unique.push(example);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return unique;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* @deprecated Use extractFunctionalExamples() instead.
|
|
216
|
+
* This method counts ALL code blocks including configs and install commands.
|
|
217
|
+
*/
|
|
218
|
+
extractCodeExamples(content) {
|
|
219
|
+
const examples = [];
|
|
220
|
+
const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
|
|
221
|
+
let match;
|
|
222
|
+
let lineNumber = 0;
|
|
223
|
+
const lines = content.split("\n");
|
|
224
|
+
while ((match = codeBlockRegex.exec(content)) !== null) {
|
|
225
|
+
const language = match[1] || "plaintext";
|
|
226
|
+
const code = match[2].trim();
|
|
227
|
+
// Find line number
|
|
228
|
+
const position = match.index;
|
|
229
|
+
const beforeMatch = content.substring(0, position);
|
|
230
|
+
lineNumber = beforeMatch.split("\n").length;
|
|
231
|
+
// Try to find description from preceding lines
|
|
232
|
+
let description = "";
|
|
233
|
+
const lineIndex = lines.findIndex((_, i) => lines.slice(0, i + 1).join("\n").length >= position);
|
|
234
|
+
if (lineIndex > 0) {
|
|
235
|
+
const prevLine = lines[lineIndex - 1].trim();
|
|
236
|
+
if (prevLine && !prevLine.startsWith("#")) {
|
|
237
|
+
description = prevLine;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
examples.push({
|
|
241
|
+
code,
|
|
242
|
+
language,
|
|
243
|
+
description,
|
|
244
|
+
lineNumber,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
return examples;
|
|
248
|
+
}
|
|
249
|
+
checkInstallInstructions(content) {
|
|
250
|
+
const installKeywords = [
|
|
251
|
+
"install",
|
|
252
|
+
"npm install",
|
|
253
|
+
"yarn add",
|
|
254
|
+
"pip install",
|
|
255
|
+
"setup",
|
|
256
|
+
"getting started",
|
|
257
|
+
];
|
|
258
|
+
const contentLower = content.toLowerCase();
|
|
259
|
+
return installKeywords.some((keyword) => contentLower.includes(keyword));
|
|
260
|
+
}
|
|
261
|
+
checkUsageGuide(content) {
|
|
262
|
+
const usageKeywords = [
|
|
263
|
+
"usage",
|
|
264
|
+
"how to use",
|
|
265
|
+
"example",
|
|
266
|
+
"quick start",
|
|
267
|
+
"tutorial",
|
|
268
|
+
];
|
|
269
|
+
const contentLower = content.toLowerCase();
|
|
270
|
+
return usageKeywords.some((keyword) => contentLower.includes(keyword));
|
|
271
|
+
}
|
|
272
|
+
checkAPIReference(content) {
|
|
273
|
+
const apiKeywords = [
|
|
274
|
+
"api",
|
|
275
|
+
"reference",
|
|
276
|
+
"methods",
|
|
277
|
+
"functions",
|
|
278
|
+
"parameters",
|
|
279
|
+
"returns",
|
|
280
|
+
"endpoints",
|
|
281
|
+
];
|
|
282
|
+
const contentLower = content.toLowerCase();
|
|
283
|
+
return apiKeywords.some((keyword) => contentLower.includes(keyword));
|
|
284
|
+
}
|
|
285
|
+
extractSection(content, section) {
|
|
286
|
+
const sectionRegex = new RegExp(`#+\\s*${section}[\\s\\S]*?(?=\\n#|$)`, "gi");
|
|
287
|
+
const match = content.match(sectionRegex);
|
|
288
|
+
return match ? match[0].trim() : "";
|
|
289
|
+
}
|
|
290
|
+
determineDocumentationStatus(metrics) {
|
|
291
|
+
let score = 0;
|
|
292
|
+
const maxScore = 5;
|
|
293
|
+
if (metrics.hasReadme)
|
|
294
|
+
score++;
|
|
295
|
+
if (metrics.hasInstallInstructions)
|
|
296
|
+
score++;
|
|
297
|
+
if (metrics.hasUsageGuide)
|
|
298
|
+
score++;
|
|
299
|
+
if (metrics.hasAPIReference)
|
|
300
|
+
score++;
|
|
301
|
+
if (metrics.exampleCount >= metrics.requiredExamples)
|
|
302
|
+
score++;
|
|
303
|
+
const percentage = (score / maxScore) * 100;
|
|
304
|
+
if (percentage >= 80)
|
|
305
|
+
return "PASS";
|
|
306
|
+
if (percentage >= 50)
|
|
307
|
+
return "NEED_MORE_INFO";
|
|
308
|
+
return "FAIL";
|
|
309
|
+
}
|
|
310
|
+
generateExplanation(metrics) {
|
|
311
|
+
const parts = [];
|
|
312
|
+
if (!metrics.hasReadme) {
|
|
313
|
+
parts.push("No README documentation found.");
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
parts.push(`README contains ${metrics.exampleCount} code examples.`);
|
|
317
|
+
const features = [];
|
|
318
|
+
if (metrics.hasInstallInstructions)
|
|
319
|
+
features.push("installation");
|
|
320
|
+
if (metrics.hasUsageGuide)
|
|
321
|
+
features.push("usage guide");
|
|
322
|
+
if (metrics.hasAPIReference)
|
|
323
|
+
features.push("API reference");
|
|
324
|
+
if (features.length > 0) {
|
|
325
|
+
parts.push(`Documentation includes: ${features.join(", ")}.`);
|
|
326
|
+
}
|
|
327
|
+
if (metrics.missingExamples.length > 0) {
|
|
328
|
+
parts.push(`Missing examples: ${metrics.missingExamples.join(", ")}.`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return parts.join(" ");
|
|
332
|
+
}
|
|
333
|
+
generateRecommendations(metrics) {
|
|
334
|
+
const recommendations = [];
|
|
335
|
+
if (!metrics.hasReadme) {
|
|
336
|
+
recommendations.push("Create a comprehensive README.md file");
|
|
337
|
+
}
|
|
338
|
+
if (!metrics.hasInstallInstructions) {
|
|
339
|
+
recommendations.push("Add clear installation instructions");
|
|
340
|
+
}
|
|
341
|
+
if (!metrics.hasUsageGuide) {
|
|
342
|
+
recommendations.push("Include a usage guide with examples");
|
|
343
|
+
}
|
|
344
|
+
if (!metrics.hasAPIReference) {
|
|
345
|
+
recommendations.push("Document all available tools and parameters");
|
|
346
|
+
}
|
|
347
|
+
if (metrics.exampleCount < metrics.requiredExamples) {
|
|
348
|
+
recommendations.push(`Add ${metrics.requiredExamples - metrics.exampleCount} more code examples`);
|
|
349
|
+
}
|
|
350
|
+
metrics.missingExamples.forEach((missing) => {
|
|
351
|
+
recommendations.push(`Add ${missing}`);
|
|
352
|
+
});
|
|
353
|
+
return recommendations;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Handling Assessor Module
|
|
3
|
+
* Tests error handling and input validation
|
|
4
|
+
*/
|
|
5
|
+
import { ErrorHandlingAssessment } from "../../../lib/assessmentTypes.js";
|
|
6
|
+
import { BaseAssessor } from "./BaseAssessor.js";
|
|
7
|
+
import { AssessmentContext } from "../AssessmentOrchestrator.js";
|
|
8
|
+
export declare class ErrorHandlingAssessor extends BaseAssessor {
|
|
9
|
+
assess(context: AssessmentContext): Promise<ErrorHandlingAssessment>;
|
|
10
|
+
private selectToolsForTesting;
|
|
11
|
+
private testToolErrorHandling;
|
|
12
|
+
private testMissingParameters;
|
|
13
|
+
private testWrongTypes;
|
|
14
|
+
private testInvalidValues;
|
|
15
|
+
private testExcessiveInput;
|
|
16
|
+
private getToolSchema;
|
|
17
|
+
private generateWrongTypeParams;
|
|
18
|
+
private generateInvalidValueParams;
|
|
19
|
+
private generateParamsWithValue;
|
|
20
|
+
private calculateMetrics;
|
|
21
|
+
private determineErrorHandlingStatus;
|
|
22
|
+
private generateExplanation;
|
|
23
|
+
private generateRecommendations;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=ErrorHandlingAssessor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ErrorHandlingAssessor.d.ts","sourceRoot":"","sources":["../../../../src/services/assessment/modules/ErrorHandlingAssessor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,uBAAuB,EAIxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAE9D,qBAAa,qBAAsB,SAAQ,YAAY;IAC/C,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,uBAAuB,CAAC;IAyC1E,OAAO,CAAC,qBAAqB;YA8Cf,qBAAqB;YAuBrB,qBAAqB;YAmGrB,cAAc;YAmFd,iBAAiB;YA8DjB,kBAAkB;IA6DhC,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,uBAAuB;IAgC/B,OAAO,CAAC,0BAA0B;IAgClC,OAAO,CAAC,uBAAuB;IA4B/B,OAAO,CAAC,gBAAgB;IAoGxB,OAAO,CAAC,4BAA4B;IAapC,OAAO,CAAC,mBAAmB;IAuE3B,OAAO,CAAC,uBAAuB;CA4ChC"}
|