@jrpool/kilotest 38.1.0 → 39.0.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/.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 +202 -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 +581 -539
- package/api/reportList.js +0 -83
- package/api/reportSummary.js +0 -181
- package/reportIssue/api.js +0 -183
- package/reportIssues/util.js +0 -138
- package/researchAgent.js +0 -206
- package/target/api.js +0 -126
- package/testRecForm/api.js +0 -55
package/api/util.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/*
|
|
2
|
+
util.js
|
|
3
|
+
Utilities for API requests.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
const {sendAlert} = require('../alerts');
|
|
9
|
+
const {
|
|
10
|
+
getAgoDays,
|
|
11
|
+
getLatestReportExtracts,
|
|
12
|
+
getNowStamp,
|
|
13
|
+
getPlainText,
|
|
14
|
+
getRandomString,
|
|
15
|
+
getReportExtract,
|
|
16
|
+
getReportStats,
|
|
17
|
+
issuesClassification,
|
|
18
|
+
objectSort,
|
|
19
|
+
ruleEngines,
|
|
20
|
+
updateRecs
|
|
21
|
+
} = require('../util');
|
|
22
|
+
|
|
23
|
+
// CONSTANTS
|
|
24
|
+
|
|
25
|
+
const thisHost = exports.thisHost = process.env.THIS_KILOTEST_HOST;
|
|
26
|
+
|
|
27
|
+
// FUNCTIONS
|
|
28
|
+
|
|
29
|
+
// Returns uniform metadata for every response.
|
|
30
|
+
exports.getResponseMetadata = () => ({
|
|
31
|
+
identifier: `${getNowStamp()}-${getRandomString(3)}`,
|
|
32
|
+
'date and time': new Date().toISOString()
|
|
33
|
+
});
|
|
34
|
+
// Returns facts about the tool collection (Kilotest).
|
|
35
|
+
exports.getToolsFacts = () => ({
|
|
36
|
+
'name': 'Kilotest',
|
|
37
|
+
'description': {
|
|
38
|
+
'what Kilotest does': 'Kilotest tools generate and make available findings about the front-end quality (i.e. accessibility, usability, and standards conformity) of web pages. A Kilotest job generates findings by using Testaro to test a page against more than a thousand rules defined by an ensemble of ten rule engines. Testaro produces a report of the job. The report describes violations of the rules. Kilotest uses Testilo to enhance the report with a classification of the rule violations into about 300 issues. Kilotest makes facts about the issues and the violations retrievable at four levels of granularity.',
|
|
39
|
+
'how to retrieve findings': {
|
|
40
|
+
'level 1': 'Use the listReports tool to get a list of available reports.',
|
|
41
|
+
'level 2': 'Use the listIssues tool to get a list of issues in one report.',
|
|
42
|
+
'level 3': 'Use the listViolators tool to get a list of elements on one page that were reported in one report as exhibiting one issue.',
|
|
43
|
+
'level 4': 'Use the listDiagnoses tool to get a list of diagnoses of how one element on one page exhibited one issue in one report.',
|
|
44
|
+
},
|
|
45
|
+
'how to generate more findings': {
|
|
46
|
+
'new testing': 'If no report is available yet about a page, use the requestTest tool to request that it be tested.',
|
|
47
|
+
'retesting': 'If the listIssues tool shows that the latest report about a page is obsolete, because the page has been revised or for another reason, use the requestRetest tool to request that the page be retested.',
|
|
48
|
+
'latency': 'Requests for testing and retesting are usually approved and fulfilled within one day.',
|
|
49
|
+
'confirmation': 'Use the listReports tool to determine whether a requested new report exists. There is currently no process for notification of the outcome of requests.'
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
'URL': `${thisHost}/mcp`,
|
|
53
|
+
'web users can obtain similar functionalities at': thisHost
|
|
54
|
+
});
|
|
55
|
+
// Returns the facts about a rule engine.
|
|
56
|
+
const getRuleEngineFacts = exports.getRuleEngineFacts = ruleEngineID => {
|
|
57
|
+
const ruleEngineData = ruleEngines[ruleEngineID] || [null, null];
|
|
58
|
+
return {
|
|
59
|
+
identifier: ruleEngineID,
|
|
60
|
+
name: ruleEngineData[0] || null,
|
|
61
|
+
sponsor: ruleEngineData[1] || null
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
// Returns the facts about rule engines.
|
|
65
|
+
exports.getRuleEnginesFacts = ruleEngineIDSet => {
|
|
66
|
+
const ruleEnginesFacts = Array.from(ruleEngineIDSet).map(id => getRuleEngineFacts(id));
|
|
67
|
+
objectSort(ruleEnginesFacts, 'name', 'alpha');
|
|
68
|
+
return ruleEnginesFacts;
|
|
69
|
+
};
|
|
70
|
+
// Returns the basics about a report, without reading the report.
|
|
71
|
+
exports.getReportBasics = async (timeStamp, jobID) => {
|
|
72
|
+
// Get the creation time of the report.
|
|
73
|
+
const reportStats = await getReportStats(timeStamp, jobID);
|
|
74
|
+
// If the report does not exist:
|
|
75
|
+
if (!reportStats) {
|
|
76
|
+
// Log and return this.
|
|
77
|
+
console.error(`Report ${timeStamp}-${jobID} does not exist.`);
|
|
78
|
+
return {
|
|
79
|
+
error: `Report ${timeStamp}-${jobID} could not be retrieved.`
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
// Get an extract of the report.
|
|
83
|
+
const reportExtract = await getReportExtract(timeStamp, jobID);
|
|
84
|
+
// If no such report exists:
|
|
85
|
+
if (reportExtract.error) {
|
|
86
|
+
// Return this.
|
|
87
|
+
return reportExtract;
|
|
88
|
+
}
|
|
89
|
+
const {url, what} = reportExtract;
|
|
90
|
+
const latestReportExtracts = await getLatestReportExtracts();
|
|
91
|
+
// Otherwise, i.e. if the report exists, get whether this report has been superseded.
|
|
92
|
+
const isSuperseded = latestReportExtracts
|
|
93
|
+
.every(extract => extract.timeStamp !== timeStamp || extract.jobID !== jobID);
|
|
94
|
+
const {reportTime} = reportStats;
|
|
95
|
+
// Get the basics about the report.
|
|
96
|
+
const basics = {
|
|
97
|
+
identifier: `${timeStamp}-${jobID}`,
|
|
98
|
+
'completion date and time': reportTime.toISOString(),
|
|
99
|
+
'days since the report was completed': getAgoDays(reportTime),
|
|
100
|
+
'tested web page': {
|
|
101
|
+
description: what,
|
|
102
|
+
URL: url
|
|
103
|
+
},
|
|
104
|
+
'whether a later report about the same page exists': isSuperseded
|
|
105
|
+
};
|
|
106
|
+
// Return them.
|
|
107
|
+
return basics;
|
|
108
|
+
};
|
|
109
|
+
// Returns the classification of an issue.
|
|
110
|
+
exports.getIssueClassification = issueID => {
|
|
111
|
+
// Get the issue classification.
|
|
112
|
+
const issueClassification = issuesClassification[issueID] ?? {};
|
|
113
|
+
const {summary, wcag, weight, why} = issueClassification;
|
|
114
|
+
// If the issue is non-ignorable and fully classified:
|
|
115
|
+
if (
|
|
116
|
+
issueID
|
|
117
|
+
&& issueID !== 'ignorable'
|
|
118
|
+
&& issueClassification
|
|
119
|
+
&& summary
|
|
120
|
+
&& wcag
|
|
121
|
+
&& [1, 2, 3, 4].includes(weight)
|
|
122
|
+
&& why
|
|
123
|
+
) {
|
|
124
|
+
// Return the classification.
|
|
125
|
+
return issueClassification;
|
|
126
|
+
}
|
|
127
|
+
// Otherwise, i.e. if it is ignorable or not fully classified, return this.
|
|
128
|
+
return null;
|
|
129
|
+
};
|
|
130
|
+
// Processes a test or retest request.
|
|
131
|
+
exports.processTestRequest = async (testType, what, url, why) => {
|
|
132
|
+
// Get an email-safe version of the reason.
|
|
133
|
+
const plainWhy = getPlainText(why);
|
|
134
|
+
// Update the waiting recommendations as a transaction.
|
|
135
|
+
const updateResult = await updateRecs(what, url, plainWhy);
|
|
136
|
+
// If the recommendation was a duplicate:
|
|
137
|
+
if (updateResult.error === 'duplicate') {
|
|
138
|
+
// Return this.
|
|
139
|
+
return {
|
|
140
|
+
status: 'error',
|
|
141
|
+
message: 'Duplicate request'
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
// Otherwise, i.e. if it was not a duplicate, alert a manager about it.
|
|
145
|
+
await sendAlert(
|
|
146
|
+
`Kilotest: new ${testType} recommendation in the API`,
|
|
147
|
+
`Target: ${what}\nURL: ${url}\nReason: ${plainWhy}`
|
|
148
|
+
);
|
|
149
|
+
};
|
package/diagnoses/index.js
CHANGED
|
@@ -64,7 +64,7 @@ const populateQuery = async (issueID, timeStamp, jobID, catalogIndex, pathID, qu
|
|
|
64
64
|
query.testInfo = testInfo;
|
|
65
65
|
query.issue = issues[issueID]?.summary;
|
|
66
66
|
// If adding the issue summary failed:
|
|
67
|
-
if (!
|
|
67
|
+
if (!query.issue) {
|
|
68
68
|
// Populate the query with the reason.
|
|
69
69
|
query.error = 'Issue not found';
|
|
70
70
|
// Stop populating the query.
|
|
@@ -77,7 +77,7 @@ const populateQuery = async (issueID, timeStamp, jobID, catalogIndex, pathID, qu
|
|
|
77
77
|
query.priority = getWeightName(weight);
|
|
78
78
|
query.wcag = `<a href="${getWCAGLink(wcag)}">${wcag}</a>`;
|
|
79
79
|
query.tagName = tagName || 'HTML';
|
|
80
|
-
if (text && !
|
|
80
|
+
if (text && !['HTML', 'BODY', 'HEAD', 'SCRIPT', 'STYLE', 'NOSCRIPT'].includes(tagName)) {
|
|
81
81
|
const textString = text.split('\n').join(' … ');
|
|
82
82
|
query.text = `<q>${htmlSafe(textString)}</q>`;
|
|
83
83
|
}
|
|
@@ -1,32 +1,33 @@
|
|
|
1
|
-
# Kilotest as connector to and
|
|
1
|
+
# Kilotest as connector to and collection of AI Tools
|
|
2
2
|
|
|
3
3
|
## Introduction
|
|
4
4
|
|
|
5
5
|
Until 2026 Kilotest was intended, and implemented, as a web application performing a service for human users.
|
|
6
6
|
|
|
7
|
-
Beginning in May 2026, it [became evident](https://github.com/jrpool/kilotest/issues/2) that Kilotest could also act as a
|
|
7
|
+
Beginning in May 2026, it [became evident](https://github.com/jrpool/kilotest/issues/2) that Kilotest could also act as a provider of tools for use by language models. Language models are asked for help in all domains, including the domain of software quality. When asked about the front-end quality (accessibility, usability, and standards conformity) of specific web pages, models gave answers without the use of tools. The answers were at best fragmentary but often speculative and fabricated. If Kilotest provided its functionalities to language models, models could give more inexpensive, comprehensive, factual, authoritative, and grounded answers. Every reported defect could be documented and ascribed to one or more specific rule engines in the Kilotest ensemble.
|
|
8
8
|
|
|
9
|
-
Given the potential of Kilotest to serve language models and the expected continued growth in the share of questions that are directed to AI platforms that use language models, a decision was made to **make Kilotest discoverable, usable, and, where appropriate, used as a
|
|
9
|
+
Given the potential of Kilotest to serve language models and the expected continued growth in the share of questions that are directed to AI platforms that use language models, a decision was made to **make Kilotest discoverable, usable, and, where appropriate, used as a provider of tools for language models**.
|
|
10
10
|
|
|
11
11
|
## Terms
|
|
12
12
|
|
|
13
13
|
- **Language model**: A model (e.g., Claude Haiku 4.5, Kimi K2.6, GPT-5.4, Gemini 3.1 Pro) that can consume and generate text, images, or other content.
|
|
14
14
|
- **AI platform**: A platform (e.g., Claude Desktop, Perplexity, ChatGPT, Gemini 3.5 Flash) that gives human users access to the services of language models and connects language models to productivity resources.
|
|
15
|
-
- **tool**: A specialized productivity resource providing a capability that
|
|
15
|
+
- **tool**: A specialized productivity resource providing a capability that a language model needs but does not have. In the OpenAPI specification, the term _operation_ is used to mean tool.
|
|
16
16
|
- **connector**: A service that allows AI platforms to enable their language models to discover, evaluate, and use tools.
|
|
17
17
|
|
|
18
|
-
Tools can be independent of connectors that connect them to language models, but Kilotest can be both. It already provides capabilities to human users; it can re-use the same functions that underlie those capabilities and reconfigure those functions as tools for language models. Kilotest can also provide connectors to those tools for language models and make those connectors compatible with AI platforms.
|
|
19
|
-
|
|
20
18
|
## Internal additions
|
|
21
19
|
|
|
22
|
-
The internal changes that have been made in the Kilotest codebase to support the use of Kilotest as a
|
|
20
|
+
The internal changes that have been made in the Kilotest codebase to support the use of Kilotest as a collection of tools are:
|
|
23
21
|
|
|
24
22
|
- An API, consisting of:
|
|
25
23
|
- Additions to `index.js`.
|
|
26
24
|
- Additions to `util.js`.
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
- `util.js`
|
|
25
|
+
- An `api` directory containing:
|
|
26
|
+
- Eight tool modules.
|
|
27
|
+
- A `util.js` module with shared functions used by tool modules.
|
|
28
|
+
- A `routes.js` file.
|
|
29
|
+
- A `schemas.js` file.
|
|
30
|
+
- A `testSuite.js` module for testing the API.
|
|
30
31
|
- A [`JSON-LD`](https://json-ld.org/) script in the `index.html` file, providing structured data about the Kilotest API.
|
|
31
32
|
- Additions to the `env.example` file.
|
|
32
33
|
- An `llms.txt` file and an `llms-full.txt` file, documenting the use of Kilotest by language models, conforming to the [llms-txt](https://llmstxt.org/) specification.
|
|
@@ -34,12 +35,12 @@ The internal changes that have been made in the Kilotest codebase to support the
|
|
|
34
35
|
- An `mcp.js` file, providing an MCP server for Kilotest.
|
|
35
36
|
- A `sitemap.xml` file.
|
|
36
37
|
- Additions to the `README.md` file.
|
|
37
|
-
- This `AI-
|
|
38
|
-
- A
|
|
38
|
+
- This `AI-TOOLS.md` file.
|
|
39
|
+
- A [tutorial on authorizing language models to use Kilotest](https://kilotest.com/qai/).
|
|
39
40
|
|
|
40
41
|
## External actions
|
|
41
42
|
|
|
42
|
-
The external actions that have been taken to support the use of Kilotest as
|
|
43
|
+
The external actions that have been taken to support the use of Kilotest as a collection of AI tools are:
|
|
43
44
|
|
|
44
45
|
- A [pull request](https://github.com/public-apis/public-apis/pull/6346/changes) to add Kilotest to the list of public APIs in the `public-apis` repository.
|
|
45
46
|
- A [request](https://rapidapi.com/studio/api_91f2ce07-2572-48bd-a34d-ff01ed6cd039/publish/general) to add Kilotest to the Rapid API Hub.
|
|
@@ -53,7 +54,7 @@ The external actions that have been taken to support the use of Kilotest as an A
|
|
|
53
54
|
|
|
54
55
|
## Use cases
|
|
55
56
|
|
|
56
|
-
The rationale for Kilotest as a
|
|
57
|
+
The rationale for Kilotest as a collection of tools for language models is set forth in the `llms-full.txt` file and is not repeated here.
|
|
57
58
|
|
|
58
59
|
Some common anticipated use cases for this role are:
|
|
59
60
|
|
|
@@ -68,11 +69,7 @@ Among these use cases, case 3 would make it feasible for the user to tell an AI
|
|
|
68
69
|
|
|
69
70
|
Use cases 1, 2, 4, 5, and 6 exemplify a widespread expectation and demand for AI platform capability. The commonality is: “I have a question; answer it.” If Kilotest can be employed as an expert for AI platforms in relevant cases, platforms will be more successful in satisfying that demand. At present this is a difficult problem because of platform limitations and a lack of standardization.
|
|
70
71
|
|
|
71
|
-
##
|
|
72
|
-
|
|
73
|
-
The Kilotest project is attempting to solve the just-described problem. That attempt follows the following strategy.
|
|
74
|
-
|
|
75
|
-
### Increments
|
|
72
|
+
## Implementation strategy
|
|
76
73
|
|
|
77
74
|
Connector and tool discovery and utilization have been only partly standardized. Major differences in protocols exist among model and platform families. Therefore, small testable increments of improvement in the connector and tool functionality of Kilotest can best be defined by model and platform family and by use-case characteristics.
|
|
78
75
|
|
|
@@ -80,7 +77,11 @@ One benefit of such incrementalism is that, after the first increment succeeds,
|
|
|
80
77
|
|
|
81
78
|
Another benefit is that subsequent increments can be defined incrementally rather than in advance. Lessons learned from the work on each increment can inform the choice of what to work on next.
|
|
82
79
|
|
|
83
|
-
|
|
80
|
+
## Implementation history
|
|
81
|
+
|
|
82
|
+
The Kilotest project has been attempting to solve the just-described problem. A chronology of this work follows.
|
|
83
|
+
|
|
84
|
+
### Increment 1
|
|
84
85
|
|
|
85
86
|
In the first increment, the objective was to make Anthropic Claude models use Kilotest to help them answer questions from developers using the Claude Desktop application for use case 3, where the code in question is already deployed as a public web page.
|
|
86
87
|
|
|
@@ -88,7 +89,7 @@ A locally installed MCP server, `@ivotoby/openapi-mcp-server`, was used. It turn
|
|
|
88
89
|
|
|
89
90
|
The model behavior motivated an architectural change in the MCP server and improvements in the naming and description of the Kilotest tools before the next increment.
|
|
90
91
|
|
|
91
|
-
|
|
92
|
+
### Increment 2
|
|
92
93
|
|
|
93
94
|
Increment 2 repeated the exercise of increment 1 with the `claude.ai` web application instead of Claude Desktop, and with the improvements indicated by increment 1 made.
|
|
94
95
|
|
|
@@ -105,7 +106,7 @@ The trial in increment 2 suggested some opportunities for further improvements:
|
|
|
105
106
|
- Clearer instructions about the sequencing of tool calls
|
|
106
107
|
- Addition of instructions to attribute the interpretive additions by the model to the model or its other sources rather than to Kilotest.
|
|
107
108
|
|
|
108
|
-
|
|
109
|
+
### Increment 3
|
|
109
110
|
|
|
110
111
|
After the above-described improvements were made, a similar exercise, with the home page of a company named “CMT Services”, was performed again in Claude Desktop. In this case Claude Haiku 4.5 succeeded in recognizing the relevance of Kilotest and used its tools, though imperfectly. Instead of using Kilotest to check for an existing report on the page, the model searched for it in a cached file with:
|
|
111
112
|
|
|
@@ -137,7 +138,7 @@ The misbehavior of Claude Haiku suggested that further improvements in the instr
|
|
|
137
138
|
- a warning not to use cached lists of available reports
|
|
138
139
|
- stronger advice to avoid implicitly attributing interpretations, judgments, and advice to Kilotest.
|
|
139
140
|
|
|
140
|
-
|
|
141
|
+
### Increment 4
|
|
141
142
|
|
|
142
143
|
The first trial in which the user asked about a page **without** an available report repeated the conditions of Increment 3, except for the web page in question and the model. The model here was Claude Haiku in Extended mode, an option available with `claude.ai` but not Claude Desktop.
|
|
143
144
|
|
|
@@ -160,7 +161,7 @@ The most notable fact was that Claude Haiku converted a long workflow to a short
|
|
|
160
161
|
|
|
161
162
|
This result suggested that the instructions should be revised to describe, as a valid option, a short workflow ending with a recommendation to the user to get the test results by self-service after the model submits a testing recommendation.
|
|
162
163
|
|
|
163
|
-
|
|
164
|
+
### Increment 5
|
|
164
165
|
|
|
165
166
|
In the next increment the AI platform was changed to [Perplexity Pro](https://www.perplexity.ai/) on the web. With the platform UI, two configuration steps were taken:
|
|
166
167
|
|
|
@@ -181,8 +182,35 @@ For both models, the same exercise was also attempted with the second configurat
|
|
|
181
182
|
|
|
182
183
|
The [Perplexity documentation](https://www.perplexity.ai/help-center/en/articles/13915507-adding-custom-remote-connectors) states that as of now the installed Perplexity application for macOS does not support remote connectors.
|
|
183
184
|
|
|
184
|
-
|
|
185
|
+
### Increment 6
|
|
185
186
|
|
|
186
187
|
Once the compatibility of Kilotest as a connector with AI platforms of two different vendors had been confirmed, the focus of work returned to completion of the missing endpoints of the API. The first was an endpoint that would let a model efficiently search for an available report about a particular web page.
|
|
187
188
|
|
|
188
|
-
The new `summarizeQualityOfMatchingWebPages` tool
|
|
189
|
+
The new `summarizeQualityOfMatchingWebPages` tool provided inputs for fragments of a description and of a URL hostname of a web page and responds with summary data about all and only the available reports of pages that match at least one of those fragments, where matching means either including or being included by the provided fragment, case-insensitively.
|
|
190
|
+
|
|
191
|
+
### Increment 7
|
|
192
|
+
|
|
193
|
+
Further experimentation made it clear that Increment 6 had been a detour. The problem with Increment 6 was that it does not improve efficiency by eliminating the need to get a list of all reports. Models must get a list of all reports in order to be confident that the answer to the question “Is a report available yet?” is correct. Any algorithm that matches a page description and a URL with those of existing reports is error-prone, organization names change, website designs change, URLs change, and organizations are related to one another. Typically, a desired report does not yet exist. In that case, the model needs the full list of reports in order to understand the naming convention for pages and to adhere to that convention when submitting a request to have the page of interest tested.
|
|
194
|
+
|
|
195
|
+
For this reason, the tool being developed in increment 6 was deleted.
|
|
196
|
+
|
|
197
|
+
### Increment 8
|
|
198
|
+
|
|
199
|
+
The next increment reorganized the API by placing API-related code into a new `api` directory.
|
|
200
|
+
|
|
201
|
+
### Increment 9
|
|
202
|
+
|
|
203
|
+
The collection of tools was enlarged to a total of eight tools:
|
|
204
|
+
|
|
205
|
+
- listReports
|
|
206
|
+
- listIssues
|
|
207
|
+
- listViolators
|
|
208
|
+
- listDiagnoses
|
|
209
|
+
- getReport
|
|
210
|
+
- requestRetest
|
|
211
|
+
- requestTest
|
|
212
|
+
- requestFeature
|
|
213
|
+
|
|
214
|
+
## Increment 10
|
|
215
|
+
|
|
216
|
+
In the next increment, API code was refactored and the tools were made more consistent with one another.
|
package/docs/SERVICE.md
CHANGED
|
@@ -203,8 +203,9 @@ kilotest.com {
|
|
|
203
203
|
}
|
|
204
204
|
@allowedPOST {
|
|
205
205
|
method POST
|
|
206
|
-
path /mcp /retestRec.html/* /testRec.html
|
|
206
|
+
path /mcp /retestRec.html/* /testRec.html /recAction.html /reannotate.html /wcagRenew.html /api/* /tutorialComment.html
|
|
207
207
|
}
|
|
208
|
+
# Respond to OPTIONS requests.
|
|
208
209
|
@allowedOPTIONS method OPTIONS
|
|
209
210
|
handle @allowedOPTIONS {
|
|
210
211
|
header {
|
|
@@ -214,6 +215,8 @@ kilotest.com {
|
|
|
214
215
|
}
|
|
215
216
|
respond 204
|
|
216
217
|
}
|
|
218
|
+
# Redirect any /qai request to /qai/.
|
|
219
|
+
redir /qai /qai/ 301
|
|
217
220
|
# Truncate any initial /qai from the forwarded path.
|
|
218
221
|
handle_path /qai* {
|
|
219
222
|
# Forward any QAI request to port 3001.
|
|
@@ -1,49 +1,51 @@
|
|
|
1
1
|
---
|
|
2
|
-
date: 2026-07-
|
|
3
|
-
status:
|
|
2
|
+
date: 2026-07-14
|
|
3
|
+
status: accepted
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# API design
|
|
7
7
|
|
|
8
8
|
## Context and problem
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Kilotest manages data that are organized into reports. Each report contains the results of one session in which multiple rule engines were used for testing the front-end quality of one web page.
|
|
11
|
+
|
|
12
|
+
The UI offers users opportunities to influence and interrogate the data managed by the application. Users do this with input via links and forms. The output is HTML. UI users are typically humans.
|
|
11
13
|
|
|
12
14
|
The opportunities to interrogate the data in the UI are organized as a hierarchy of granularity:
|
|
13
15
|
|
|
14
16
|
1. All available reports
|
|
15
|
-
2.
|
|
16
|
-
3.
|
|
17
|
-
4.
|
|
17
|
+
2. Details of one report, including summaries of the reported issues.
|
|
18
|
+
3. Details of one issue, including summaries of its violators.
|
|
19
|
+
4. Details of one violator, including its diagnoses.
|
|
18
20
|
|
|
19
|
-
A violator is a particular element (HTML, SVG, etc.) on the page
|
|
21
|
+
A violator of an issue is a particular element (HTML, SVG, etc.) on the page. When a rule engine reports a violation of one of its rules, the rule engine usually identifies an element as responsible for that violation. If the rule classifier used by Kilotest classifies that rule as belonging to a particular issue, then Kilotest refers to that element as a violator of that issue.
|
|
20
22
|
|
|
21
|
-
This hierarchical design is based on a realistic assumption about how human users interact with Kilotest. They visit its home page and begin what to them is a session. Although requests to Kilotest are stateless, the response at each level includes links that contain all the specifications required for a request at the next level. For example, the response to a request at level 3 includes, for each violator, a link that makes a request at level 4 and includes identifiers of the page, the issue and the violator.
|
|
23
|
+
This hierarchical design is based on a realistic assumption about how human users interact with Kilotest. They visit its home page and begin what to them is a session. Although requests to Kilotest are stateless, the response at each level includes links that contain all the specifications required for a request at the next level. For example, the response to a request at level 3 includes, for each violator, a link that makes a request at level 4 and includes identifiers of the page, the issue, and the violator.
|
|
22
24
|
|
|
23
|
-
The responses to requests in the UI at any level include
|
|
25
|
+
The responses to requests in the UI at any level include some of the information about the previous levels. For example, a response at level 3 includes a summary of information about the report and about the issues in that report. The prior-level summarized information is based on assumptions about what a typical human user would value. For example, at level 3, the prior-level information does not summarize all the available reports, but does include a list of the rule engines that succeeded and failed in testing the page and the counts of issues reported.
|
|
24
26
|
|
|
25
|
-
These assumptions for human UI users may or may not be valid for how
|
|
27
|
+
These assumptions for human UI users may or may not be valid for how API consumers prefer to interact with a Kilotest API. Those interactions may vary depending on the tasks that they are performing. Consider three scenarios:
|
|
26
28
|
|
|
27
|
-
1. A user asks
|
|
28
|
-
2. A user asks
|
|
29
|
-
3. A user asks
|
|
29
|
+
1. A user asks a language model for all available information about the front-end quality of a particular web page.
|
|
30
|
+
2. A user asks a language model for all the available information about issues related to a particular topic, such as keyboard navigation, for a particular web page.
|
|
31
|
+
3. A user asks a language model to describe the front-end quality of a particular web page. Having consumed the description, the user asks the model to describe one of the reported issues. Having consumed that description, the user asks the model to describe one of the reported violators of that issue. Having consumed that description, the user asks the model to describe the diagnoses of that violation.
|
|
30
32
|
|
|
31
|
-
Scenario 1 seems to justify an API that can aggregate all
|
|
33
|
+
Scenario 1 seems to justify an API that can aggregate all information from all available reports about the page into a single response.
|
|
32
34
|
|
|
33
|
-
Scenario 2 seems to justify an API that
|
|
35
|
+
Scenario 2 seems to justify an API that filters out from the scenario-1 response all information about issues unrelated to the specified topic.
|
|
34
36
|
|
|
35
|
-
Scenario 3 seems to justify an API that mimics the UI in structure
|
|
37
|
+
Scenario 3 seems to justify an API that mimics the UI in structure.
|
|
36
38
|
|
|
37
|
-
Even these apparently justified API designs depend on the assumption that users mean what they say,
|
|
39
|
+
Even these apparently justified API designs depend on the assumption that users mean what they say. For example, in scenario 1 a user asks for **all** available information about the front-end quality of a particular web page. The user may really want all that information only if it is not too voluminous, and otherwise want a summary.
|
|
38
40
|
|
|
39
|
-
Uncertainty about how
|
|
41
|
+
Uncertainty about how consumers will interact with the API creates a design problem: How should the API be further developed?
|
|
40
42
|
|
|
41
|
-
As of the date of this decision, a partial initial API version already exists: an API that mimics the UI for levels 1, 2, and 3. For each level, the
|
|
43
|
+
As of the date of this decision, a partial initial API version already exists: an API that mimics the UI for levels 1, 2, and 3. It is exposed as a traditional HTTP REST API and also as a connector to a collection of MCP tools. For each level, the API provides an operation.
|
|
42
44
|
|
|
43
45
|
## Considered options
|
|
44
46
|
|
|
45
|
-
1. **Iterate and instrument.** (1) Finish redesigning the
|
|
46
|
-
2. **Research first.** (1) Pause further API development while doing fundamental research to predict
|
|
47
|
+
1. **Iterate and instrument.** (1) Finish redesigning the operations at levels 1, 2, and 3, to represent the hypothesized typical needs of language-model consumers. Then (2) add an operation that implements level 4. Then (3) add an operation that collects comments from consumers on how the API design could better satisfy their needs. Then (4) add metrics to make API usage and submitted comments available to Kilotest managers.
|
|
48
|
+
2. **Research first.** (1) Pause further API development while doing fundamental research to predict consumer needs. Then (2) resume API and metrics development, including API redesign if the research results warrant.
|
|
47
49
|
|
|
48
50
|
## Decision
|
|
49
51
|
|
|
@@ -51,60 +53,68 @@ The decision is to adopt **option 1: iterate and instrument**.
|
|
|
51
53
|
|
|
52
54
|
## Empirical basis
|
|
53
55
|
|
|
54
|
-
The decision is based on the
|
|
56
|
+
The decision is based on the working hypothesis that the typical consumer will be a language model (most often a foundation model) performing a task for a human user. Even without a pause for fundamental research, there is enough empirical evidence to justify this decision.
|
|
55
57
|
|
|
56
58
|
### Completion of the 4-level hierarchy
|
|
57
59
|
|
|
58
|
-
There is empirical support for an API
|
|
60
|
+
There is empirical support for an API optimized for scenario 3, i.e. incremental rather than one-shot interrogation. Both single-shot comprehensive requests and incremental drill-downs occur in significant numbers in real-world language-model use.
|
|
59
61
|
|
|
60
|
-
- Jian
|
|
61
|
-
-
|
|
62
|
+
- Jian _et al._ (2024) studied experienced ChatGPT users and found that **43.8% of prompts were single-component**, **38.2% were dual-component**, and only **18.0% were multi-component**. A request such as scenario 1 (“tell me everything about page X”) or scenario 2 (“tell me about defect class X on page Y”) is structurally common because it is typically a single-component prompt.
|
|
63
|
+
- However, follow-up refinement is frequent. Mysore _et al._ (2025) analyzed large-scale Bing Copilot and WildChat writing sessions and report: “Rather than passively accepting output, users actively refine, explore, and co-construct text.” This matches scenario 3.
|
|
62
64
|
|
|
63
|
-
For scenario 3, current
|
|
65
|
+
For scenario 3, current patterns do not require the consumer to restart at level 1. Anthropic’s tool-using agent documentation describes an agentic loop in which the full conversation history is kept and “every turn sees the complete prior context”. Cohere’s multi-step tool-use documentation explains that the model can use information learned from one tool call to parameterize a subsequent call. MCP’s typed tool schemas support this pattern. Therefore the existing hierarchy can be expected to be practical in scenario 3.
|
|
64
66
|
|
|
65
67
|
### Usage and suggestion metrics
|
|
66
68
|
|
|
67
|
-
There is also empirical support for modesty in any estimate of the adequacy of the current or any other single API design. As found by Zhu
|
|
69
|
+
There is also empirical support for modesty in any estimate of the adequacy of the current or any other single API design. As found by Zhu _et al._ (2025), “users do not transfer existing conversational norms to LLM interactions, but instead develop new registers with distinct pragmatic features.” Metrics will help to inform the contributors to the API about how consumers are actually using it and about proposed improvements.
|
|
68
70
|
|
|
69
71
|
## Cost and context-window constraints
|
|
70
72
|
|
|
71
|
-
A user-friendly rendering of an entire Kilotest report is estimated at about 5 MB of text, or roughly 1.25 million tokens.
|
|
73
|
+
A user-friendly rendering of an entire Kilotest report is estimated at about 5 MB of text, or roughly 1.25 million tokens. For a cost estimate, we can use one major frontier model as of the date of this decision (Claude 3.5 Sonnet). On that basis, the report size exceeds the 200,000-token context window of the model, so the model cannot consume the whole report in one prompt. If it is passed chunked, each pass will cost roughly $3.75 in input tokens. Every subsequent question about it will require re-sending it. By contrast, a four-level drill-down through report → issue → violator → diagnosis costs roughly $0.07 for a typical path.
|
|
74
|
+
|
|
75
|
+
These estimates can be applied to a hypothetically typical conversation. It would involve one report, two issues, and six violators (two per issue). The user would not ask about diagnoses. Delivering such results with the full-report method requires one pass to identify the report, two passes to describe the issues, and six passes to describe the violators, for a total of about $33.75. Doing so incrementally costs about $0.26, which is only **1/130th** of the cost of the same result with the full-report strategy.
|
|
76
|
+
|
|
77
|
+
On the basis of these cost estimates, even if a user initially wanted a comprehensive report and then wanted a language model to interpret it, the impact of that strategy on cost would likely motivate the user to switch to an incremental strategy as a default.
|
|
72
78
|
|
|
73
79
|
## Sub-decision: ancestor information in level responses
|
|
74
80
|
|
|
75
|
-
Given this decision, a secondary decision to make is what information from prior levels to repeat in a response.
|
|
81
|
+
Given this decision, a secondary decision to make is what information from prior levels to repeat in a response. Four alternatives were considered:
|
|
82
|
+
|
|
83
|
+
1. **Identifiers only:** The report, issue, and violator would be described by their unique identifiers only. The consumer would use the identifiers to retrieve from its conversation-history context any forgotten information needed about them.
|
|
84
|
+
2. **Identifiers plus labels:** Ancestor entities (report, issue, and/or violator) would be described only by their unique identifiers and human-interpretable labels. If the consumer needs other forgotten facts about them, it would use the identifiers to retrieve those facts by repeating ancestor requests.
|
|
85
|
+
3. **Currently pertinent facts:** The facts from ancestor requests would be repeated if, and only if, the consumer could need them for interpreting the response to the current-level request. For example, if the current-level request is about one report, facts about other reports are not pertinent, but any facts about the report are pertinent. Likewise for one issue or one violator.
|
|
86
|
+
4. **Cumulative facts:** All the information from the prior responses would be repeated, so that any needed information would be available in the current response and no search of the conversation history would be needed.
|
|
76
87
|
|
|
77
|
-
|
|
78
|
-
2. **Identifiers plus labels:** The report, issue, and violator would be described by their unique identifiers and human-interpretable labels. If the LLM needs more information about them than what their labels provide, the LLM would use the identifiers to retrieve from its conversation-history context that additional information.
|
|
79
|
-
3. **Cumulative responses:** All the information from the prior responses would be repeated, so that any needed information would be available in the current response and no search of the conversation history would be needed.
|
|
88
|
+
The sub-decision was **currently pertinent facts** (option 3). This decision was based on cost and reliability, as follows.
|
|
80
89
|
|
|
81
|
-
|
|
90
|
+
- **Cost:** Option 3 is slightly more costly than option 2 when all pertinent facts are remembered, because it provides duplicate information. But option 3 is substantially less costly than option 2 if needed facts are forgotten, because option 3 avoids the need to repeat an ancestor request to re-retrieve forgotten facts. Option 4 is much more costly than option 3, because of the much larger response bodies and the inclusion of much irrelevant information in responses.
|
|
91
|
+
- **Quality:** Option 2 provides better locality than option 1, and option 3 provides better locality than option 2. Locality improves the probability that a language model will actually make use of pertinent facts instead of ignoring them or fabricating hallucinated replacements for them.
|
|
92
|
+
- **Reliability:** Option 1 entails a greater risk of information loss and hallucination than option 2. With option 1, the consumer could be unable to retrieve the needed information using identifiers if the conversation history were truncated, if an operation were invoked in isolation, or if the model were asked later to summarize findings in natural language.
|
|
82
93
|
|
|
83
|
-
-
|
|
84
|
-
- **Reliability:** Option 1 would entail a greater risk of information loss and hallucination than option 2, because the LLM could be unable to retrieve the needed information using identifiers if its history were truncated, if a tool were invoked in isolation, or if the model were asked later to summarize findings in natural language.
|
|
94
|
+
No sub-decision option is optimal under all possible conditions, but the working hypothesis about prevailing use patterns favors option 3.
|
|
85
95
|
|
|
86
96
|
## Implementation plan
|
|
87
97
|
|
|
88
|
-
- The existing
|
|
89
|
-
- Level 4 will be implemented
|
|
90
|
-
- A
|
|
91
|
-
- Usage metrics will be defined, captured, and recorded
|
|
98
|
+
- The existing request handlers for levels 1, 2, and 3 will be revised. After revision, they will omit excess information about prior levels and include all currently pertinent facts.
|
|
99
|
+
- Level 4 will be implemented. It will return the diagnoses for one violator of one issue in one report.
|
|
100
|
+
- A comment operation will be provided. This will allow consumers to report friction or suggest improvements to the API design.
|
|
101
|
+
- Usage metrics will be defined, captured, and recorded. This will allow Kilotest managers to observe which levels, issues, and patterns are most common and what comments have been received.
|
|
92
102
|
- If the empirical data later show that the API does not serve some common scenario well, the API will be redesigned accordingly.
|
|
93
103
|
|
|
94
104
|
### Risks and mitigations
|
|
95
105
|
|
|
96
|
-
- **Risk:** The hierarchy may not match the way
|
|
97
|
-
- **Mitigation:** The
|
|
98
|
-
- **Risk:** The cost
|
|
99
|
-
- **Mitigation:**
|
|
100
|
-
- **Risk:** Scenario 1
|
|
101
|
-
- **Mitigation:** A future
|
|
106
|
+
- **Risk:** The hierarchy may not match the way consumers actually want to query the data.
|
|
107
|
+
- **Mitigation:** The comment operation and usage metrics will surface mismatches early, allowing Kilotest managers to make data-driven decisions about API improvements.
|
|
108
|
+
- **Risk:** The cost estimates assume the typical consumer preserves conversation history. A poorly implemented consumer could still incur higher-than-necessary costs.
|
|
109
|
+
- **Mitigation:** It is hypothesized that typical consumers will be models that maintain message history. When they do not, the ancestor labels provide a minimal human-friendly substitute that facilitates history reconstruction when needed.
|
|
110
|
+
- **Risk:** Scenario 1 consumers may still want an aggregated “everything about page X” response.
|
|
111
|
+
- **Mitigation:** A future operation can be added to aggregate the hierarchy on the server side and return a structured summary. That decision is deferred until usage and comment data justify it.
|
|
102
112
|
|
|
103
113
|
## References
|
|
104
114
|
|
|
105
|
-
- Jian, J.,
|
|
106
|
-
- Mysore, S.,
|
|
107
|
-
- Zhu, S.,
|
|
115
|
+
- Jian, J., _et al._ (2024). [Understanding User Prompting Behavior in Generative AI](https://dl.acm.org/doi/abs/10.1002/pra2.1318).
|
|
116
|
+
- Mysore, S., _et al._ (2025). [Prototypical Human-AI Collaboration Behaviors from LLM-Assisted Writing in the Wild](https://aclanthology.org/2025.emnlp-main.852/).
|
|
117
|
+
- Zhu, S., _et al._ (2025). [Show or Tell? Modeling the evolution of request-making in human-LLM conversations](https://arxiv.org/html/2508.01213v2).
|
|
108
118
|
- Anthropic. [Tutorial: Build a tool-using agent](https://platform.claude.com/docs/en/agents-and-tools/tool-use/build-a-tool-using-agent).
|
|
109
|
-
- Cohere. [Multi-step Tool Use](https://docs.cohere.com/
|
|
119
|
+
- Cohere. [Multi-step Tool Use](https://docs.cohere.com/docs/tool-use-usage-patterns#multi-step-tool-use).
|
|
110
120
|
- [Model Context Protocol specification](https://modelcontextprotocol.io/specification/2025-06-18/server/tools).
|
package/eslint.config.mjs
CHANGED
|
@@ -8,19 +8,29 @@ import {defineConfig} from "eslint/config";
|
|
|
8
8
|
export default defineConfig([
|
|
9
9
|
{
|
|
10
10
|
ignores: [
|
|
11
|
-
"IDEAS.md",
|
|
11
|
+
"docs/IDEAS.md",
|
|
12
12
|
"package-lock.json"
|
|
13
13
|
]
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
files: ["**/*.{js,mjs,cjs}"],
|
|
17
|
-
plugins: {
|
|
17
|
+
plugins: {
|
|
18
|
+
js
|
|
19
|
+
},
|
|
18
20
|
extends: ["js/recommended"],
|
|
19
21
|
languageOptions: {
|
|
20
22
|
globals: globals.node
|
|
21
23
|
},
|
|
22
24
|
rules: {
|
|
23
|
-
"no-control-regex": "off"
|
|
25
|
+
"no-control-regex": "off",
|
|
26
|
+
"rest-spread-spacing": ["error", "never"],
|
|
27
|
+
"space-unary-ops": [
|
|
28
|
+
"error",
|
|
29
|
+
{
|
|
30
|
+
"words": true,
|
|
31
|
+
"nonwords": false
|
|
32
|
+
}
|
|
33
|
+
]
|
|
24
34
|
}
|
|
25
35
|
},
|
|
26
36
|
{
|
package/index.html
CHANGED
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
<main>
|
|
59
59
|
<h1>Kilotest</h1>
|
|
60
60
|
<p>Kilotest performs more than a thousand tests on a web page.</p>
|
|
61
|
-
<p
|
|
61
|
+
<p>New to accessibility testing? <a href="/tutorial.html">Start with the tutorial.</a></p>
|
|
62
62
|
<details>
|
|
63
63
|
<summary>More than a thousand? Really?</summary>
|
|
64
64
|
<h2>Ensemble testing</h2>
|