@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.
Files changed (57) hide show
  1. package/.markdownlint-cli2.jsonc +12 -0
  2. package/.vscode/settings.json +3 -0
  3. package/CLAUDE.md +5 -0
  4. package/README.md +6 -4
  5. package/ai0BalanceForm/index.html +1 -1
  6. package/alerts.js +3 -1
  7. package/api/getReport.js +66 -0
  8. package/api/listDiagnoses.js +157 -0
  9. package/api/listIssues.js +225 -0
  10. package/api/listReports.js +87 -0
  11. package/api/listViolators.js +164 -0
  12. package/api/requestFeature.js +56 -0
  13. package/api/requestRetest.js +102 -0
  14. package/api/requestTest.js +106 -0
  15. package/api/routes.js +97 -0
  16. package/api/schemas.js +297 -0
  17. package/api/testSuite.js +294 -0
  18. package/api/util.js +149 -0
  19. package/diagnoses/index.js +2 -2
  20. package/docs/{AI-TOOL.md → AI-TOOLS.md} +54 -26
  21. package/docs/SERVICE.md +4 -1
  22. package/docs/decisions/001-api-design.md +59 -49
  23. package/eslint.config.mjs +13 -3
  24. package/index.html +1 -1
  25. package/index.js +202 -188
  26. package/issues/index.js +13 -21
  27. package/jsconfig.json +8 -0
  28. package/llms-full.txt +2 -2
  29. package/llms.txt +16 -11
  30. package/mcp.js +129 -65
  31. package/openapi.yaml +1234 -572
  32. package/package.json +18 -11
  33. package/reannotate/index.js +26 -15
  34. package/reannotateForm/index.js +15 -13
  35. package/redocly.yaml +4 -0
  36. package/reportHideForm/index.html +1 -1
  37. package/reportHideForm/index.js +24 -38
  38. package/reportIssue/index.js +3 -3
  39. package/reportIssues/index.js +128 -1
  40. package/reportUnhideForm/index.js +36 -44
  41. package/reportsExpungeForm/index.js +22 -16
  42. package/reportsPruneForm/index.js +22 -12
  43. package/reportsRewindForm/index.js +17 -9
  44. package/retestRec/index.js +8 -4
  45. package/retestRecForm/index.js +18 -6
  46. package/scripts/generate-openapi.js +83 -0
  47. package/targets/index.js +18 -13
  48. package/testOrder/index.js +11 -8
  49. package/tutorial/index.html +12 -12
  50. package/util.js +581 -539
  51. package/api/reportList.js +0 -83
  52. package/api/reportSummary.js +0 -181
  53. package/reportIssue/api.js +0 -183
  54. package/reportIssues/util.js +0 -138
  55. package/researchAgent.js +0 -206
  56. package/target/api.js +0 -126
  57. 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
+ };
@@ -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 (! query.issue) {
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 && ! ['HTML', 'BODY', 'HEAD', 'SCRIPT', 'STYLE', 'NOSCRIPT'].includes(tagName)) {
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 creator of AI Tools
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 connector to 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 offered to connect language models to its functionalities as tools, 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.
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 connector to, and creator of, tools for language models**.
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 the language model itself needs but does not have. In the OpenAPI specification, the term _operation_ is used to mean tool.
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 set of tools and connectors to them are:
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
- - Within directories providing API functionality:
28
- - `api.js` modules.
29
- - `util.js` modules, if needed, containing resources shared by the `index.js` and `api.js` modules in those directories.
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-TOOL.md` file.
38
- - A `researchAgent.js` file, testing the API.
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 an AI tool are:
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 connector to tools for language models is set forth in the `llms-full.txt` file and is not repeated here.
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
- ## Strategy
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
- #### Increment 1
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
- #### Increment 2
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
- #### Increment 3
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
- #### Increment 4
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
- #### Increment 5
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
- #### Increment 6
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 provides 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.
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/* /recAction.html /reannotate.html /wcagRenew.html /api/* /tutorialComment.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-09
3
- status: proposed
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
- The UI of Kilotest offers human non-manager users opportunities to influence and to interrogate the data managed by the application. The data are organized into reports. Each report contains the results of one session in which 10 rule engines were used for testing the front-end quality of one web page.
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. All issues from one report
16
- 3. All violators of one issue from one report
17
- 4. All diagnoses for one violator of one issue from one report
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 that some rule engine reported having violated a particular rule of the rule engine. If the rule classifier used by Kilotest classifies that rule as belonging to a particular issue, then that element is referred to as a violator of that issue.
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 a summary of 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.
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 LLMs interact with the Kilotest API. Those interactions may vary depending on the tasks that they are performing. Consider three scenarios:
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 an LLM for all available information about the front-end quality of a particular web page.
28
- 2. A user asks an LLM for all the available information about issues related to a particular topic, such as keyboard navigation, for a particular web page.
29
- 3. A user asks an LLM to summarize the front-end quality of a particular web page; having consumed the summary, asks the LLM for a summary of information about one of the reported issues; having consumed that summary, asks the LLM for a summary of information about one of the reported violators of that issue; and, having consumed that summary, asks the LLM for a summary of information about the diagnoses for that violator of that issue.
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 the information about the latest report about the page into a single response.
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 offers enough information about all the available reports and all the defined issues to allow the LLM to make a request at level 3 for a particular issue in a particular report.
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, but perhaps not in the prior-levels information provided at each level.
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, particularly in scenario 1. A user who asks for **all** available information about the front-end quality of a particular web page may do so because of ignorance about how voluminous that information is, and the real wish may be more conditional: all the information if there is not much, but otherwise a summary so I can select what I want to learn more about.
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 LLMs will interact with the API creates a design problem. How should the API be further developed?
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 MCP server provides a tool.
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 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
- 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
+ 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 fact that, even without a pause for fundamental research, there is enough empirical evidence to justify this decision.
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 that assumes 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 LLM use.
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 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.
61
- - At the same time, follow-up refinement is routine. 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
+ - 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 LLM tool-use patterns do not require the model to restart at level 1. Anthropic's tool-use 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.
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 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 LLMs are actually using it and how they want it to be improved.
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. This exceeds the 200,000-token context window of Claude 3.5 Sonnet, so the model cannot consume the whole report in one prompt. If it could be passed whole, each pass would cost roughly $3.75 in input tokens at current pricing, and every subsequent question about it would require re-sending it. By contrast, a four-level drill-down through report → issue → violator → diagnosis costs roughly $0.07 for a typical path. An intuitive guess is that a typical conversation would involve 1 report, 2 issues, and 3 violators per issue. Delivering such results with the full-report method would require 2 issue passes and 6 violator passes beyond the initial pass, for a total of about $33.75. Doing so incrementally would cost about $0.26, which is only **1/130th** of the cost of the same result with the full-report strategy. Even if a user initially wanted a comprehensive report and then wanted an LLM to find and interpret it, the impact of that strategy on cost would likely motivate the user to switch to an incremental strategy.
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. Three alternatives were considered:
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
- 1. **Identifiers only:** The report, issue, and violator would be described by their unique identifiers only. The LLM would use the identifiers to retrieve from its conversation-history context any information needed about them.
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
- The sub-decision was **identifiers plus labels** (option 2). This decision was based on cost and reliability, as follows.
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
- - **Cost:** Option 3 would probably make a four-level drill-down several times as expensive as option 2, because of the large response volumes.
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 MCP tools for levels 1, 2, and 3 will be revised to remove excess information about prior levels and include only identifiers and labels for them.
89
- - Level 4 will be implemented as an MCP tool that returns the diagnoses for one violator of one issue in one report.
90
- - A suggestions tool will be provided so that LLM-based clients can report friction or suggest improvements to the API design.
91
- - Usage metrics will be defined, captured, and recorded so that Kilotest managers can observe which levels, issues, and patterns are most common and what suggestions have been received.
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 LLMs actually want to query the data.
97
- - **Mitigation:** The suggestions tool and usage metrics will surface mismatches early.
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
- - **Mitigation:** MCP clients are expected to maintain message history by protocol; the ancestor labels provide a minimal human-friendly substitute.
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 and suggestions data justify it.
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., et al. (2024). [Understanding User Prompting Behavior in Generative AI](https://dl.acm.org/doi/abs/10.1002/pra2.1318).
106
- - Mysore, S., et al. (2025). [Prototypical Human-AI Collaboration Behaviors from LLM-Assisted Writing in the Wild](https://aclanthology.org/2025.emnlp-main.852/).
107
- - Zhu, S., et al. (2025). [Show or Tell? Modeling the evolution of request-making in human-LLM conversations](https://arxiv.org/html/2508.01213v2).
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/v1/docs/multi-step-tool-use).
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: { js },
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><a href="/tutorial.html">New to accessibility testing? Start with the tutorial.</a></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>