@jrpool/kilotest 38.0.0 → 39.0.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/.markdownlint-cli2.jsonc +12 -0
- package/.vscode/settings.json +3 -0
- package/CLAUDE.md +5 -0
- package/README.md +6 -4
- package/ai0BalanceForm/index.html +1 -1
- package/alerts.js +3 -1
- package/api/getReport.js +66 -0
- package/api/listDiagnoses.js +157 -0
- package/api/listIssues.js +225 -0
- package/api/listReports.js +87 -0
- package/api/listViolators.js +164 -0
- package/api/requestFeature.js +56 -0
- package/api/requestRetest.js +102 -0
- package/api/requestTest.js +106 -0
- package/api/routes.js +97 -0
- package/api/schemas.js +297 -0
- package/api/testSuite.js +294 -0
- package/api/util.js +149 -0
- package/diagnoses/index.js +2 -2
- package/docs/{AI-TOOL.md → AI-TOOLS.md} +54 -26
- package/docs/SERVICE.md +4 -1
- package/docs/decisions/001-api-design.md +59 -49
- package/eslint.config.mjs +13 -3
- package/index.html +1 -1
- package/index.js +201 -188
- package/issues/index.js +13 -21
- package/jsconfig.json +8 -0
- package/llms-full.txt +2 -2
- package/llms.txt +16 -11
- package/mcp.js +129 -65
- package/openapi.yaml +1234 -572
- package/package.json +18 -11
- package/reannotate/index.js +26 -15
- package/reannotateForm/index.js +15 -13
- package/redocly.yaml +4 -0
- package/reportHideForm/index.html +1 -1
- package/reportHideForm/index.js +24 -38
- package/reportIssue/index.js +3 -3
- package/reportIssues/index.js +128 -1
- package/reportUnhideForm/index.js +36 -44
- package/reportsExpungeForm/index.js +22 -16
- package/reportsPruneForm/index.js +22 -12
- package/reportsRewindForm/index.js +17 -9
- package/retestRec/index.js +8 -4
- package/retestRecForm/index.js +18 -6
- package/scripts/generate-openapi.js +83 -0
- package/targets/index.js +18 -13
- package/testOrder/index.js +11 -8
- package/tutorial/index.html +12 -12
- package/util.js +580 -539
- package/reportIssue/api.js +0 -183
- package/reportIssues/util.js +0 -138
- package/reportList/api.js +0 -83
- package/reportSummary/api.js +0 -181
- package/researchAgent.js +0 -206
- package/target/api.js +0 -126
- package/testRecForm/api.js +0 -55
package/researchAgent.js
DELETED
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
© 2026 Jonathan Robert Pool.
|
|
3
|
-
|
|
4
|
-
Licensed under the MIT License. See LICENSE file at the project root or
|
|
5
|
-
https://opensource.org/license/mit/ for details.
|
|
6
|
-
|
|
7
|
-
SPDX-License-Identifier: MIT
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/*
|
|
11
|
-
researchAgent.js
|
|
12
|
-
Simulates a research agent.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
// IMPORTS
|
|
16
|
-
|
|
17
|
-
require('dotenv').config();
|
|
18
|
-
const httpClient = require('http');
|
|
19
|
-
const httpsClient = require('https');
|
|
20
|
-
|
|
21
|
-
// CONSTANTS
|
|
22
|
-
|
|
23
|
-
const kilotestHosts = [
|
|
24
|
-
process.env.LOCAL_KILOTEST_HOST || 'http://localhost:3000',
|
|
25
|
-
process.env.DEPLOYED_KILOTEST_HOST || 'https://kilotest.com'
|
|
26
|
-
];
|
|
27
|
-
// Kilotest host specified by the argument.
|
|
28
|
-
const kilotestHost = kilotestHosts[process.argv[2] === 'pub' ? 1 : 0];
|
|
29
|
-
const hostParts = kilotestHost.split(/:\/*/);
|
|
30
|
-
const scheme = hostParts[0] === 'https' ? 'https' : 'http';
|
|
31
|
-
const client = scheme === 'https' ? httpsClient : httpClient;
|
|
32
|
-
const host = hostParts[1] || 'localhost';
|
|
33
|
-
const port = hostParts[2] || (scheme === 'https' ? 443 : 80);
|
|
34
|
-
|
|
35
|
-
// FUNCTIONS
|
|
36
|
-
|
|
37
|
-
// Gets and outputs the content or error message from a response.
|
|
38
|
-
const getContent = async response => {
|
|
39
|
-
const content = await new Promise(resolve => {
|
|
40
|
-
// Initialize an array of data from the response.
|
|
41
|
-
const chunks = [];
|
|
42
|
-
response
|
|
43
|
-
// If the response throws an error:
|
|
44
|
-
.on('error', error => {
|
|
45
|
-
const {message} = error;
|
|
46
|
-
// Report and return the error message.
|
|
47
|
-
console.log(message);
|
|
48
|
-
resolve({error: message});
|
|
49
|
-
})
|
|
50
|
-
// Whenever the response delivers data:
|
|
51
|
-
.on('data', chunk => {
|
|
52
|
-
// Add them to the array.
|
|
53
|
-
chunks.push(chunk);
|
|
54
|
-
})
|
|
55
|
-
// When the response is completed:
|
|
56
|
-
.on('end', () => {
|
|
57
|
-
const contentString = chunks.join('');
|
|
58
|
-
try {
|
|
59
|
-
const content = JSON.parse(contentString);
|
|
60
|
-
// Return the response content as an object.
|
|
61
|
-
resolve(content);
|
|
62
|
-
}
|
|
63
|
-
// If it is not JSON:
|
|
64
|
-
catch {
|
|
65
|
-
// Return this.
|
|
66
|
-
resolve({message: `Response content not JSON (${contentString})`});
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
console.log(JSON.stringify(content, null, 2));
|
|
71
|
-
return content;
|
|
72
|
-
};
|
|
73
|
-
const getRequestOptions = (path, method = 'GET') => ({
|
|
74
|
-
method,
|
|
75
|
-
host,
|
|
76
|
-
port,
|
|
77
|
-
path,
|
|
78
|
-
headers: {
|
|
79
|
-
'content-type': 'application/json; charset=utf-8'
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
// Submits a request, returns the response content, and increments the results.
|
|
83
|
-
const submitRequest = async (path, method, body = null) => new Promise(resolve => {
|
|
84
|
-
console.log(`Making ${scheme} ${method} request on port ${port} to ${host}${path}`);
|
|
85
|
-
client.request(getRequestOptions(path, method), async response => {
|
|
86
|
-
const responseContent = await getContent(response);
|
|
87
|
-
resolve(responseContent);
|
|
88
|
-
})
|
|
89
|
-
.on('error', error => {
|
|
90
|
-
console.log(`ERROR submitting request (${JSON.stringify(error, null, 2)})`);
|
|
91
|
-
resolve({error});
|
|
92
|
-
})
|
|
93
|
-
.end(body ? JSON.stringify(body) : '');
|
|
94
|
-
});
|
|
95
|
-
// Submits requests to a random Kilotest host.
|
|
96
|
-
const requestService = async () => {
|
|
97
|
-
let method;
|
|
98
|
-
let path;
|
|
99
|
-
let content;
|
|
100
|
-
let reports;
|
|
101
|
-
let report;
|
|
102
|
-
let timeStamp;
|
|
103
|
-
let jobID;
|
|
104
|
-
let description;
|
|
105
|
-
let url;
|
|
106
|
-
console.log('======================\nRequest: Summarize nonexistent reports');
|
|
107
|
-
method = 'POST';
|
|
108
|
-
path = '/api/target';
|
|
109
|
-
content = await submitRequest(path, method, {
|
|
110
|
-
description: 'oesntuhaesouht',
|
|
111
|
-
hostname: 'osentuhaoesuht.aoesntuh'
|
|
112
|
-
});
|
|
113
|
-
if (content.error) {
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
console.log('======================\nRequest: List all available reports');
|
|
117
|
-
method = 'GET';
|
|
118
|
-
path = '/api/reportList';
|
|
119
|
-
content = await submitRequest(path, method);
|
|
120
|
-
reports = content?.['response content'] ?? [];
|
|
121
|
-
if (content.error || ! Array.isArray(reports) || ! reports.length) {
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
console.log('======================\nRequest: Summarize matching reports');
|
|
125
|
-
// Choose one available report at random.
|
|
126
|
-
report = reports[Math.floor(Math.random() * reports.length)];
|
|
127
|
-
({description, URL: url} = report?.['tested web page'] ?? ['', '']);
|
|
128
|
-
if (! (description && url)) {
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
method = 'POST';
|
|
132
|
-
path = '/api/target';
|
|
133
|
-
content = await submitRequest(path, method, {
|
|
134
|
-
description,
|
|
135
|
-
hostname: new URL(url).hostname ?? ''
|
|
136
|
-
});
|
|
137
|
-
if (content.error) {
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
console.log('======================\nRequest: Summarize one nonexistent report');
|
|
141
|
-
[timeStamp, jobID] = ['111111T1111', 'abc'];
|
|
142
|
-
method = 'GET';
|
|
143
|
-
path = `/api/reportSummary/${timeStamp}/${jobID}`;
|
|
144
|
-
content = await submitRequest(path, method);
|
|
145
|
-
if (! content.message) {
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
console.log('======================\nRequest: Summarize one report');
|
|
149
|
-
[timeStamp, jobID] = report.identifier?.split('-') ?? ['', ''];
|
|
150
|
-
if (! (timeStamp && jobID)) {
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
method = 'GET';
|
|
154
|
-
path = `/api/reportSummary/${timeStamp}/${jobID}`;
|
|
155
|
-
content = await submitRequest(path, method);
|
|
156
|
-
if (content.message || ! content.summary) {
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
console.log('======================\nRequest: Describe one issue from one report');
|
|
160
|
-
if (content['response content']['number of elements reported as violators'] === 0) {
|
|
161
|
-
console.log('reportIssue request cannot be submitted, because no issues were reported');
|
|
162
|
-
}
|
|
163
|
-
else {
|
|
164
|
-
// Get the issue IDs.
|
|
165
|
-
const issueIDs = Object
|
|
166
|
-
.values(content['response content']['issues revealed'])
|
|
167
|
-
.map(issue => issue.identifier);
|
|
168
|
-
// Choose one at random.
|
|
169
|
-
const issueID = issueIDs[Math.floor(Math.random() * issueIDs.length)];
|
|
170
|
-
method = 'GET';
|
|
171
|
-
path = `/api/reportIssue/${issueID}/${timeStamp}/${jobID}`;
|
|
172
|
-
content = await submitRequest(path, method);
|
|
173
|
-
if (content.message) {
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
console.log('======================\nRequest: Make a permitted test recommendation');
|
|
178
|
-
method = 'POST';
|
|
179
|
-
path = '/api/testRecForm';
|
|
180
|
-
content = await submitRequest(path, method, {
|
|
181
|
-
'description of the web page': 'aoseeou',
|
|
182
|
-
'URL of the web page': 'https://oaaestuh.osneth',
|
|
183
|
-
'reason for testing the web page': 'Just testing'
|
|
184
|
-
});
|
|
185
|
-
if (content.message) {
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
console.log('======================\nRequest: Make an illicit test recommendation');
|
|
189
|
-
content = await submitRequest(path, method, {
|
|
190
|
-
'description of the web page': description,
|
|
191
|
-
'URL of the web page': url,
|
|
192
|
-
'reason for testing the web page': 'Just testing'
|
|
193
|
-
});
|
|
194
|
-
if (! content.message) {
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
console.log('======================\nRequest: Results');
|
|
198
|
-
console.log('All tests succeeded');
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
// EXECUTION
|
|
202
|
-
|
|
203
|
-
// Execute the research agent.
|
|
204
|
-
(async () => {
|
|
205
|
-
await requestService();
|
|
206
|
-
})();
|
package/target/api.js
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
api.js
|
|
3
|
-
Responds to the target request.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// IMPORTS
|
|
7
|
-
|
|
8
|
-
const {
|
|
9
|
-
getLogs,
|
|
10
|
-
getNowStamp,
|
|
11
|
-
getRandomString,
|
|
12
|
-
getReportData,
|
|
13
|
-
minifyURL
|
|
14
|
-
} = require('../util');
|
|
15
|
-
|
|
16
|
-
// CONSTANTS
|
|
17
|
-
|
|
18
|
-
const thisHost = process.env.THIS_KILOTEST_HOST;
|
|
19
|
-
|
|
20
|
-
// FUNCTIONS
|
|
21
|
-
|
|
22
|
-
// Returns a response to a target request.
|
|
23
|
-
exports.response = async args => {
|
|
24
|
-
const [description = '', hostname = ''] = args;
|
|
25
|
-
const hostLC = hostname.toLowerCase();
|
|
26
|
-
const whatLC = description.toLowerCase();
|
|
27
|
-
// Initialize an array of summaries of matching reports.
|
|
28
|
-
const matchingReports = [];
|
|
29
|
-
// Get the non-hidden logs.
|
|
30
|
-
const targetLogs = await getLogs();
|
|
31
|
-
// For each log:
|
|
32
|
-
for (const targetLog of targetLogs) {
|
|
33
|
-
const {jobName} = targetLog;
|
|
34
|
-
const [timeStamp, jobID] = jobName.split('-');
|
|
35
|
-
const {superseded, url: reportURL, what: reportWhat} = targetLog;
|
|
36
|
-
const reportHost = new URL(minifyURL(reportURL)).hostname;
|
|
37
|
-
const reportWhatLC = reportWhat.toLowerCase();
|
|
38
|
-
// If its description or hostname fragment matches the requested page:
|
|
39
|
-
if (
|
|
40
|
-
hostLC && (reportHost.includes(hostLC) || hostLC.includes(reportHost))
|
|
41
|
-
|| whatLC && (reportWhatLC.includes(whatLC) || whatLC.includes(reportWhatLC))
|
|
42
|
-
) {
|
|
43
|
-
// Get data about its report.
|
|
44
|
-
const data = await getReportData(timeStamp, jobID);
|
|
45
|
-
const {
|
|
46
|
-
error,
|
|
47
|
-
creationDate,
|
|
48
|
-
daysAgo,
|
|
49
|
-
issueCount,
|
|
50
|
-
toolNames,
|
|
51
|
-
toolCount,
|
|
52
|
-
reporterNames,
|
|
53
|
-
reporterCount,
|
|
54
|
-
violatorCount,
|
|
55
|
-
preventedToolNames,
|
|
56
|
-
preventedToolCount
|
|
57
|
-
} = data;
|
|
58
|
-
// If this failed:
|
|
59
|
-
if (error) {
|
|
60
|
-
// Report why.
|
|
61
|
-
console.error(error);
|
|
62
|
-
}
|
|
63
|
-
// Otherwise, i.e. if it succeeded:
|
|
64
|
-
else {
|
|
65
|
-
// Add a report summary to the array.
|
|
66
|
-
matchingReports.push({
|
|
67
|
-
identifier: jobName,
|
|
68
|
-
'creation date': creationDate,
|
|
69
|
-
'days since the creation date': daysAgo,
|
|
70
|
-
'tested web page': {
|
|
71
|
-
description: reportWhat,
|
|
72
|
-
URL: reportURL
|
|
73
|
-
},
|
|
74
|
-
'whether a later report about the same page exists': !! superseded,
|
|
75
|
-
'rule engines that tried to test the page': {
|
|
76
|
-
number: toolCount,
|
|
77
|
-
names: toolNames
|
|
78
|
-
},
|
|
79
|
-
'rule engines that were unable to test the page': {
|
|
80
|
-
number: preventedToolCount,
|
|
81
|
-
names: preventedToolNames
|
|
82
|
-
},
|
|
83
|
-
'rule engines that reported issues': {
|
|
84
|
-
number: reporterCount,
|
|
85
|
-
names: reporterNames
|
|
86
|
-
},
|
|
87
|
-
'number of issues reported': issueCount,
|
|
88
|
-
'number of HTML elements reported as exhibiting issues': violatorCount,
|
|
89
|
-
'URLs for getting data about the reported issues': {
|
|
90
|
-
'for you': `${thisHost}/api/reportSummary/${timeStamp}/${jobID}`,
|
|
91
|
-
'for humans': `${thisHost}/reportIssues.html/${timeStamp}/${jobID}`
|
|
92
|
-
},
|
|
93
|
-
'URL for getting the full technical report as JSON': `${thisHost}/fullReport.json/${timeStamp}/${jobID}`
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
// Get a response.
|
|
99
|
-
const content = {
|
|
100
|
-
summary: `This document fulfills a request made by a language model to the Kilotest API. The model asked whether Kilotest had tested a specified web page for front-end quality (i.e. accessibility, usability, and standards conformity) and had made a report of the test results available. The response provides summary information about all and only the available reports of pages that match at least one of the provided fragments, where matching means either including or being included by the provided fragment, case-insensitively. 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 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}), whose home page contains an introduction and a link to a tutorial.`,
|
|
101
|
-
'tool collection name': 'Kilotest',
|
|
102
|
-
'tool name': 'summarizeQualityOfMatchingWebPages',
|
|
103
|
-
request: {
|
|
104
|
-
'type of request': {
|
|
105
|
-
identifier: 'target',
|
|
106
|
-
description: 'Summarize the quality of matching web pages.'
|
|
107
|
-
},
|
|
108
|
-
method: 'POST',
|
|
109
|
-
payload: {
|
|
110
|
-
description,
|
|
111
|
-
hostname
|
|
112
|
-
},
|
|
113
|
-
URLs: {
|
|
114
|
-
'for you': `${thisHost}/api/target`,
|
|
115
|
-
'for humans': 'none'
|
|
116
|
-
},
|
|
117
|
-
'closest ancestor request': null
|
|
118
|
-
},
|
|
119
|
-
'response metadata': {
|
|
120
|
-
identifier: `${getNowStamp()}-${getRandomString(3)}`,
|
|
121
|
-
'date and time': new Date().toISOString(),
|
|
122
|
-
},
|
|
123
|
-
'matching reports': matchingReports
|
|
124
|
-
};
|
|
125
|
-
return content;
|
|
126
|
-
};
|
package/testRecForm/api.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
api.js
|
|
3
|
-
Responds to the test-recommendation request.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// IMPORTS
|
|
7
|
-
|
|
8
|
-
const {sendAlert} = require('../alerts')
|
|
9
|
-
const {
|
|
10
|
-
getNowStamp,
|
|
11
|
-
getRandomString,
|
|
12
|
-
updateRecs
|
|
13
|
-
} = require('../util');
|
|
14
|
-
|
|
15
|
-
// FUNCTIONS
|
|
16
|
-
|
|
17
|
-
// Returns a response to a test-recommendation request.
|
|
18
|
-
exports.response = async (what, url, why) => {
|
|
19
|
-
const thisHost = process.env.THIS_KILOTEST_HOST;
|
|
20
|
-
// Record the recommendation.
|
|
21
|
-
await updateRecs(what, url, why);
|
|
22
|
-
// Alert a manager about it.
|
|
23
|
-
await sendAlert(
|
|
24
|
-
`Kilotest: new test recommendation in the API`,
|
|
25
|
-
`Target: ${what}\nURL: ${url}\nReason: ${why}`
|
|
26
|
-
);
|
|
27
|
-
// Get a response.
|
|
28
|
-
const content = {
|
|
29
|
-
summary: `This response acknowledges a request made by a language model to a Kilotest tool. The model recommended that Kilotest test, for the first time, the ${what} web page at ${url} for front-end quality (i.e. accessibility, usability, and standards conformity). A Kilotest manager usually approves a recommendation within a day. When the recommendation is approved, the testing will be performed and results will become available. You can check for the availability of the results at ${thisHost}/api/reportList. Kilotest performs its testing with the help of Testaro, Testilo, and an ensemble of ten rule engines, using a combination of rule- and machine-learning-based methods. Kilotest exposes several API endpoints and several web UI URLs 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.`,
|
|
30
|
-
'tool collection name': 'Kilotest',
|
|
31
|
-
'tool name': 'recommendQualityTestingOfOneWebPage',
|
|
32
|
-
request: {
|
|
33
|
-
'type of request': {
|
|
34
|
-
identifier: 'testRecForm',
|
|
35
|
-
description: 'Recommend quality testing of one web page.'
|
|
36
|
-
},
|
|
37
|
-
method: 'POST',
|
|
38
|
-
payload: {
|
|
39
|
-
'description of the web page': what,
|
|
40
|
-
'URL of the web page': url,
|
|
41
|
-
'reason for testing the web page': why
|
|
42
|
-
},
|
|
43
|
-
URLs: {
|
|
44
|
-
'for you': `${thisHost}/api/testRecForm`,
|
|
45
|
-
'for humans': `${thisHost}/testRecForm.html`
|
|
46
|
-
},
|
|
47
|
-
'closest ancestor request': null
|
|
48
|
-
},
|
|
49
|
-
'response metadata': {
|
|
50
|
-
identifier: `${getNowStamp()}-${getRandomString(3)}`,
|
|
51
|
-
'date and time': new Date().toISOString()
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
return content;
|
|
55
|
-
};
|