@jrpool/kilotest 37.1.0 → 38.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{reportList/api.js → api/reportList.js} +36 -28
- package/api/reportSummary.js +181 -0
- package/diagnoses/index.js +39 -5
- package/docs/decisions/001-api-design.md +2 -2
- package/index.js +26 -26
- package/issues/index.js +31 -10
- package/llms-full.txt +15 -15
- package/llms.txt +2 -2
- package/mcp.js +25 -19
- package/openapi.yaml +125 -70
- package/package.json +1 -1
- package/reannotate/index.js +10 -2
- package/reannotateForm/index.js +19 -4
- package/reportHideForm/index.js +20 -1
- package/reportIssue/api.js +28 -9
- package/reportIssue/index.js +39 -7
- package/reportIssues/index.js +26 -14
- package/reportIssues/util.js +27 -8
- package/reportUnhideForm/index.js +20 -2
- package/reportsExpungeForm/index.js +9 -1
- package/reportsPruneForm/index.js +11 -1
- package/reportsRewindForm/index.js +9 -1
- package/researchAgent.js +38 -29
- package/retestRec/index.js +11 -2
- package/retestRecForm/index.js +5 -0
- package/target/api.js +46 -35
- package/targets/index.js +20 -1
- package/testRecForm/api.js +2 -2
- package/util.js +63 -48
- package/reportIssues/api.js +0 -129
package/util.js
CHANGED
|
@@ -37,6 +37,7 @@ const tools = exports.tools = {
|
|
|
37
37
|
wave: ['WAVE', 'Utah State University'],
|
|
38
38
|
wax: ['WallyAX', 'Wally']
|
|
39
39
|
};
|
|
40
|
+
exports.ruleEngines = tools;
|
|
40
41
|
exports.researchAgents = {
|
|
41
42
|
'research-agent': 'Internal Research Agent'
|
|
42
43
|
}
|
|
@@ -83,7 +84,7 @@ const getRecordPath = exports.getRecordPath = (recordType, timeStamp, jobID) =>
|
|
|
83
84
|
return null;
|
|
84
85
|
}
|
|
85
86
|
};
|
|
86
|
-
// Returns a log or report
|
|
87
|
+
// Returns a log or report.
|
|
87
88
|
const getRecord = exports.getRecord = async (recordType, timeStamp, jobID) => {
|
|
88
89
|
const recordPath = getRecordPath(recordType, timeStamp, jobID);
|
|
89
90
|
let recordJSON, record;
|
|
@@ -92,18 +93,18 @@ const getRecord = exports.getRecord = async (recordType, timeStamp, jobID) => {
|
|
|
92
93
|
}
|
|
93
94
|
catch(error) {
|
|
94
95
|
console.log(error.message);
|
|
95
|
-
|
|
96
|
+
recordJSON = JSON.stringify({error: `Requested ${recordType} ${timeStamp}-${jobID} not found`});
|
|
96
97
|
}
|
|
97
98
|
try {
|
|
98
99
|
record = JSON.parse(recordJSON);
|
|
99
100
|
}
|
|
100
101
|
catch (error) {
|
|
101
102
|
console.log(error.message);
|
|
102
|
-
|
|
103
|
+
record = {error: `Requested ${recordType} ${timeStamp}-${jobID} not JSON`};
|
|
103
104
|
}
|
|
104
105
|
return record;
|
|
105
106
|
};
|
|
106
|
-
// Returns a report
|
|
107
|
+
// Returns a report.
|
|
107
108
|
const getReport = exports.getReport = async (timeStamp, jobID) => await getRecord(
|
|
108
109
|
'report', timeStamp, jobID
|
|
109
110
|
);
|
|
@@ -246,13 +247,13 @@ const getIssue = exports.getIssue = (ruleIDs, toolID, ruleID) => {
|
|
|
246
247
|
// Adds issue IDs to the standard instances of a report.
|
|
247
248
|
const annotateReport = exports.annotateReport = async (ruleIDs, timeStamp, jobID) => {
|
|
248
249
|
// Get a copy of the report.
|
|
249
|
-
const
|
|
250
|
+
const report = await getReport(timeStamp, jobID);
|
|
250
251
|
// If this failed:
|
|
251
|
-
if (
|
|
252
|
-
// Return
|
|
253
|
-
return
|
|
252
|
+
if (report.error) {
|
|
253
|
+
// Return why.
|
|
254
|
+
return report.error;
|
|
254
255
|
}
|
|
255
|
-
|
|
256
|
+
// Otherwise, i.e. if it succeeded:
|
|
256
257
|
const unclassifiableRules = new Set();
|
|
257
258
|
// For each of its acts:
|
|
258
259
|
for (const act of report.acts) {
|
|
@@ -293,36 +294,35 @@ const annotateReport = exports.annotateReport = async (ruleIDs, timeStamp, jobID
|
|
|
293
294
|
// Save the annotated report.
|
|
294
295
|
await fs.writeFile(getReportPath(timeStamp, jobID), getJSON(report));
|
|
295
296
|
// Get a copy of the log of the report.
|
|
296
|
-
const
|
|
297
|
+
const log = await getLog(timeStamp, jobID, false);
|
|
297
298
|
// If this failed:
|
|
298
|
-
if (
|
|
299
|
-
// Return
|
|
300
|
-
return
|
|
299
|
+
if (log.error) {
|
|
300
|
+
// Return why.
|
|
301
|
+
return log.error;
|
|
301
302
|
}
|
|
302
303
|
// Otherwise, i.e. if it succeeded:
|
|
303
304
|
else {
|
|
304
|
-
const log = logOrError;
|
|
305
305
|
// Mark the report as annotated in the log.
|
|
306
306
|
log.annotated = true;
|
|
307
307
|
// Save the revised log.
|
|
308
308
|
await fs.writeFile(getLogPath(timeStamp, jobID), getJSON(log));
|
|
309
|
-
// Return
|
|
309
|
+
// Return success.
|
|
310
|
+
return '';
|
|
310
311
|
}
|
|
311
312
|
};
|
|
312
|
-
// Returns a report log after conditionally annotating it
|
|
313
|
+
// Returns a report log after conditionally annotating it.
|
|
313
314
|
const getLog = exports.getLog = async (timeStamp, jobID, annotate = false) => {
|
|
314
315
|
const log = await getRecord('log', timeStamp, jobID);
|
|
315
|
-
if (
|
|
316
|
-
annotateReport(ruleIDs, timeStamp, jobID);
|
|
316
|
+
if (annotate && ! (log.error || log.annotated)) {
|
|
317
|
+
await annotateReport(ruleIDs, timeStamp, jobID);
|
|
317
318
|
}
|
|
318
319
|
return log;
|
|
319
320
|
};
|
|
320
|
-
// Returns whether a report is hidden
|
|
321
|
+
// Returns whether a report is hidden.
|
|
321
322
|
exports.isHidden = async (timeStamp, jobID) => {
|
|
323
|
+
// Get the log of the report.
|
|
322
324
|
const log = await getLog(timeStamp, jobID, false);
|
|
323
|
-
|
|
324
|
-
return log;
|
|
325
|
-
}
|
|
325
|
+
// Return whether the log exists and its report is hidden.
|
|
326
326
|
return !! log.hidden;
|
|
327
327
|
};
|
|
328
328
|
// Returns summary data on a report.
|
|
@@ -330,9 +330,9 @@ exports.getReportData = async (timeStamp, jobID) => {
|
|
|
330
330
|
// Validate the report and annotate it if necessary.
|
|
331
331
|
const log = await getLog(timeStamp, jobID, true);
|
|
332
332
|
// If this failed:
|
|
333
|
-
if (
|
|
334
|
-
// Return
|
|
335
|
-
return log;
|
|
333
|
+
if (log.error) {
|
|
334
|
+
// Return why.
|
|
335
|
+
return {error: log.error};
|
|
336
336
|
}
|
|
337
337
|
// Initialize the data.
|
|
338
338
|
const data = {
|
|
@@ -357,9 +357,9 @@ exports.getReportData = async (timeStamp, jobID) => {
|
|
|
357
357
|
// Get the report.
|
|
358
358
|
const report = await getReport(timeStamp, jobID);
|
|
359
359
|
// If this failed:
|
|
360
|
-
if (
|
|
361
|
-
// Return
|
|
362
|
-
return report;
|
|
360
|
+
if (report.error) {
|
|
361
|
+
// Return why.
|
|
362
|
+
return {error: report.error};
|
|
363
363
|
}
|
|
364
364
|
// For each act of the report:
|
|
365
365
|
report.acts.forEach(act => {
|
|
@@ -526,30 +526,31 @@ const getLogs = exports.getLogs = async () => {
|
|
|
526
526
|
try {
|
|
527
527
|
logFileNames = await fs.readdir(logsPath);
|
|
528
528
|
}
|
|
529
|
-
catch
|
|
530
|
-
|
|
529
|
+
catch {
|
|
530
|
+
console.error('Logs directory not readable');
|
|
531
|
+
logFileNames = [];
|
|
531
532
|
}
|
|
532
533
|
// For each log:
|
|
533
534
|
for (const fileName of logFileNames) {
|
|
534
535
|
const logName = fileName.slice(0, -5);
|
|
535
536
|
const [timeStamp, jobID] = logName.split('-');
|
|
536
537
|
// Get it.
|
|
537
|
-
const
|
|
538
|
+
const log = await getLog(timeStamp, jobID);
|
|
538
539
|
// If this failed:
|
|
539
|
-
if (
|
|
540
|
-
//
|
|
541
|
-
|
|
540
|
+
if (log.error) {
|
|
541
|
+
// Report why.
|
|
542
|
+
console.error(log.error);
|
|
542
543
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
544
|
+
// Otherwise, i.e. if it succeeded:
|
|
545
|
+
else {
|
|
546
|
+
// If the report is not hidden:
|
|
547
|
+
if (! log.hidden) {
|
|
548
|
+
// Add the job name to the log.
|
|
549
|
+
log.jobName = logName;
|
|
550
|
+
// Add the log to the logs.
|
|
551
|
+
logs.push(log);
|
|
552
|
+
}
|
|
548
553
|
}
|
|
549
|
-
// Add the job name to the log.
|
|
550
|
-
log.jobName = logName;
|
|
551
|
-
// Add the log to the logs.
|
|
552
|
-
logs.push(log);
|
|
553
554
|
}
|
|
554
555
|
// Sort the logs by target name and secondarily by test time.
|
|
555
556
|
logs.sort((a, b) => {
|
|
@@ -692,12 +693,12 @@ const getPageData = exports.getPageData = async (timeStamp, jobID) => {
|
|
|
692
693
|
// Get the log of the report.
|
|
693
694
|
const log = await getLog(timeStamp, jobID, false);
|
|
694
695
|
// If this failed:
|
|
695
|
-
if (
|
|
696
|
-
// Return
|
|
696
|
+
if (log.error) {
|
|
697
|
+
// Return why.
|
|
697
698
|
return log;
|
|
698
699
|
}
|
|
699
700
|
const {url, what} = log;
|
|
700
|
-
//
|
|
701
|
+
// Otherwise, i.e. if it succeeded, get the elapsed time in days since the test.
|
|
701
702
|
const daysAgo = getAgoDays(timeStamp);
|
|
702
703
|
// Return the data.
|
|
703
704
|
return {
|
|
@@ -713,7 +714,15 @@ exports.getPageDataStrings = async (timeStamp, jobID, pageData) => {
|
|
|
713
714
|
// Get them.
|
|
714
715
|
pageData = await getPageData(timeStamp, jobID);
|
|
715
716
|
}
|
|
716
|
-
const {
|
|
717
|
+
const {daysAgo, error, url, what} = pageData;
|
|
718
|
+
// If the page data are invalid:
|
|
719
|
+
if (error) {
|
|
720
|
+
// Return why.
|
|
721
|
+
return {
|
|
722
|
+
error
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
// Otherwise, i.e. if they are valid, get a description of the timestamp.
|
|
717
726
|
const when = getDateTimeString(timeStamp);
|
|
718
727
|
// Return the HTML strings.
|
|
719
728
|
return {
|
|
@@ -766,7 +775,13 @@ exports.isReportAvailable = async (what, url) => {
|
|
|
766
775
|
};
|
|
767
776
|
// Returns the size of a report.
|
|
768
777
|
exports.getReportSize = async (timeStamp, jobID) => {
|
|
769
|
-
const reportStat = await fs.stat(
|
|
778
|
+
const reportStat = await fs.stat(
|
|
779
|
+
path.join(reportsPath, `${timeStamp}-${jobID}.json`),
|
|
780
|
+
{throwIfNoEntry: false}
|
|
781
|
+
);
|
|
782
|
+
if (! reportStat) {
|
|
783
|
+
return 0;
|
|
784
|
+
}
|
|
770
785
|
const reportSize = reportStat.size;
|
|
771
786
|
return reportSize;
|
|
772
787
|
};
|
package/reportIssues/api.js
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
api.js
|
|
3
|
-
Responds to the report-issues request.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// IMPORTS
|
|
7
|
-
|
|
8
|
-
const {getData} = require('./util');
|
|
9
|
-
const {
|
|
10
|
-
getDateTime,
|
|
11
|
-
getNowStamp,
|
|
12
|
-
getRandomString,
|
|
13
|
-
getToolsFacts,
|
|
14
|
-
isHidden,
|
|
15
|
-
tools
|
|
16
|
-
} = require('../util');
|
|
17
|
-
|
|
18
|
-
// FUNCTIONS
|
|
19
|
-
|
|
20
|
-
// Gets facts about an issue.
|
|
21
|
-
const getIssueFacts = (thisHost, timeStamp, jobID, issue) => {
|
|
22
|
-
const {issueID, reporterCount, reporters, summary, violatorCount, wcag, why} = issue;
|
|
23
|
-
const wcagType = wcag.length === 3 ? 'guideline' : 'success criterion';
|
|
24
|
-
return {
|
|
25
|
-
identifier: issueID,
|
|
26
|
-
summary,
|
|
27
|
-
'related WCAG 2.2 standard': {
|
|
28
|
-
layer: wcagType,
|
|
29
|
-
'numeric identifier': wcag
|
|
30
|
-
},
|
|
31
|
-
'impact on a user': why,
|
|
32
|
-
'rule engines reporting the issue': {
|
|
33
|
-
'number': reporterCount,
|
|
34
|
-
'names': reporters.map(tool => tool.toolName)
|
|
35
|
-
},
|
|
36
|
-
'number of HTML elements reported as exhibiting the issue': violatorCount,
|
|
37
|
-
'URLs for details about the issue on the page': {
|
|
38
|
-
'for you': `${thisHost}/api/reportIssue/${issueID}/${timeStamp}/${jobID}`,
|
|
39
|
-
'for humans': `${thisHost}/reportIssue.html/${issueID}/${timeStamp}/${jobID}`
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
};
|
|
43
|
-
// Returns a response to a report-issues request.
|
|
44
|
-
exports.response = async args => {
|
|
45
|
-
const [timeStamp, jobID] = args;
|
|
46
|
-
const reportIsHidden = await isHidden(timeStamp, jobID);
|
|
47
|
-
// If the report is not available:
|
|
48
|
-
if (reportIsHidden) {
|
|
49
|
-
return {
|
|
50
|
-
status: 'error',
|
|
51
|
-
message: 'Report not available'
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
// Otherwise, i.e. if the report is available, get data on the target and issues.
|
|
55
|
-
const data = await getData(timeStamp, jobID);
|
|
56
|
-
const {pageData, issuesData} = data;
|
|
57
|
-
const {what, url, daysAgo} = pageData;
|
|
58
|
-
const {issueCount, issues, preventions, reporterCount, reporters, violatorCount} = issuesData;
|
|
59
|
-
const preventedTools = Object.entries(preventions).map(prevention => ({
|
|
60
|
-
name: tools[prevention[0]][0],
|
|
61
|
-
'reason for failure': prevention[1]
|
|
62
|
-
}));
|
|
63
|
-
const thisHost = process.env.THIS_KILOTEST_HOST;
|
|
64
|
-
// Get a response.
|
|
65
|
-
const content = {
|
|
66
|
-
summary: `This document fulfills a request made by a language model to a Kilotest tool. The model requested data from a Kilotest report about the front-end quality (i.e. accessibility, usability, and standards conformity) of a web page. Kilotest, with the help of Testaro, Testilo, and an ensemble of ten rule engines, performs tests on web pages, using a combination of rule- and machine-learning-based methods, and produces reports. Kilotest exposes several API endpoints to recommend web pages for testing and to obtain information from Kilotest reports. To learn more about Kilotest and the advantages of testing with an ensemble of rule engines, visit the deployed instance of Kilotest (${process.env.DEPLOYED_KILOTEST_HOST}), which contains an introduction on its home page and a tutorial.`,
|
|
67
|
-
'tool collection name': 'Kilotest',
|
|
68
|
-
'tool name': 'describeQualityOfOneWebPage',
|
|
69
|
-
request: {
|
|
70
|
-
'type of request': {
|
|
71
|
-
identifier: 'reportIssues',
|
|
72
|
-
description: 'Describe the quality of one web page.'
|
|
73
|
-
},
|
|
74
|
-
method: 'GET',
|
|
75
|
-
URLs: {
|
|
76
|
-
'URL of your request': `${thisHost}/api/reportIssues/${timeStamp}/${jobID}`,
|
|
77
|
-
'equivalent URL for humans': `${thisHost}/reportIssues.html/${timeStamp}/${jobID}`
|
|
78
|
-
},
|
|
79
|
-
'closest ancestor request': {
|
|
80
|
-
identifier: 'getListOfAllAvailableReports',
|
|
81
|
-
description: 'Get a list of all available reports.',
|
|
82
|
-
URLs: {
|
|
83
|
-
'for you': `${thisHost}/api/reportList`,
|
|
84
|
-
'for humans': `${thisHost}/targets.html`
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
'response metadata': {
|
|
89
|
-
identifier: `${getNowStamp()}-${getRandomString(3)}`,
|
|
90
|
-
'date and time': new Date().toISOString()
|
|
91
|
-
},
|
|
92
|
-
report: {
|
|
93
|
-
identifier: `${timeStamp}-${jobID}`,
|
|
94
|
-
'creation date': getDateTime(timeStamp),
|
|
95
|
-
'days since the creation date': daysAgo
|
|
96
|
-
},
|
|
97
|
-
'tested web page': {
|
|
98
|
-
description: what,
|
|
99
|
-
URL: url
|
|
100
|
-
},
|
|
101
|
-
'rule engines that tried to test the page': getToolsFacts(Object.keys(tools)),
|
|
102
|
-
'rule engines that were unable to test the page': preventedTools,
|
|
103
|
-
'rule engines that reported issues': {
|
|
104
|
-
number: reporterCount,
|
|
105
|
-
names: reporters.map(tool => tool.toolName)
|
|
106
|
-
},
|
|
107
|
-
'number of issues reported': {
|
|
108
|
-
total: issueCount,
|
|
109
|
-
'by priority': {
|
|
110
|
-
'highest priority': issues[4].length,
|
|
111
|
-
'high priority': issues[3].length,
|
|
112
|
-
'low priority': issues[2].length,
|
|
113
|
-
'lowest priority': issues[1].length
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
|
-
'number of HTML elements reported as exhibiting issues': violatorCount,
|
|
117
|
-
'issues reported': {
|
|
118
|
-
'highest priority': issues[4]
|
|
119
|
-
.map(issue => getIssueFacts(thisHost, timeStamp, jobID, issue)),
|
|
120
|
-
'high priority': issues[3]
|
|
121
|
-
.map(issue => getIssueFacts(thisHost, timeStamp, jobID, issue)),
|
|
122
|
-
'low priority': issues[2]
|
|
123
|
-
.map(issue => getIssueFacts(thisHost, timeStamp, jobID, issue)),
|
|
124
|
-
'lowest priority': issues[1]
|
|
125
|
-
.map(issue => getIssueFacts(thisHost, timeStamp, jobID, issue))
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
return content;
|
|
129
|
-
};
|