@jrpool/kilotest 37.1.0 → 38.1.0

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