@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
|
@@ -22,54 +22,62 @@ const thisHost = process.env.THIS_KILOTEST_HOST;
|
|
|
22
22
|
|
|
23
23
|
// Returns a response to an API request for reports.
|
|
24
24
|
exports.response = async () => {
|
|
25
|
+
// Initialize an array of facts about reports.
|
|
25
26
|
const reportsFacts = [];
|
|
26
27
|
// Get the available logs, with added job names.
|
|
27
28
|
const availableLogs = await getLogs();
|
|
28
|
-
// For each
|
|
29
|
+
// For each log:
|
|
29
30
|
for (const availableLog of availableLogs) {
|
|
30
31
|
const {jobName} = availableLog;
|
|
31
32
|
const [timeStamp, jobID] = jobName.split('-');
|
|
32
33
|
const reportSize = await getReportSize(timeStamp, jobID);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
'
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
34
|
+
// If its report exists:
|
|
35
|
+
if (reportSize) {
|
|
36
|
+
const {superseded, url, what} = availableLog;
|
|
37
|
+
// Add facts about the report to the array.
|
|
38
|
+
reportsFacts.push({
|
|
39
|
+
identifier: jobName,
|
|
40
|
+
'creation date and time': getDateTime(timeStamp),
|
|
41
|
+
'days since the creation date': getAgoDays(timeStamp),
|
|
42
|
+
'tested web page': {
|
|
43
|
+
description: what,
|
|
44
|
+
URL: url
|
|
45
|
+
},
|
|
46
|
+
'whether a later report about the same page exists': !! superseded,
|
|
47
|
+
'URLs for more details': {
|
|
48
|
+
'for you': `${thisHost}/api/reportSummary/${timeStamp}/${jobID}`,
|
|
49
|
+
'for humans': `${thisHost}/reportIssues.html/${timeStamp}/${jobID}`
|
|
50
|
+
},
|
|
51
|
+
'size of the report in bytes': reportSize,
|
|
52
|
+
'URL to get the entire report as machine-oriented JSON': `${thisHost}/fullReport.json/${timeStamp}/${jobID}`
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
// Otherwise, i.e. if the report does not exist:
|
|
56
|
+
else {
|
|
57
|
+
// Report this.
|
|
58
|
+
console.error(`Log ${jobName} exists but its report does not.`);
|
|
59
|
+
}
|
|
50
60
|
}
|
|
51
|
-
//
|
|
61
|
+
// Create a response body.
|
|
52
62
|
const content = {
|
|
53
63
|
summary: `This document fulfills a request made by a language model to a Kilotest tool. The model asked which reports about the front-end quality (i.e. accessibility, usability, and standards conformity) of web pages are available from Kilotest. Each report describes the results of a job that tested one web page. Kilotest, with the help of Testaro, Testilo, and an ensemble of ten rule engines, performs more than a thousand tests on a public web page, using a combination of rule- and machine-learning-based methods. Kilotest exposes API endpoints and web UI URLs to recommend web pages for testing and to obtain information from Kilotest reports. You and your users can learn more about Kilotest and the advantages of testing with an ensemble of rule engines at the deployed instance of Kilotest (${process.env.DEPLOYED_KILOTEST_HOST}).`,
|
|
54
64
|
'tool collection name': 'Kilotest',
|
|
55
|
-
'tool name': '
|
|
65
|
+
'tool name': 'listAllAvailableReports',
|
|
56
66
|
request: {
|
|
57
|
-
'
|
|
58
|
-
identifier: 'reportList',
|
|
59
|
-
description: 'Get a list of all available reports. Each report describes the results of a job that tested a web page for front-end quality (i.e. accessibility, usability, and standards conformity). For each report, the list identifies which page was tested and when and provides URLs to begin incremental retrieval of the test results.'
|
|
60
|
-
},
|
|
67
|
+
description: 'List all available reports. For each report, the list should identify which page was tested and when and provide URLs to begin incremental retrieval of the test results from the report.',
|
|
61
68
|
method: 'GET',
|
|
62
69
|
URLs: {
|
|
63
|
-
'
|
|
64
|
-
'
|
|
70
|
+
'for you': `${thisHost}/api/reportList`,
|
|
71
|
+
'for humans': `${thisHost}/targets.html`
|
|
65
72
|
},
|
|
66
73
|
'closest ancestor request': null
|
|
67
74
|
},
|
|
68
75
|
'response metadata': {
|
|
69
76
|
identifier: `${getNowStamp()}-${getRandomString(3)}`,
|
|
70
|
-
'date and time': new Date().toISOString()
|
|
77
|
+
'date and time': new Date().toISOString()
|
|
71
78
|
},
|
|
72
|
-
'
|
|
79
|
+
'response content': reportsFacts
|
|
73
80
|
};
|
|
81
|
+
// Return it.
|
|
74
82
|
return content;
|
|
75
83
|
};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/*
|
|
2
|
+
api.js
|
|
3
|
+
Responds to the reportSummary API request.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
getNowStamp,
|
|
10
|
+
getRandomString,
|
|
11
|
+
getReport,
|
|
12
|
+
isHidden,
|
|
13
|
+
objectSort,
|
|
14
|
+
ruleEngines
|
|
15
|
+
} = require('../util');
|
|
16
|
+
const issuesClassification = require('testilo/procs/score/tic').issues;
|
|
17
|
+
|
|
18
|
+
// CONSTANTS
|
|
19
|
+
|
|
20
|
+
const thisHost = process.env.THIS_KILOTEST_HOST;
|
|
21
|
+
|
|
22
|
+
// FUNCTIONS
|
|
23
|
+
|
|
24
|
+
// Returns facts about a report.
|
|
25
|
+
const getReportFacts = async (report) => {
|
|
26
|
+
// Initialize the facts.
|
|
27
|
+
const facts = {
|
|
28
|
+
ruleEngineIDs: new Set(),
|
|
29
|
+
reporterIDs: new Set(),
|
|
30
|
+
issueIDs: new Set(),
|
|
31
|
+
violators: new Set()
|
|
32
|
+
};
|
|
33
|
+
// For each act in the report:
|
|
34
|
+
report.acts.forEach(act => {
|
|
35
|
+
// If it is a test act:
|
|
36
|
+
if (act.type === 'test') {
|
|
37
|
+
const {result, which} = act;
|
|
38
|
+
// Ensure its rule engine is in the facts.
|
|
39
|
+
facts.ruleEngineIDs.add(which);
|
|
40
|
+
const instances = result?.standardResult?.instances ?? [];
|
|
41
|
+
// For each of its standard instances:
|
|
42
|
+
instances.forEach(instance => {
|
|
43
|
+
const {catalogIndex, issueID} = instance;
|
|
44
|
+
// If the instance identifies a non-ignorable issue:
|
|
45
|
+
if (issueID && issueID !== 'ignorable') {
|
|
46
|
+
const issueClassification = issuesClassification[issueID];
|
|
47
|
+
// If the issue is classified and has a valid weight:
|
|
48
|
+
if (issueClassification && [1, 2, 3, 4].includes(issueClassification.weight)) {
|
|
49
|
+
// Ensure the issue ID is in the facts.
|
|
50
|
+
facts.issueIDs.add(issueID);
|
|
51
|
+
// Ensure its rule engine is among the reporters in the facts.
|
|
52
|
+
facts.reporterIDs.add(which);
|
|
53
|
+
// If the instance has a catalog index:
|
|
54
|
+
if (catalogIndex) {
|
|
55
|
+
// Ensure the violator is in the facts.
|
|
56
|
+
facts.violators.add(catalogIndex);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
// Return the facts.
|
|
64
|
+
return facts;
|
|
65
|
+
};
|
|
66
|
+
// Returns facts about a rule engine.
|
|
67
|
+
const getRuleEngineFacts = ruleEngineID => {
|
|
68
|
+
const ruleEngineData = ruleEngines[ruleEngineID];
|
|
69
|
+
return {
|
|
70
|
+
identifier: ruleEngineID,
|
|
71
|
+
name: ruleEngineData[0],
|
|
72
|
+
sponsor: ruleEngineData[1]
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
// Returns facts about rule engines that were prevented from testing the page.
|
|
76
|
+
const getPreventionFacts = report => {
|
|
77
|
+
return objectSort(Object.entries(report.jobData.preventions).map(([ruleEngineID, reason]) => ({
|
|
78
|
+
'name': getRuleEngineFacts(ruleEngineID).name,
|
|
79
|
+
'reason for failure': reason
|
|
80
|
+
})), 'name', 'alpha');
|
|
81
|
+
};
|
|
82
|
+
// Returns rule engine IDs sorted by name.
|
|
83
|
+
const getSortedRuleEngineIDs = ruleEngineIDSet => {
|
|
84
|
+
return Array.from(ruleEngineIDSet).sort((a, b) => {
|
|
85
|
+
const aData = ruleEngines[a][0];
|
|
86
|
+
const bData = ruleEngines[b][0];
|
|
87
|
+
return aData.localeCompare(bData, 'en', {sensitivity: 'base'});
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
// Returns issue IDs sorted by priority and summary.
|
|
91
|
+
const getSortedIssueIDs = issueIDSet => {
|
|
92
|
+
return Array.from(issueIDSet).sort((a, b) => {
|
|
93
|
+
const aData = issuesClassification[a];
|
|
94
|
+
const bData = issuesClassification[b];
|
|
95
|
+
if (aData.weight !== bData.weight) {
|
|
96
|
+
return bData.weight - aData.weight;
|
|
97
|
+
}
|
|
98
|
+
return aData.summary.localeCompare(bData.summary, 'en', {sensitivity: 'base'});
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
// Get facts about an issue.
|
|
102
|
+
const getIssueFacts = (issueID, timeStamp, jobID) => {
|
|
103
|
+
const issueData = issuesClassification[issueID];
|
|
104
|
+
const {summary, weight, why} = issueData;
|
|
105
|
+
return {
|
|
106
|
+
identifier: issueID,
|
|
107
|
+
summary,
|
|
108
|
+
'impact on a user': why,
|
|
109
|
+
'priority': ['lowest', 'low', 'high', 'highest'][weight - 1],
|
|
110
|
+
'URLs for more details': {
|
|
111
|
+
'for you': `${thisHost}/api/reportIssue/${issueID}/${timeStamp}/${jobID}`,
|
|
112
|
+
'for humans': `${thisHost}/reportIssue.html/${issueID}/${timeStamp}/${jobID}`
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
// Returns a response to an API request for a summary of one report.
|
|
117
|
+
exports.response = async (args) => {
|
|
118
|
+
const [timeStamp, jobID] = args;
|
|
119
|
+
const reportIsHidden = await isHidden(timeStamp, jobID);
|
|
120
|
+
// If the report exists and is hidden:
|
|
121
|
+
if (reportIsHidden) {
|
|
122
|
+
// Return this.
|
|
123
|
+
return {
|
|
124
|
+
status: 'error',
|
|
125
|
+
message: 'Report not available',
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
// Otherwise, i.e. if the report is not hidden, get it.
|
|
129
|
+
const report = await getReport(timeStamp, jobID);
|
|
130
|
+
const {error} = report;
|
|
131
|
+
// If this failed:
|
|
132
|
+
if (error) {
|
|
133
|
+
// Return why.
|
|
134
|
+
return {
|
|
135
|
+
status: 'error',
|
|
136
|
+
message: error
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
// Otherwise, i.e. if it succeeded, get facts about it.
|
|
140
|
+
const reportFacts = await getReportFacts(report);
|
|
141
|
+
// Create a response body.
|
|
142
|
+
const content = {
|
|
143
|
+
summary: 'This document fulfills a request made by a language model to a Kilotest tool. The model asked for a summary of one Kilotest report. The model had previously used the listAllAvailableReports tool and had acquired from that tool basic facts about Kilotest, the ensemble testing that Kilotest performs, and the reports available from Kilotest. This summary does not repeat those facts.',
|
|
144
|
+
'tool collection name': 'Kilotest',
|
|
145
|
+
'tool name': 'summarizeOneReport',
|
|
146
|
+
request: {
|
|
147
|
+
description: 'Summarize one report. The summary should briefly describe the testing job and the results, including the rule engines that tested the web page and the issues that were revealed by the reported rule violations, and provide URLs for getting details about issues. The timeStamp and jobID parameters identify the report and were obtained from the response to a listAllAvailableReports operation.',
|
|
148
|
+
method: 'GET',
|
|
149
|
+
URLs: {
|
|
150
|
+
'for you': `${thisHost}/api/reportSummary/${timeStamp}/${jobID}`,
|
|
151
|
+
'for humans': `${thisHost}/reportIssues.html/${timeStamp}/${jobID}`
|
|
152
|
+
},
|
|
153
|
+
'closest ancestor request': {
|
|
154
|
+
'tool name': 'listAllAvailableReports',
|
|
155
|
+
description: 'List all available reports.',
|
|
156
|
+
URLs: {
|
|
157
|
+
'for you': `${thisHost}/api/reportList`,
|
|
158
|
+
'for humans': `${thisHost}/targets.html`
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
'response metadata': {
|
|
163
|
+
identifier: `${getNowStamp()}-${getRandomString(3)}`,
|
|
164
|
+
'date and time': new Date().toISOString()
|
|
165
|
+
},
|
|
166
|
+
'response content': {
|
|
167
|
+
'rule engines that tried to test the page': getSortedRuleEngineIDs(reportFacts.ruleEngineIDs)
|
|
168
|
+
.map(id => getRuleEngineFacts(id)),
|
|
169
|
+
'rule engines that could not test the page': getPreventionFacts(report),
|
|
170
|
+
'names of rule engines that reported rule violations': Array
|
|
171
|
+
.from(reportFacts.reporterIDs)
|
|
172
|
+
.map(id => getRuleEngineFacts(id).name)
|
|
173
|
+
.sort((a, b) => a.localeCompare(b, 'en', {sensitivity: 'base'})),
|
|
174
|
+
'number of elements reported as violators': reportFacts.violators.size,
|
|
175
|
+
'issues revealed': getSortedIssueIDs(reportFacts.issueIDs)
|
|
176
|
+
.map(id => getIssueFacts(id, timeStamp, jobID))
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
// Return it.
|
|
180
|
+
return content;
|
|
181
|
+
};
|
package/diagnoses/index.js
CHANGED
|
@@ -23,10 +23,27 @@ const path = require('path');
|
|
|
23
23
|
|
|
24
24
|
// Adds parameters to a query for the answer page.
|
|
25
25
|
const populateQuery = async (issueID, timeStamp, jobID, catalogIndex, pathID, query) => {
|
|
26
|
+
// Get descriptions of the page facts.
|
|
26
27
|
const pageDataStrings = await getPageDataStrings(timeStamp, jobID);
|
|
27
|
-
|
|
28
|
+
// If this failed:
|
|
29
|
+
if (pageDataStrings.error) {
|
|
30
|
+
// Populate the query with the reason.
|
|
31
|
+
query.error = pageDataStrings.error;
|
|
32
|
+
// Stop populating the query.
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const {testInfo, url, urlLink, what} = pageDataStrings;
|
|
36
|
+
// Otherwise, i.e. if it succeeded, get the report.
|
|
28
37
|
const report = await getReport(timeStamp, jobID);
|
|
29
38
|
const {acts, catalog} = report;
|
|
39
|
+
// If this failed:
|
|
40
|
+
if (report.error) {
|
|
41
|
+
// Populate the query with the reason.
|
|
42
|
+
query.error = report.error;
|
|
43
|
+
// Stop populating the query.
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// Otherwise, i.e. if it succeeded, get the catalog item of the specified violator.
|
|
30
47
|
const catalogItem = catalog[catalogIndex] ?? {};
|
|
31
48
|
const {boxID, startTag, tagName, text} = catalogItem;
|
|
32
49
|
query.catalogIndex = catalogIndex;
|
|
@@ -45,7 +62,15 @@ const populateQuery = async (issueID, timeStamp, jobID, catalogIndex, pathID, qu
|
|
|
45
62
|
query.target = what;
|
|
46
63
|
query.urlLink = urlLink;
|
|
47
64
|
query.testInfo = testInfo;
|
|
48
|
-
query.issue = issues[issueID]
|
|
65
|
+
query.issue = issues[issueID]?.summary;
|
|
66
|
+
// If adding the issue summary failed:
|
|
67
|
+
if (! query.issue) {
|
|
68
|
+
// Populate the query with the reason.
|
|
69
|
+
query.error = 'Issue not found';
|
|
70
|
+
// Stop populating the query.
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// Otherwise, i.e. if it succeeded, get the issue details.
|
|
49
74
|
const issue = issues[issueID];
|
|
50
75
|
const {wcag, weight, why} = issue;
|
|
51
76
|
query.why = why;
|
|
@@ -118,7 +143,15 @@ exports.answer = async (pageArgs, search) => {
|
|
|
118
143
|
const query = {};
|
|
119
144
|
// Create a query to replace the placeholders.
|
|
120
145
|
await populateQuery(issueID, timeStamp, jobID, catalogIndex, pathID, query);
|
|
121
|
-
// If
|
|
146
|
+
// If this failed:
|
|
147
|
+
if (query.error) {
|
|
148
|
+
// Return why.
|
|
149
|
+
return {
|
|
150
|
+
status: 'error',
|
|
151
|
+
message: query.error
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
// Otherwise, if it succeeded and the report facts were obtained:
|
|
122
155
|
if (query.testInfo) {
|
|
123
156
|
// Get the template.
|
|
124
157
|
let answerPage = await fs.readFile(path.join(__dirname, 'index.html'), 'utf8');
|
|
@@ -132,9 +165,10 @@ exports.answer = async (pageArgs, search) => {
|
|
|
132
165
|
answerPage
|
|
133
166
|
};
|
|
134
167
|
}
|
|
135
|
-
// Otherwise, report
|
|
168
|
+
// Otherwise, i.e. if the report facts were not obtained:
|
|
169
|
+
// Report this.
|
|
136
170
|
return {
|
|
137
171
|
status: 'error',
|
|
138
|
-
message: '
|
|
172
|
+
message: 'Report facts not obtained'
|
|
139
173
|
};
|
|
140
174
|
};
|
|
@@ -42,7 +42,7 @@ As of the date of this decision, a partial initial API version already exists: a
|
|
|
42
42
|
|
|
43
43
|
## Considered options
|
|
44
44
|
|
|
45
|
-
1. **Iterate and instrument.** (1)
|
|
45
|
+
1. **Iterate and instrument.** (1) Finish redesigning the tools at levels 1, 2, and 3, to represent the hypothesized typical needs of LLM consumers; then (2) add a tool that implements level 4; then (3) add a tool that asks LLMs to comment on how the API design could better satisfy their needs; then (4) add metrics to make API usage and submitted LLM comments available to Kilotest managers.
|
|
46
46
|
2. **Research first.** (1) Pause further API development while doing fundamental research to predict LLM needs; then (2) resume API and metrics development, including API redesign if the research results warrant.
|
|
47
47
|
|
|
48
48
|
## Decision
|
|
@@ -98,7 +98,7 @@ The sub-decision was **identifiers plus labels** (option 2). This decision was b
|
|
|
98
98
|
- **Risk:** The cost estimate assumes a well-behaved client that preserves conversation history; a poorly implemented client could still incur higher-than-necessary costs.
|
|
99
99
|
- **Mitigation:** MCP clients are expected to maintain message history by protocol; the ancestor labels provide a minimal human-friendly substitute.
|
|
100
100
|
- **Risk:** Scenario 1 users may still want an aggregated "everything about page X" response.
|
|
101
|
-
- **Mitigation:** A future tool can be added to aggregate the hierarchy on the server side and return a structured summary, but that decision is deferred until usage data justify it.
|
|
101
|
+
- **Mitigation:** A future tool can be added to aggregate the hierarchy on the server side and return a structured summary, but that decision is deferred until usage and suggestions data justify it.
|
|
102
102
|
|
|
103
103
|
## References
|
|
104
104
|
|
package/index.js
CHANGED
|
@@ -274,26 +274,30 @@ const requestHandler = async (request, response) => {
|
|
|
274
274
|
if (isTimeStamp(timeStamp) && isJobID(jobID)) {
|
|
275
275
|
const reportHidden = await isHidden(timeStamp, jobID);
|
|
276
276
|
// If the report exists and is hidden:
|
|
277
|
-
if (reportHidden
|
|
278
|
-
console.
|
|
277
|
+
if (reportHidden) {
|
|
278
|
+
console.error(`Hidden report ${timeStamp}-${jobID} requested`);
|
|
279
279
|
// Report this as suspected abuse.
|
|
280
280
|
await serveError(
|
|
281
|
-
getAbuseError(request, `
|
|
281
|
+
getAbuseError(request, `Hidden report ${timeStamp}-${jobID} requested`),
|
|
282
282
|
response,
|
|
283
283
|
true
|
|
284
284
|
);
|
|
285
285
|
}
|
|
286
|
-
// Otherwise, if the report
|
|
287
|
-
else if (typeof reportHidden === 'string') {
|
|
288
|
-
// Report this as suspected abuse.
|
|
289
|
-
await serveError(getAbuseError(request, reportHidden), response, true);
|
|
290
|
-
}
|
|
291
|
-
// Otherwise, i.e. if the report log is valid and not hidden:
|
|
286
|
+
// Otherwise, i.e. if the report is not hidden:
|
|
292
287
|
else {
|
|
293
|
-
// Get
|
|
288
|
+
// Get it.
|
|
294
289
|
const report = await getReport(timeStamp, jobID);
|
|
295
|
-
// If
|
|
296
|
-
if (
|
|
290
|
+
// If this failed:
|
|
291
|
+
if (report.error) {
|
|
292
|
+
// Report this as suspected abuse.
|
|
293
|
+
await serveError(
|
|
294
|
+
getAbuseError(request, `Nonexistent report ${timeStamp}-${jobID} requested`),
|
|
295
|
+
response,
|
|
296
|
+
true
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
// Otherwise, i.e. if it succeeded:
|
|
300
|
+
else {
|
|
297
301
|
// Serve response headers for a JSON download.
|
|
298
302
|
setHeaders('application/json', null, 'low');
|
|
299
303
|
response.setHeader(
|
|
@@ -302,11 +306,6 @@ const requestHandler = async (request, response) => {
|
|
|
302
306
|
// Download the report.
|
|
303
307
|
response.end(getJSON(report));
|
|
304
308
|
}
|
|
305
|
-
// Otherwise, i.e. if an error occurred:
|
|
306
|
-
else {
|
|
307
|
-
// Report it.
|
|
308
|
-
await serveError({message: report}, response, true);
|
|
309
|
-
}
|
|
310
309
|
}
|
|
311
310
|
}
|
|
312
311
|
// Otherwise, i.e. if the request is syntactically invalid:
|
|
@@ -346,15 +345,15 @@ const requestHandler = async (request, response) => {
|
|
|
346
345
|
// If the service is provision of a list of all available reports:
|
|
347
346
|
if (service === 'reportList') {
|
|
348
347
|
// Get the response (potentially error) data.
|
|
349
|
-
const responseData = await require(path.join(__dirname, '
|
|
348
|
+
const responseData = await require(path.join(__dirname, 'api', 'reportList')).response(specs);
|
|
350
349
|
// Send them.
|
|
351
350
|
setHeaders('application/json', null, 'ultra');
|
|
352
351
|
response.end(JSON.stringify(responseData));
|
|
353
352
|
}
|
|
354
|
-
// Otherwise, if the service is provision of
|
|
355
|
-
else if (service === '
|
|
353
|
+
// Otherwise, if the service is provision of a report summary:
|
|
354
|
+
else if (service === 'reportSummary') {
|
|
356
355
|
// Get the response (potentially error) data.
|
|
357
|
-
const responseData = await require(path.join(__dirname, '
|
|
356
|
+
const responseData = await require(path.join(__dirname, 'api', 'reportSummary'))
|
|
358
357
|
.response(specs);
|
|
359
358
|
// Send them.
|
|
360
359
|
setHeaders('application/json', null, 'high');
|
|
@@ -702,12 +701,12 @@ const requestHandler = async (request, response) => {
|
|
|
702
701
|
}
|
|
703
702
|
// Otherwise, if the first segment is the report finding service:
|
|
704
703
|
else if (segments[0] === 'target') {
|
|
705
|
-
const {
|
|
706
|
-
// If the payload contains a description
|
|
707
|
-
if (
|
|
704
|
+
const {description = '', hostname = ''} = postData;
|
|
705
|
+
// If the payload contains a description or hostname:
|
|
706
|
+
if (description || hostname) {
|
|
708
707
|
// Process the request and get the response data.
|
|
709
708
|
const responseData = await require(path.join(__dirname, 'target', 'api'))
|
|
710
|
-
.response([
|
|
709
|
+
.response([description, hostname]);
|
|
711
710
|
// Send them.
|
|
712
711
|
setHeaders('application/json', null, 'ultra');
|
|
713
712
|
response.end(JSON.stringify(responseData));
|
|
@@ -732,7 +731,8 @@ const requestHandler = async (request, response) => {
|
|
|
732
731
|
if (await isReportAvailable(what, url)) {
|
|
733
732
|
// Report this.
|
|
734
733
|
responseData = {
|
|
735
|
-
|
|
734
|
+
status: 'warning',
|
|
735
|
+
message: 'A report on the page is already available'
|
|
736
736
|
}
|
|
737
737
|
}
|
|
738
738
|
// Otherwise, i.e. if no report on the page is available:
|
package/issues/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
index.js
|
|
3
|
-
Answers the
|
|
3
|
+
Answers the issues question.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
@@ -24,6 +24,8 @@ const path = require('path');
|
|
|
24
24
|
|
|
25
25
|
// Gets summary data on the issues reported in a set of reports.
|
|
26
26
|
const getIssuesSummary = async logs => {
|
|
27
|
+
// Initialize the summary.
|
|
28
|
+
const summary = {};
|
|
27
29
|
// Initialize data for a summary.
|
|
28
30
|
const issuesData = {};
|
|
29
31
|
// For each log of a report to be inspected:
|
|
@@ -37,8 +39,14 @@ const getIssuesSummary = async logs => {
|
|
|
37
39
|
}
|
|
38
40
|
// Get the corresponding report.
|
|
39
41
|
const report = await getReport(timeStamp, jobID);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
const {acts = [], error} = report;
|
|
43
|
+
// If this failed:
|
|
44
|
+
if (error) {
|
|
45
|
+
// Populate the summary with the reason.
|
|
46
|
+
summary.error = error;
|
|
47
|
+
}
|
|
48
|
+
// For each act in it (none if the report retrieval failed):
|
|
49
|
+
acts.forEach(act => {
|
|
42
50
|
// If it is a test act:
|
|
43
51
|
if (act.type === 'test') {
|
|
44
52
|
const {result, which} = act;
|
|
@@ -60,11 +68,9 @@ const getIssuesSummary = async logs => {
|
|
|
60
68
|
}
|
|
61
69
|
});
|
|
62
70
|
}
|
|
63
|
-
// Initialize the summary.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
issues: []
|
|
67
|
-
};
|
|
71
|
+
// Initialize the summary properties.
|
|
72
|
+
summary.totalCount = 0;
|
|
73
|
+
summary.issues = [];
|
|
68
74
|
// For each issue:
|
|
69
75
|
Object.entries(issuesData).forEach(([issueID, data]) => {
|
|
70
76
|
const {count, reporters} = data;
|
|
@@ -107,7 +113,14 @@ const populateQuery = async query => {
|
|
|
107
113
|
const targetLogs = (await getLogs()).filter(log => ! log.superseded);
|
|
108
114
|
// Get summary data on the issues.
|
|
109
115
|
const issuesSummary = await getIssuesSummary(targetLogs);
|
|
110
|
-
//
|
|
116
|
+
// If this failed:
|
|
117
|
+
if (issuesSummary.error) {
|
|
118
|
+
// Populate the query with the reason.
|
|
119
|
+
query.error = issuesSummary.error;
|
|
120
|
+
// Stop populating the query.
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
// Otherwise, i.e. if it succeeded, initialize the lines.
|
|
111
124
|
const lines = [];
|
|
112
125
|
const margin = ' '.repeat(6);
|
|
113
126
|
// For each weight:
|
|
@@ -159,7 +172,15 @@ exports.answer = async () => {
|
|
|
159
172
|
const query = {};
|
|
160
173
|
// Create a query to replace placeholders.
|
|
161
174
|
await populateQuery(query);
|
|
162
|
-
//
|
|
175
|
+
// If the query reports an error:
|
|
176
|
+
if (query.error) {
|
|
177
|
+
// Return why.
|
|
178
|
+
return {
|
|
179
|
+
status: 'error',
|
|
180
|
+
message: query.error
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
// Otherwise, i.e. if the query does not report an error, get the template.
|
|
163
184
|
let answerPage = await fs.readFile(path.join(__dirname, 'index.html'), 'utf8');
|
|
164
185
|
// Replace its placeholders.
|
|
165
186
|
Object.keys(query).forEach(param => {
|
package/llms-full.txt
CHANGED
|
@@ -63,7 +63,7 @@ Kilotest offers a comprehensive suite of capabilities to users via its web UI:
|
|
|
63
63
|
- Get statistics about issues reported in one report: `https://kilotest.com/reportIssues.html/{timeStamp}/{jobID}`
|
|
64
64
|
- Get details about one issue reported in one report: `https://kilotest.com/reportIssue.html/{issueID}/{timeStamp}/{jobID}`
|
|
65
65
|
- Get diagnoses by rule engines of rule violations for one HTML element exhibiting one issue in one report: `https://kilotest.com/diagnoses.html/{issueID}/{timeStamp}/{jobID}/{catalogIndex}`
|
|
66
|
-
- [Recommend the testing of a not-yet-tested page](
|
|
66
|
+
- [Recommend the testing of a not-yet-tested page](https://kilotest.com/testRecForm.html)
|
|
67
67
|
- Recommend the retesting of a previously tested page: `https://kilotest.com/retestRecForm.html/{timeStamp}/{jobID}`
|
|
68
68
|
- [Get statistics about frequently reported issues across all pages](https://kilotest.com/issues.html)
|
|
69
69
|
- [Get a list of the operations that Kilotest managers are authorized to request](https://kilotest.com/manage.html)
|
|
@@ -73,28 +73,28 @@ Kilotest offers a comprehensive suite of capabilities to users via its web UI:
|
|
|
73
73
|
|
|
74
74
|
Kilotest is implementing a richer suite of capabilities optimized for language models. In the specifications below, the term `report` refers to the records of one session in which a web page is tested.
|
|
75
75
|
|
|
76
|
-
The implementation is currently in an alpha phase and offers
|
|
76
|
+
The implementation is currently in an alpha phase and offers these tools:
|
|
77
77
|
|
|
78
78
|
- `summarizeQualityOfMatchingWebPages`
|
|
79
79
|
- method: `POST`
|
|
80
80
|
- purpose: summarize front-end quality test results from available reports about web pages whose descriptions or hostnames match those that you specify
|
|
81
81
|
- path: `/api/target`
|
|
82
82
|
- payload properties
|
|
83
|
-
- `
|
|
84
|
-
- `hostname`: all or part of the hostname of the page
|
|
85
|
-
- `
|
|
83
|
+
- `description of the web page`: all or part of a description of the page
|
|
84
|
+
- `hostname of the web page`: all or part of the hostname of the page
|
|
85
|
+
- `listAllAvailableReports`
|
|
86
86
|
- method: `GET`
|
|
87
|
-
- purpose:
|
|
87
|
+
- purpose: list all available reports
|
|
88
88
|
- path: `/api/reportList`
|
|
89
89
|
- caveat: Cached results are likely to be stale and should not be used
|
|
90
|
-
- `
|
|
90
|
+
- `summarizeOneReport`
|
|
91
91
|
- method: `GET`
|
|
92
|
-
- purpose:
|
|
93
|
-
- path: `/api/
|
|
92
|
+
- purpose: summarize one report
|
|
93
|
+
- path: `/api/reportSummary/{timeStamp}/{jobID}`
|
|
94
94
|
- parameters
|
|
95
95
|
- `timeStamp`: initial segment of report identifier
|
|
96
96
|
- `jobID`: final segment of report identifier
|
|
97
|
-
- source of parameters: response to a `
|
|
97
|
+
- source of parameters: response to a `listAllAvailableReports` request
|
|
98
98
|
- `describeOneIssueFromOneReport`
|
|
99
99
|
- method: `GET`
|
|
100
100
|
- purpose: provide details about one issue from one report
|
|
@@ -103,7 +103,7 @@ The implementation is currently in an alpha phase and offers 4 tools:
|
|
|
103
103
|
- `issueID`: identifier of the issue
|
|
104
104
|
- `timeStamp`: initial segment of report identifier
|
|
105
105
|
- `jobID`: final segment of report identifier
|
|
106
|
-
- source of parameters: response to a `
|
|
106
|
+
- source of parameters: response to a `summarizeOneReport` request
|
|
107
107
|
- `describeOneViolatorOfOneIssueFromOneReport`
|
|
108
108
|
- method: `GET`
|
|
109
109
|
- purpose: provide diagnoses about one issue exhibited by one HTML element in one report [NOT YET IMPLEMENTED]
|
|
@@ -120,9 +120,9 @@ The implementation is currently in an alpha phase and offers 4 tools:
|
|
|
120
120
|
- path: `/api/testRecForm`
|
|
121
121
|
- payload properties
|
|
122
122
|
|
|
123
|
-
- `
|
|
124
|
-
- `
|
|
125
|
-
- `
|
|
123
|
+
- `description of the web page`: description of the page to be tested
|
|
124
|
+
- `hostname of the web page`: URL of the page to be tested
|
|
125
|
+
- `reason for testing the web page`: reason for testing the page
|
|
126
126
|
|
|
127
127
|
- possible responses:
|
|
128
128
|
|
|
@@ -146,7 +146,7 @@ An [OpenAPI specification for Kilotest](https://kilotest.com/openapi.yaml) is av
|
|
|
146
146
|
|
|
147
147
|
When you use Kilotest tools, you are free to perform any appropriate interpretation and condensation of the results for presentation to users. However, users expect the sources of facts and interpretations to be truthfully represented. Do not meld facts from Kilotest tools with interpretations of your own in a manner that implies attribution of both to Kilotest.
|
|
148
148
|
|
|
149
|
-
An example of a **poor** attribution is “Here are the results from Kilotest: …. The page is at risk of …. Among all the issues, … are critical and require immediate attention.” This attribution is poor because it incorrectly implies
|
|
149
|
+
An example of a **poor** attribution is “Here are the results from Kilotest: …. The page is at risk of …. Among all the issues, … are critical and require immediate attention.” This attribution is poor because it incorrectly implies that Kilotest produced the interpretations and advice.
|
|
150
150
|
|
|
151
151
|
An example of a **good** attribution is “Kilotest reported these issues: …. On the basis of these results, I conclude that there is a serious risk of …, and, if you are responsible for maintaining the page, I recommend ….”
|
|
152
152
|
|
package/llms.txt
CHANGED
|
@@ -9,8 +9,8 @@ There is a [detailed explanation of Kilotest and its capabilities for LLMs](http
|
|
|
9
9
|
## API
|
|
10
10
|
|
|
11
11
|
- [Get summaries of reports about a specified web page](/api/target)
|
|
12
|
-
- [
|
|
13
|
-
-
|
|
12
|
+
- [List all available reports](/api/reportList)
|
|
13
|
+
- Summarize one report: `/api/reportSummary/{timeStamp}/{jobID}`
|
|
14
14
|
- Get details about one issue from one report: `/api/reportIssue/{issueID}/{timeStamp}/{jobID}`
|
|
15
15
|
- Recommend testing of a web page: `/api/testRecForm` (requires `POST` request)
|
|
16
16
|
- [OpenAPI specification for Kilotest](https://kilotest.com/openapi.yaml)
|