@jrpool/kilotest 34.0.1 → 35.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/SERVICE.md CHANGED
@@ -181,13 +181,23 @@ curl -sv https://example.com/ -o /dev/null
181
181
 
182
182
  Requests to `https://kilotest.com` are received on port 443 and processed by [Caddy](https://caddyserver.com/), which forwards them via HTTP to the application at `localhost:3000`. Caddy manages, provisions, and renews a TLS certificate via Let’s Encrypt. Any request to `http://kilotest.com` is received on port 80, and Caddy redirects it to an `https` request. Caddy forwards `https` requests to the [local server](http://localhost:3000), where it is processed by the Kilotest service.
183
183
 
184
- The Caddy configuration is maintained and tracked in `/etc/caddy/Caddyfile`:
184
+ The Caddy configuration is maintained and tracked in `/etc/caddy/Caddyfile`. Leading 2 spaces below represent 1 Tab character. The `handle_path` block is used for the QAI application, which is accessed at `https://kilotest.com/qai` and listens on port 3001.
185
+
186
+ ```caddyfile
187
+ # Configuration of the Caddy web server.
188
+ # Docs: https://caddyserver.com/docs/caddyfile.
185
189
 
186
- ```javascript
187
190
  kilotest.com {
188
191
  # Enable Zstandard and Gzip compression of responses.
189
192
  encode zstd gzip
190
- # Reverse proxy all requests to localhost:3000.
193
+
194
+ # Truncate the initial /qai from the forwarded path.
195
+ handle_path /qai* {
196
+ # Forward any matching request to port 3001.
197
+ reverse_proxy localhost:3001
198
+ }
199
+
200
+ # Forward any other request to port 3000.
191
201
  reverse_proxy localhost:3000 {
192
202
  # Improve SSE latency.
193
203
  flush_interval -1
package/index.js CHANGED
@@ -69,8 +69,6 @@ const claimedPath = path.join(jobsPath, 'claimed');
69
69
  const failedPath = path.join(jobsPath, 'failed');
70
70
  const testaroAgent = process.env.TESTARO_AGENT;
71
71
  const testaroAgentPW = process.env.TESTARO_AGENT_PW;
72
- const researchAgent = process.env.RESEARCH_AGENT;
73
- const researchAgentPW = process.env.RESEARCH_AGENT_PW;
74
72
  // Values that may require alerts.
75
73
  const balancePath = path.join(__dirname, 'aiService0Balance.json');
76
74
  const WAVE_THRESHOLD = Number(process.env.WAVE_BALANCE_THRESHOLD);
@@ -339,27 +337,21 @@ const requestHandler = async (request, response) => {
339
337
  }
340
338
  // Otherwise, if the service is provision of issue statistics for a report:
341
339
  else if (service === 'reportIssues') {
342
- // Get the report identifiers from the path.
343
- const [timeStamp, jobID] = specs;
344
- const reportSpecsBad = await isHidden(timeStamp, jobID);
345
- // If the report is nonexistent or hidden:
346
- if (reportSpecsBad) {
347
- // Report this.
348
- await serveError(
349
- {message: reportSpecsBad === true ? 'Report nonexistent or hidden' : reportSpecsBad},
350
- response,
351
- false
352
- );
353
- }
354
- // Otherwise, i.e. if the report is available:
355
- else {
356
- // Get the response (potentially error) data.
357
- const responseData = await require(path.join(__dirname, 'reportIssues', 'api'))
358
- .response(specs);
359
- // Send them.
360
- setHeaders('application/json', null, 'high');
361
- response.end(JSON.stringify(responseData));
362
- }
340
+ // Get the response (potentially error) data.
341
+ const responseData = await require(path.join(__dirname, 'reportIssues', 'api'))
342
+ .response(specs);
343
+ // Send them.
344
+ setHeaders('application/json', null, 'high');
345
+ response.end(JSON.stringify(responseData));
346
+ }
347
+ // Otherwise, if the service is the description of an issue from a report:
348
+ else if (service === 'reportIssue') {
349
+ // Get the response (potentially error) data.
350
+ const responseData = await require(path.join(__dirname, 'reportIssue', 'api'))
351
+ .response(specs);
352
+ // Send them.
353
+ setHeaders('application/json', null, 'high');
354
+ response.end(JSON.stringify(responseData));
363
355
  }
364
356
  // Otherwise, i.e. if the service is invalid:
365
357
  else {
package/llms-full.txt CHANGED
@@ -95,6 +95,25 @@ The implementation is currently in an alpha phase and offers 4 tools:
95
95
  - `timeStamp`: initial segment of report identifier
96
96
  - `jobID`: final segment of report identifier
97
97
  - source of parameters: response to a `summarizeQualityOfAllTestedWebPages` request
98
+ - `describeOneIssueFromOneReport`
99
+ - method: `GET`
100
+ - purpose: provide details about one issue from one report
101
+ - path: `/api/reportIssue/{issueID}/{timeStamp}/{jobID}`
102
+ - parameters
103
+ - `issueID`: identifier of the issue
104
+ - `timeStamp`: initial segment of report identifier
105
+ - `jobID`: final segment of report identifier
106
+ - source of parameters: response to a `describeQualityOfOneWebPage` request
107
+ - `describeOneViolatorOfOneIssueFromOneReport`
108
+ - method: `GET`
109
+ - purpose: provide diagnoses about one issue exhibited by one HTML element in one report [NOT YET IMPLEMENTED]
110
+ - path: `/api/diagnoses/{issueID}/{timeStamp}/{jobID}/{catalogIndex}`
111
+ - parameters
112
+ - `issueID`: identifier of the issue
113
+ - `timeStamp`: initial segment of report identifier
114
+ - `jobID`: final segment of report identifier
115
+ - `catalogIndex`: index of the catalog entry
116
+ - source of parameters: response to a `describeOneIssueFromOneReport` request
98
117
  - `recommendQualityTestingOfOneWebPage`
99
118
  - method: `POST`
100
119
  - purpose: recommend quality testing of one web page about which no report is available
@@ -125,9 +144,9 @@ An [OpenAPI specification for Kilotest](https://kilotest.com/openapi.yaml) is av
125
144
 
126
145
  ## How to report results to users
127
146
 
128
- 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 with an implied attribution of both to Kilotest.
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.
129
148
 
130
- 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.”
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.
131
150
 
132
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 ….”
133
152
 
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # Kilotest
2
2
 
3
- Kilotest helps language models and AI platforms to give thorough, accurate and truthful answers when users ask about the front-end quality (i.e. accessibility, usability, and standards conformity) of web pages. Kilotest integrates ten independent rule engines to test web pages and report results with multipage, single-page, and element-level statistics and details.
3
+ Kilotest helps language models and AI platforms to give thorough, accurate and truthful answers when users ask about the front-end quality (i.e. accessibility, usability, and standards conformity) of web pages. Kilotest integrates ten independent rule engines to test any public web page and creates a structured report of the results. Kilotest tools can provide multipage, single-page, and element-level test results.
4
4
 
5
5
  In the API section below, all endpoints require `GET` requests except as noted.
6
6
 
@@ -8,9 +8,10 @@ There is a [detailed explanation of Kilotest and its capabilities for LLMs](http
8
8
 
9
9
  ## API
10
10
 
11
- - [Identify reports about matching web pages](/api/identifyReportsAboutMatchingWebPages)
12
- - [Summarize test results for all tested pages](/api/targets)
13
- - Provide statistics about issues reported in one job: `/api/reportIssues/{timeStamp}/{jobID}`
11
+ - [Find any available reports about a specified web page](/api/identifyReportsAboutMatchingWebPages)
12
+ - [Get summaries of all available reports](/api/targets)
13
+ - Get summarized test results from one report: `/api/reportIssues/{timeStamp}/{jobID}`
14
+ - Get details about one issue from one report: `/api/reportIssue/{issueID}/{timeStamp}/{jobID}`
14
15
  - Recommend testing of a web page: `/api/testRecForm` (requires `POST` request)
15
16
  - [OpenAPI specification for Kilotest](https://kilotest.com/openapi.yaml)
16
17
 
package/mcp.js CHANGED
@@ -12,6 +12,7 @@ const {isReportAvailable, isURL} = require('./util');
12
12
  const targetAPI = require('./target/api');
13
13
  const targetsAPI = require('./targets/api');
14
14
  const reportIssuesAPI = require('./reportIssues/api');
15
+ const reportIssueAPI = require('./reportIssue/api');
15
16
  const testRecFormAPI = require('./testRecForm/api');
16
17
 
17
18
  // FUNCTIONS
@@ -61,7 +62,7 @@ const createMCPServer = () => {
61
62
  server.registerTool(
62
63
  'describeQualityOfOneWebPage',
63
64
  {
64
- description: 'Returns data from a specified Kilotest report about issues of front-end quality (i.e. accessibility, usability, and standards conformity) of a web page. The required timeStamp and jobID parameters identify the report and are obtained from a summarizeQualityOfAllTestedWebPages response.',
65
+ description: 'Returns data from a specified Kilotest report about issues for the front-end quality (i.e. accessibility, usability, and standards conformity) of a web page. The required timeStamp and jobID parameters identify the report and are obtained from a summarizeQualityOfAllTestedWebPages response.',
65
66
  inputSchema: {
66
67
  timeStamp: z.string().describe('Report timestamp in YYMMDDTHHMM format, e.g. 260503T0432'),
67
68
  jobID: z.string().describe('Job identifier, e.g. x9z')
@@ -79,6 +80,28 @@ const createMCPServer = () => {
79
80
  return {content: [{type: 'text', text: JSON.stringify(result)}]};
80
81
  }
81
82
  );
83
+ server.registerTool(
84
+ 'describeOneIssueFromOneReport',
85
+ {
86
+ description: 'Returns data from a specified Kilotest report about one of the issues for the front-end quality (i.e. accessibility, usability, and standards conformity) of a web page. The required issueID, timeStamp, and jobID parameters identify the issue and the report and are obtained from a describeQualityOfOneWebPage response.',
87
+ inputSchema: {
88
+ issueID: z.string().describe('Issue identifier, e.g. contrastPoor'),
89
+ timeStamp: z.string().describe('Report timestamp in YYMMDDTHHMM format, e.g. 260503T0432'),
90
+ jobID: z.string().describe('Job identifier, e.g. x9z')
91
+ },
92
+ annotations: {
93
+ title: 'Describe one issue from one report',
94
+ readOnlyHint: true,
95
+ idempotentHint: true,
96
+ destructiveHint: false,
97
+ openWorldHint: false
98
+ }
99
+ },
100
+ async ({issueID, timeStamp, jobID}) => {
101
+ const result = await reportIssueAPI.response([issueID, timeStamp, jobID]);
102
+ return {content: [{type: 'text', text: JSON.stringify(result)}]};
103
+ }
104
+ );
82
105
  server.registerTool(
83
106
  'recommendQualityTestingOfOneWebPage',
84
107
  {
package/openapi.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  openapi: 3.1.0
2
2
  info:
3
3
  title: Kilotest Agent API
4
- description: Kilotest tests web pages for front-end quality (i.e. accessibility, usability, and standards conformity) using an ensemble of ten independent rule engines that employ rule-based and machine-learning-based methods. This API enables AI agents to recommend web pages for testing, discover available test reports, and retrieve data from reports. For background on Kilotest and the advantages of ensemble testing, visit https://kilotest.com.
4
+ description: Kilotest tests web pages for front-end quality (i.e. accessibility, usability, and standards conformity) using an ensemble of ten independent rule engines that employ rule-based and machine-learning-based methods. This API enables AI agents to recommend web pages for testing, discover available test reports, and retrieve data from reports. Each report describes results of one job that tested one web page. For background on Kilotest and the advantages of ensemble testing, visit https://kilotest.com.
5
5
  version: 1.0.0
6
6
  contact:
7
7
  name: Kilotest
@@ -101,9 +101,9 @@ paths:
101
101
 
102
102
  /api/reportIssue/{issueID}/{timeStamp}/{jobID}:
103
103
  get:
104
- operationId: describeHTMLElementsHavingOneQualityIssue
105
- summary: Describe HTML elements having one quality issue (NOT YET IMPLEMENTED)
106
- description: Get issue-specific data from a specified report about the front-end quality (i.e. accessibility, usability, and standards conformity) of a web page. The data describe the issue, all of the HTML elements of the page that have the issue, and, for each such element, URLs for retrieving diagnoses by rule engines of the issue on the element. NOT YET IMPLEMENTED.
104
+ operationId: describeOneIssueFromOneReport
105
+ summary: Describe one issue from one report
106
+ description: Get issue-specific data from a specified report about the front-end quality (i.e. accessibility, usability, and standards conformity) of a web page. The data describe the issue, all of the HTML elements of the page that have the issue, and, for each such element, URLs for retrieving diagnoses by rule engines of the issue on the element.
107
107
  parameters:
108
108
  - name: issueID
109
109
  in: path
@@ -118,14 +118,18 @@ paths:
118
118
  required: true
119
119
  schema:
120
120
  type: string
121
+ examples:
122
+ - 20271124T1647
121
123
  - name: jobID
122
124
  in: path
123
125
  required: true
124
126
  schema:
125
127
  type: string
128
+ examples:
129
+ - x58
126
130
  responses:
127
131
  '200':
128
- description: Details about the specified issue in the specified report
132
+ description: Details about the specified issue from the specified report
129
133
  content:
130
134
  application/json:
131
135
  schema:
@@ -440,8 +444,139 @@ components:
440
444
  issues reported:
441
445
  $ref: '#/components/schemas/IssuesByPriority'
442
446
 
447
+ HTMLElementDetail:
448
+ type: object
449
+ description: Details about an HTML element reported as exhibiting an issue.
450
+ properties:
451
+ identifier:
452
+ type: string
453
+ description: Unique identifier of the element in the catalog of the report.
454
+ tag name:
455
+ type: string
456
+ examples:
457
+ - IMG
458
+ id attribute:
459
+ type: string
460
+ description: Value of the element's id attribute, if any.
461
+ start tag:
462
+ type: string
463
+ description: The opening tag of the element, including attributes.
464
+ inner text:
465
+ type: string
466
+ description: First and last lines of the inner text, if any, of the element.
467
+ inner text usable as a text fragment for linking:
468
+ type: boolean
469
+ description: Whether the inner text can be used as a text fragment for URL linking.
470
+ x, y, width, height in pixels:
471
+ type: array
472
+ items:
473
+ type: string
474
+ description: Bounding box coordinates and dimensions of the element.
475
+ XPath:
476
+ type: string
477
+ description: XPath of the element in the document.
478
+
443
479
  ReportIssueResponse:
444
480
  allOf:
445
481
  - $ref: '#/components/schemas/CommonResponseFields'
446
482
  - type: object
447
- description: Element-level detail for a specific issue. Schema to be completed once the tier-3 service is implemented.
483
+ properties:
484
+ report:
485
+ type: object
486
+ properties:
487
+ identifier:
488
+ type: string
489
+ examples:
490
+ - 20280216T1353-4r9
491
+ creation date:
492
+ type: string
493
+ format: date-time
494
+ days since the creation date:
495
+ type: integer
496
+ tested web page:
497
+ type: object
498
+ properties:
499
+ description:
500
+ type: string
501
+ examples:
502
+ - ARQ Space Exploration Corporation
503
+ - Edlir Velaj PF
504
+ URL:
505
+ type: string
506
+ format: uri
507
+ examples:
508
+ - https://tiendalibros.com/
509
+ rule engines that tried to test the page:
510
+ type: array
511
+ items:
512
+ $ref: '#/components/schemas/RuleEngineInfo'
513
+ rule engines that were unable to test the page:
514
+ type: array
515
+ items:
516
+ $ref: '#/components/schemas/RuleEngineFailure'
517
+ rule engines that reported issues:
518
+ $ref: '#/components/schemas/RuleEnginesSummary'
519
+ number of issues reported:
520
+ type: object
521
+ properties:
522
+ total:
523
+ type: integer
524
+ by priority:
525
+ type: object
526
+ properties:
527
+ highest priority:
528
+ type: integer
529
+ high priority:
530
+ type: integer
531
+ low priority:
532
+ type: integer
533
+ lowest priority:
534
+ type: integer
535
+ number of HTML elements reported as exhibiting issues:
536
+ type: integer
537
+ level of the issue:
538
+ type: integer
539
+ enum:
540
+ - 1
541
+ - 2
542
+ - 3
543
+ - 4
544
+ description: Priority level of the issue (4 = highest, 1 = lowest).
545
+ facts about the issue:
546
+ type: object
547
+ description: Details about the specified issue and the HTML elements exhibiting it.
548
+ properties:
549
+ identifier:
550
+ type: string
551
+ examples:
552
+ - imageNoText
553
+ summary:
554
+ type: string
555
+ description: Brief natural-language label for the issue.
556
+ examples:
557
+ - image not named
558
+ related WCAG 2.2 standard:
559
+ type: object
560
+ description: The WCAG 2.2 success criterion or guideline most closely related to the issue.
561
+ properties:
562
+ layer:
563
+ type: string
564
+ enum:
565
+ - guideline
566
+ - success criterion
567
+ 'numeric identifier':
568
+ type: string
569
+ pattern: '^\d\.\d(\.\d+)?$'
570
+ examples:
571
+ - 1.1.1
572
+ impact on a user:
573
+ type: string
574
+ description: How this issue is likely to affect users.
575
+ rule engines reporting the issue:
576
+ $ref: '#/components/schemas/RuleEnginesSummary'
577
+ number of HTML elements reported as exhibiting the issue:
578
+ type: integer
579
+ HTML elements reported as exhibiting the issue:
580
+ type: array
581
+ items:
582
+ $ref: '#/components/schemas/HTMLElementDetail'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jrpool/kilotest",
3
- "version": "34.0.1",
3
+ "version": "35.0.1",
4
4
  "description": "An ensemble testing service with a focus on accessibility",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -36,5 +36,8 @@
36
36
  "@eslint/markdown": "^7.5.1",
37
37
  "eslint": "^10.0.3",
38
38
  "globals": "^17.4.0"
39
+ },
40
+ "allowScripts": {
41
+ "fsevents@2.3.2": true
39
42
  }
40
43
  }
@@ -0,0 +1,164 @@
1
+ /*
2
+ api.js
3
+ Responds to the report-issue request.
4
+ */
5
+
6
+ // IMPORTS
7
+
8
+ const {getData} = require('../reportIssues/util');
9
+ const {
10
+ getDateTime,
11
+ getNowStamp,
12
+ getRandomString,
13
+ getReport,
14
+ getToolsFacts,
15
+ isHidden,
16
+ tools
17
+ } = require('../util');
18
+
19
+ // FUNCTIONS
20
+
21
+ // Gets facts about an issue.
22
+ const getIssueFacts = async (timeStamp, jobID, issue) => {
23
+ const {issueID, reporterCount, reporters, summary, violatorCount, wcag, why} = issue;
24
+ const wcagType = wcag.length === 3 ? 'guideline' : 'success criterion';
25
+ // Get the report.
26
+ const report = await getReport(timeStamp, jobID);
27
+ const violatorIndexSet = new Set();
28
+ const {acts, catalog} = report;
29
+ // For each act of the report:
30
+ acts.forEach(act => {
31
+ // If it is a test act:
32
+ if (act.type === 'test') {
33
+ const {result} = act;
34
+ const instances = result?.standardResult?.instances ?? [];
35
+ // For each standard instance of the act:
36
+ instances.forEach(instance => {
37
+ const {catalogIndex} = instance;
38
+ // If its issue is the issue to be described and the instance has a catalog index:
39
+ if (instance.issueID === issueID && catalogIndex) {
40
+ // Ensure that the violator is in the temporary data.
41
+ violatorIndexSet.add(catalogIndex);
42
+ }
43
+ });
44
+ }
45
+ });
46
+ return {
47
+ identifier: issueID,
48
+ summary,
49
+ 'related WCAG 2.2 standard': {
50
+ layer: wcagType,
51
+ 'numeric identifier': wcag
52
+ },
53
+ 'impact on a user': why,
54
+ 'rule engines reporting the issue': {
55
+ 'number': reporterCount,
56
+ 'names': reporters.map(tool => tool.toolName)
57
+ },
58
+ 'number of HTML elements reported as exhibiting the issue': violatorCount,
59
+ 'HTML elements reported as exhibiting the issue': Array.from(violatorIndexSet).map(index => ({
60
+ identifier: String(index),
61
+ 'tag name': catalog[index].tagName,
62
+ 'id attribute': catalog[index].id,
63
+ 'start tag': catalog[index].startTag,
64
+ 'inner text': catalog[index].text,
65
+ 'inner text usable as a text fragment for linking': catalog[index].textLinkable,
66
+ 'x, y, width, height in pixels': catalog[index].boxID.split(':'),
67
+ 'XPath': catalog[index].pathID
68
+ }))
69
+ };
70
+ };
71
+ // Returns a response to a report-issue request.
72
+ exports.response = async args => {
73
+ const [issueID, timeStamp, jobID] = args;
74
+ const reportIsHidden = await isHidden(timeStamp, jobID);
75
+ // If the report is not available:
76
+ if (reportIsHidden) {
77
+ // Return this.
78
+ return {
79
+ status: 'error',
80
+ message: 'Report not available'
81
+ };
82
+ }
83
+ // Otherwise, i.e. if the report is available, get data on the report and its issues.
84
+ const data = await getData(timeStamp, jobID);
85
+ const {pageData, issuesData} = data;
86
+ const {what, url, daysAgo} = pageData;
87
+ const {issueCount, issues, preventions, reporterCount, reporters, violatorCount} = issuesData;
88
+ let issue;
89
+ // Get the level of and the data on the issue.
90
+ const issueLevel = [4, 3, 2, 1].find(level => {
91
+ issue = issues[level].find(issue => issue.issueID === issueID);
92
+ return issue;
93
+ });
94
+ // If the issue was not one of those in the report:
95
+ if (! issue) {
96
+ // Return this.
97
+ return {
98
+ status: 'error',
99
+ message: 'Issue not found in the report'
100
+ };
101
+ }
102
+ const preventedTools = Object.entries(preventions).map(prevention => ({
103
+ name: tools[prevention[0]][0],
104
+ 'reason for failure': prevention[1]
105
+ }));
106
+ const thisHost = process.env.THIS_KILOTEST_HOST;
107
+ // Get a response.
108
+ const content = {
109
+ summary: `This document fulfills a request made by a language model to a Kilotest tool. The model requested data, drawn from a Kilotest report, about one of the issues for the front-end quality (i.e. accessibility, usability, and standard-conformity) of a web page. Kilotest, with the help of Testaro, Testilo, and an ensemble of ten rule engines, performs tests on web pages, using a combination of rule- and machine-learning-based methods, and produces reports. Kilotest exposes several API endpoints to recommend web pages for testing and to obtain information from Kilotest reports. To learn more about Kilotest and the advangages of testing with an ensemble of rule engines, visit the deployed instance of Kilotest (${process.env.DEPLOYED_KILOTEST_HOST}), which contains an introduction on its home page and a tutorial.`,
110
+ 'tool collection name': 'Kilotest',
111
+ 'tool name': 'describeOneIssueFromOneReport',
112
+ request: {
113
+ 'type of request': {
114
+ identifier: 'reportIssue',
115
+ description: 'Describe one issue from one repeort.'
116
+ },
117
+ method: 'GET',
118
+ URLs: {
119
+ 'URL of your request': `${thisHost}/api/reportIssue/${issueID}/${timeStamp}/${jobID}`,
120
+ 'equivalent URL for humans': `${thisHost}/reportIssue.html/${issueID}/${timeStamp}/${jobID}`
121
+ },
122
+ 'closest ancestor request': {
123
+ identifier: 'describeQualityOfOneWebPage',
124
+ description: 'Describe the quality of one web page.',
125
+ URLs: {
126
+ 'for you': `${thisHost}/api/reportIssues`,
127
+ 'for humans': `${thisHost}/reportIssues.html`
128
+ }
129
+ }
130
+ },
131
+ 'response metadata': {
132
+ identifier: `${getNowStamp()}-${getRandomString(3)}`,
133
+ 'date and time': new Date().toISOString()
134
+ },
135
+ report: {
136
+ identifier: `${timeStamp}-${jobID}`,
137
+ 'creation date': getDateTime(timeStamp),
138
+ 'days since the creation date': daysAgo
139
+ },
140
+ 'tested web page': {
141
+ description: what,
142
+ URL: url
143
+ },
144
+ 'rule engines that tried to test the page': getToolsFacts(Object.keys(tools)),
145
+ 'rule engines that were unable to test the page': preventedTools,
146
+ 'rule engines that reported issues': {
147
+ number: reporterCount,
148
+ names: reporters.map(tool => tool.toolName)
149
+ },
150
+ 'number of issues reported': {
151
+ total: issueCount,
152
+ 'by priority': {
153
+ 'highest priority': issues[4].length,
154
+ 'high priority': issues[3].length,
155
+ 'low priority': issues[2].length,
156
+ 'lowest priority': issues[1].length
157
+ }
158
+ },
159
+ 'number of HTML elements reported as exhibiting issues': violatorCount,
160
+ 'level of the issue': issueLevel,
161
+ 'facts about the issue': await getIssueFacts(timeStamp, jobID, issue)
162
+ };
163
+ return content;
164
+ };
package/researchAgent.js CHANGED
@@ -61,7 +61,7 @@ const getContent = async response => {
61
61
  resolve(content);
62
62
  }
63
63
  // If it is not JSON:
64
- catch (error) {
64
+ catch {
65
65
  // Return this.
66
66
  resolve({error: `Response content not JSON (${contentString})`});
67
67
  }
@@ -139,9 +139,7 @@ const requestService = async () => {
139
139
  if (content.error) {
140
140
  return;
141
141
  }
142
- console.log(
143
- '======================\nRequest 4: Summarize the issues in a report'
144
- );
142
+ console.log('======================\nRequest 4: Summarize the issues in a report');
145
143
  [timeStamp, jobID] = report.identifier?.split('-') ?? ['', ''];
146
144
  if (! (timeStamp && jobID)) {
147
145
  return;
@@ -152,7 +150,26 @@ const requestService = async () => {
152
150
  if (content.error) {
153
151
  return;
154
152
  }
155
- console.log('======================\nRequest 5: Make a permitted test recommendation');
153
+ console.log('======================\nRequest 5: Describe one issue from one report');
154
+ if (content['number of HTML elements reported as exhibiting issues'] === 0) {
155
+ console.log('reportIssue request cannot be submitted, because no issues were reported');
156
+ }
157
+ else {
158
+ // Get the issue IDs.
159
+ const issueIDs = Object
160
+ .values(content['issues reported'])
161
+ .map(levelIssues => levelIssues.map(issue => issue.identifier))
162
+ .flat();
163
+ // Choose one at random.
164
+ const issueID = issueIDs[Math.floor(Math.random() * issueIDs.length)];
165
+ method = 'GET';
166
+ path = `/api/reportIssue/${issueID}/${timeStamp}/${jobID}`;
167
+ content = await submitRequest(results, path, method);
168
+ if (content.error) {
169
+ return;
170
+ }
171
+ }
172
+ console.log('======================\nRequest 6: Make a permitted test recommendation');
156
173
  method = 'POST';
157
174
  path = '/api/testRecForm';
158
175
  content = await submitRequest(results, path, method, {
@@ -163,8 +180,8 @@ const requestService = async () => {
163
180
  if (content.error) {
164
181
  return;
165
182
  }
166
- console.log('======================\nRequest 6: Make an illicit test recommendation');
167
- content = await submitRequest(results, path, method, {
183
+ console.log('======================\nRequest 7: Make an illicit test recommendation');
184
+ await submitRequest(results, path, method, {
168
185
  'description of the web page': description,
169
186
  'URL of the web page': url,
170
187
  'reason for testing the web page': 'Just testing'