@jrpool/kilotest 38.0.0 → 39.0.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.
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 +201 -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 +580 -539
  51. package/reportIssue/api.js +0 -183
  52. package/reportIssues/util.js +0 -138
  53. package/reportList/api.js +0 -83
  54. package/reportSummary/api.js +0 -181
  55. package/researchAgent.js +0 -206
  56. package/target/api.js +0 -126
  57. package/testRecForm/api.js +0 -55
package/index.js CHANGED
@@ -10,9 +10,9 @@ require('dotenv').config({quiet: true});
10
10
  // IMPORTS
11
11
  const {
12
12
  annotateReport,
13
+ createLock,
13
14
  getJobNames,
14
15
  getJSON,
15
- getLogPath,
16
16
  getObject,
17
17
  getPOSTData,
18
18
  getReport,
@@ -22,9 +22,9 @@ const {
22
22
  isReportAvailable,
23
23
  isTimeStamp,
24
24
  isJobID,
25
- isURL,
26
25
  jobsPath,
27
- logsPath,
26
+ makeReportsExtract,
27
+ recsLock,
28
28
  reportsPath,
29
29
  ruleIDs
30
30
  } = require('./util');
@@ -71,7 +71,8 @@ const failedPath = path.join(jobsPath, 'failed');
71
71
  const testaroAgent = process.env.TESTARO_AGENT;
72
72
  const testaroAgentPW = process.env.TESTARO_AGENT_PW;
73
73
  // Values that may require alerts.
74
- const balancePath = path.join(__dirname, 'aiService0Balance.json');
74
+ const balancePath = path.join(__dirname, 'ai0Balance.json');
75
+ const jobLock = createLock();
75
76
  const WAVE_THRESHOLD = Number(process.env.WAVE_BALANCE_THRESHOLD);
76
77
  const AI_SERVICE0_THRESHOLD = Number(process.env.AI_SERVICE0_BALANCE_THRESHOLD);
77
78
  const AI_MODEL0_INPUT_PRICE = Number(process.env.AI_MODEL0_INPUT_PRICE);
@@ -83,7 +84,7 @@ const AI_MODEL0_OUTPUT_PRICE = Number(process.env.AI_MODEL0_OUTPUT_PRICE);
83
84
  const serveError = async (error, response, isHumanUser = true) => {
84
85
  const errorLines = Object.entries(error).map(pair => `${pair[0]}: ${pair[1]}`);
85
86
  console.log(errorLines.join('\n') || 'ERROR');
86
- if (! response.writableEnded) {
87
+ if (!response.writableEnded) {
87
88
  response.statusCode = 400;
88
89
  // If the request is from a human user:
89
90
  if (isHumanUser) {
@@ -152,6 +153,7 @@ const checkBalancesForAlerts = async report => {
152
153
  if (typeof newBalance === 'number') {
153
154
  // Update the recorded balance.
154
155
  await fs.writeFile(balancePath, getJSON({balance: newBalance}));
156
+ console.log(`Estimated new AI Service 0 balance: $${newBalance.toFixed(2)}`);
155
157
  // If it is nearing exhaustion:
156
158
  if (newBalance < AI_SERVICE0_THRESHOLD) {
157
159
  // Alert a manager.
@@ -186,6 +188,71 @@ const getAbuseError = (request, reason) => {
186
188
  time: new Date().toISOString()
187
189
  };
188
190
  };
191
+ // Processes a job request from a Testaro agent.
192
+ const processJobRequest = async (request, response, agentID) => jobLock(async () => {
193
+ let clean = true;
194
+ const messageStart = `Testaro agent ${agentID} requested a job, `;
195
+ const jobNames = await getJobNames();
196
+ const claimedJobNames = jobNames.claimed;
197
+ // For each claimed job:
198
+ for (const jobName of claimedJobNames) {
199
+ const job = await getObject(path.join(jobsPath, 'claimed', jobName));
200
+ const {id, sources} = job;
201
+ const {agent} = sources;
202
+ // If its assignee is the agent:
203
+ if (agent === agentID) {
204
+ const messageEnd = `but has not completed job ${id}`;
205
+ // Report this.
206
+ await serveError({message: `${messageStart}${messageEnd}`}, response, false);
207
+ // Reclassify the job as failed.
208
+ await fs.rename(
209
+ path.join(claimedPath, jobName), path.join(failedPath, jobName)
210
+ );
211
+ clean = false;
212
+ // Stop checking claimed jobs.
213
+ break;
214
+ }
215
+ }
216
+ // If no aborted-job error was found for the agent:
217
+ if (clean) {
218
+ const queuedJobNames = jobNames.queue;
219
+ // If any jobs are queued:
220
+ if (queuedJobNames.length) {
221
+ const oldestJobName = queuedJobNames[0];
222
+ // Get the first one.
223
+ const firstJob = await getObject(path.join(queuePath, oldestJobName));
224
+ // Add the agent ID to the job.
225
+ firstJob.sources.agent = agentID;
226
+ console.log(
227
+ `Job ${firstJob.id} (${firstJob.target.what}) is being sent to the agent.`
228
+ );
229
+ // Assign the job to the agent.
230
+ response.writeHead(200, {
231
+ 'content-type': 'application/json; charset=utf-8'
232
+ });
233
+ response.end(JSON.stringify(firstJob));
234
+ const messageEnd
235
+ = `and job ${firstJob.id} (${firstJob.target.what}) was assigned to the agent`;
236
+ console.log(`${messageStart}${messageEnd}`);
237
+ // Save the job in the claimed-jobs directory.
238
+ await fs.writeFile(
239
+ path.join(claimedPath, oldestJobName), getJSON(firstJob)
240
+ );
241
+ // Delete it from the queue.
242
+ await fs.unlink(path.join(queuePath, oldestJobName));
243
+ }
244
+ // Otherwise, i.e. if no jobs are queued:
245
+ else {
246
+ response.writeHead(200, {
247
+ 'content-type': 'application/json; charset=utf-8'
248
+ });
249
+ // Send a no-jobs response to the agent.
250
+ response.end(JSON.stringify({}));
251
+ const messageEnd = 'but no job was in the queue';
252
+ console.log(`${messageStart}${messageEnd}`);
253
+ }
254
+ }
255
+ });
189
256
  // Handles a request.
190
257
  const requestHandler = async (request, response) => {
191
258
  // Sets response headers.
@@ -319,7 +386,7 @@ const requestHandler = async (request, response) => {
319
386
  const topic = pageName.slice(0, -5);
320
387
  // If the page can be generated:
321
388
  if (answer[topic]) {
322
- setHeaders('text/html', `${pathname}${search}`, 'ultra');
389
+ setHeaders('text/html', pathname, 'ultra');
323
390
  // Get the answer data.
324
391
  const answerData = await answer[topic](pathTail, search);
325
392
  // If they are valid:
@@ -341,37 +408,56 @@ const requestHandler = async (request, response) => {
341
408
  }
342
409
  // Otherwise, if it is for an API service:
343
410
  else if (pageName === 'api') {
344
- const [service, ... specs] = pathTail.split('/');
345
- // If the service is provision of a list of all available reports:
346
- if (service === 'reportList') {
347
- // Get the response (potentially error) data.
348
- const responseData = await require(path.join(__dirname, 'reportList', 'api')).response(specs);
349
- // Send them.
411
+ const [service, ...specs] = pathTail.split('/');
412
+ // If the service lists the available reports:
413
+ if (service === 'listReports') {
414
+ // Get the response body.
415
+ const responseBody = await require(path.join(__dirname, 'api', 'listReports'))
416
+ .response(specs);
417
+ // Send it.
350
418
  setHeaders('application/json', null, 'ultra');
351
- response.end(JSON.stringify(responseData));
419
+ response.end(JSON.stringify(responseBody));
352
420
  }
353
- // Otherwise, if the service is provision of a report summary:
354
- else if (service === 'reportSummary') {
355
- // Get the response (potentially error) data.
356
- const responseData = await require(path.join(__dirname, 'reportSummary', 'api'))
421
+ // Otherwise, if the service lists the issues in a report:
422
+ else if (service === 'listIssues') {
423
+ // Get the response body.
424
+ const responseBody = await require(path.join(__dirname, 'api', 'listIssues'))
357
425
  .response(specs);
358
- // Send them.
426
+ // Send it.
359
427
  setHeaders('application/json', null, 'high');
360
- response.end(JSON.stringify(responseData));
428
+ response.end(JSON.stringify(responseBody));
361
429
  }
362
- // Otherwise, if the service is the description of an issue from a report:
363
- else if (service === 'reportIssue') {
364
- // Get the response (potentially error) data.
365
- const responseData = await require(path.join(__dirname, 'reportIssue', 'api'))
430
+ // Otherwise, if the service lists the violators of an issue in a report:
431
+ else if (service === 'listViolators') {
432
+ // Get the response body.
433
+ const responseBody = await require(path.join(__dirname, 'api', 'listViolators'))
366
434
  .response(specs);
367
- // Send them.
435
+ // Send it.
368
436
  setHeaders('application/json', null, 'high');
369
- response.end(JSON.stringify(responseData));
437
+ response.end(JSON.stringify(responseBody));
438
+ }
439
+ // Otherwise, if the service lists the diagnoses of a violation of an issue in a report:
440
+ else if (service === 'listDiagnoses') {
441
+ // Get the response body.
442
+ const responseBody = await require(path.join(__dirname, 'api', 'listDiagnoses'))
443
+ .response(specs);
444
+ // Send it.
445
+ setHeaders('application/json', null, 'high');
446
+ response.end(JSON.stringify(responseBody));
447
+ }
448
+ // Otherwise, if the service serves a report:
449
+ else if (service === 'getReport') {
450
+ // Get the response body.
451
+ const responseBody = await require(path.join(__dirname, 'api', 'getReport'))
452
+ .response(specs);
453
+ // Send it.
454
+ setHeaders('application/json', null, 'low');
455
+ response.end(JSON.stringify(responseBody));
370
456
  }
371
457
  // Otherwise, i.e. if the service is invalid:
372
458
  else {
373
459
  // Report this.
374
- await serveError({message: 'ERROR: Invalid service request'}, response, false);
460
+ await serveError({message: 'Invalid service request'}, response, false);
375
461
  }
376
462
  }
377
463
  // Otherwise, if it is for a tutorial image:
@@ -421,7 +507,7 @@ const requestHandler = async (request, response) => {
421
507
  else {
422
508
  // Report the error.
423
509
  await serveError(
424
- {message: `ERROR: Invalid GET request (${pathname}${search})`}, response, true
510
+ {message: `ERROR: Invalid GET request (${pathname})`}, response, true
425
511
  );
426
512
  }
427
513
  }
@@ -435,36 +521,8 @@ const requestHandler = async (request, response) => {
435
521
  else {
436
522
  // Get the data from the request body.
437
523
  const postData = await getPOSTData(request);
438
- // If the request is a retest recommendation:
439
- if (pageName === 'retestRec.html') {
440
- const {why} = postData;
441
- const [timeStamp, jobID] = pathTail.split('/');
442
- // If the request is valid:
443
- if (isTimeStamp(timeStamp) && isJobID(jobID) && why) {
444
- // Serve response headers.
445
- setHeaders('text/html', `${pathname}${search}`, 'ultra');
446
- // Get the answer data.
447
- const answerData = await require(path.join(__dirname, 'retestRec', 'index'))
448
- .answer(pathTail, why);
449
- // If they are valid:
450
- if (answerData.status === 'ok') {
451
- // Serve the answer page.
452
- response.end(answerData.answerPage);
453
- }
454
- // Otherwise, i.e. if they are invalid:
455
- else {
456
- // Report the error.
457
- await serveError({message: answerData.message}, response, true);
458
- }
459
- }
460
- // Otherwise, i.e. if the request is invalid:
461
- else {
462
- // Report the error.
463
- await serveError({message: 'ERROR: Invalid retest recommendation'}, response, true);
464
- }
465
- }
466
- // Otherwise, if it is a test recommendation:
467
- else if (pageName === 'testRec.html') {
524
+ // If the request is a test recommendation:
525
+ if (pageName === 'testRec.html') {
468
526
  const {what, url, why} = postData;
469
527
  // If the request is valid:
470
528
  if (what && url.startsWith('https://') && why) {
@@ -476,7 +534,7 @@ const requestHandler = async (request, response) => {
476
534
  // Otherwise, i.e. if no report on the page is available:
477
535
  else {
478
536
  // Serve headers for a response.
479
- setHeaders('text/html', `${pathname}${search}`, 'ultra');
537
+ setHeaders('text/html', pathname, 'ultra');
480
538
  // Get the answer data.
481
539
  const answerData = await require(path.join(__dirname, 'testRec', 'index'))
482
540
  .answer(what, url, why);
@@ -498,6 +556,34 @@ const requestHandler = async (request, response) => {
498
556
  await serveError({message: 'ERROR: Invalid test recommendation'}, response, true);
499
557
  }
500
558
  }
559
+ // Otherwise, if it is a retest recommendation:
560
+ else if (pageName === 'retestRec.html') {
561
+ const {why} = postData;
562
+ const [timeStamp, jobID] = pathTail.split('/');
563
+ // If the request is valid:
564
+ if (isTimeStamp(timeStamp) && isJobID(jobID) && why) {
565
+ // Serve response headers.
566
+ setHeaders('text/html', pathname, 'ultra');
567
+ // Get the answer data.
568
+ const answerData = await require(path.join(__dirname, 'retestRec', 'index'))
569
+ .answer(pathTail, why);
570
+ // If they are valid:
571
+ if (answerData.status === 'ok') {
572
+ // Serve the answer page.
573
+ response.end(answerData.answerPage);
574
+ }
575
+ // Otherwise, i.e. if they are invalid:
576
+ else {
577
+ // Report the error.
578
+ await serveError({message: answerData.message}, response, true);
579
+ }
580
+ }
581
+ // Otherwise, i.e. if the request is invalid:
582
+ else {
583
+ // Report the error.
584
+ await serveError({message: 'ERROR: Invalid retest recommendation'}, response, true);
585
+ }
586
+ }
501
587
  // Otherwise, if it is an action on a test or retest recommendation:
502
588
  else if (pageName === 'recAction.html') {
503
589
  const {target, authCode} = postData;
@@ -509,8 +595,8 @@ const requestHandler = async (request, response) => {
509
595
  // If the request is an approval:
510
596
  if (what) {
511
597
  // Set a location header for a response.
512
- response.setHeader('content-location', `${pathname}${search}`);
513
- // Get the answer data about the remaining recommendations.
598
+ response.setHeader('content-location', pathname);
599
+ // Process the approval and get the answer data about the remaining recommendations.
514
600
  const answerData = await require(path.join(__dirname, 'testOrder', 'index'))
515
601
  .answer(url, what, authCode);
516
602
  // If the answer data are valid:
@@ -526,12 +612,15 @@ const requestHandler = async (request, response) => {
526
612
  }
527
613
  // Otherwise, i.e. if it is a rejection:
528
614
  else {
529
- // Get the recommendations.
530
- const recs = await getRecs();
531
- // Delete the rejected URL.
532
- delete recs[url];
533
- // Save the revised recommendations.
534
- await fs.writeFile(path.join(__dirname, 'jobs', 'recs.json'), getJSON(recs));
615
+ // Isolate this revision.
616
+ await recsLock(async () => {
617
+ // Get the recommendations.
618
+ const recs = await getRecs();
619
+ // Delete the rejected URL.
620
+ delete recs[url];
621
+ // Save the revised recommendations.
622
+ await fs.writeFile(path.join(jobsPath, 'recs.json'), getJSON(recs));
623
+ });
535
624
  // Set a location header for a response.
536
625
  response.setHeader('content-location', '/recActionForm.html');
537
626
  // Get the answer data.
@@ -550,7 +639,7 @@ const requestHandler = async (request, response) => {
550
639
  else if (pageName === 'reannotate.html') {
551
640
  const {authCode} = postData;
552
641
  // Set headers for a response.
553
- setHeaders('text/html', `${pathname}${search}`, 'ultra');
642
+ setHeaders('text/html', pathname, 'ultra');
554
643
  // Get the answer data.
555
644
  const answerData = await require(path.join(__dirname, 'reannotate', 'index'))
556
645
  .answer(authCode);
@@ -569,7 +658,7 @@ const requestHandler = async (request, response) => {
569
658
  else if (pageName === 'wcagRenew.html') {
570
659
  const {authCode} = postData;
571
660
  // Set headers for a response.
572
- setHeaders('text/html', `${pathname}${search}`, 'low');
661
+ setHeaders('text/html', pathname, 'low');
573
662
  // Get the answer data.
574
663
  const answerData = await require(path.join(__dirname, 'wcagRenew', 'index'))
575
664
  .answer(authCode);
@@ -595,66 +684,8 @@ const requestHandler = async (request, response) => {
595
684
  const service = segments[1];
596
685
  // If the service is job assignment:
597
686
  if (service === 'job') {
598
- let clean = true;
599
- const messageStart = `Testaro agent ${agentID} requested a job, `;
600
- const jobNames = await getJobNames();
601
- const claimedJobNames = jobNames.claimed;
602
- // For each claimed job:
603
- for (const jobName of claimedJobNames) {
604
- const job = await getObject(path.join(jobsPath, 'claimed', jobName));
605
- const {id, sources} = job;
606
- const {agent} = sources;
607
- // If its assignee is the agent:
608
- if (agent === agentID) {
609
- const messageEnd = `but has not completed job ${id}`;
610
- // Report this.
611
- await serveError({message: `${messageStart}${messageEnd}`}, response, false);
612
- // Reclassify the job as failed.
613
- await fs.rename(
614
- path.join(claimedPath, jobName), path.join(failedPath, jobName)
615
- );
616
- clean = false;
617
- // Stop checking claimed jobs.
618
- break;
619
- }
620
- }
621
- // If no aborted-job error was found for the agent:
622
- if (clean) {
623
- const queuedJobNames = jobNames.queue;
624
- // If any jobs are queued:
625
- if (queuedJobNames.length) {
626
- const oldestJobName = queuedJobNames[0];
627
- // Get the first one.
628
- const firstJob = await getObject(path.join(queuePath, oldestJobName));
629
- // Add the agent ID to the job.
630
- firstJob.sources.agent = agentID;
631
- console.log(
632
- `Job ${firstJob.id} (${firstJob.target.what}) is being sent to the agent.`
633
- );
634
- // Assign the job to the agent.
635
- response.writeHead(200, {
636
- 'content-type': 'application/json; charset=utf-8'
637
- });
638
- response.end(JSON.stringify(firstJob));
639
- const messageEnd
640
- = `and job ${firstJob.id} (${firstJob.target.what}) was assigned to the agent`;
641
- console.log(`${messageStart}${messageEnd}`);
642
- // Move the job from the queue to the claimed-jobs directory.
643
- await fs.rename(
644
- path.join(queuePath, oldestJobName), path.join(claimedPath, oldestJobName)
645
- );
646
- }
647
- // Otherwise, i.e. if no jobs are queued:
648
- else {
649
- response.writeHead(200, {
650
- 'content-type': 'application/json; charset=utf-8'
651
- });
652
- // Send a no-jobs response to the agent.
653
- response.end(JSON.stringify({}));
654
- const messageEnd = 'but no job was in the queue';
655
- console.log(`${messageStart}${messageEnd}`);
656
- }
657
- }
687
+ // Process the job request as a transaction.
688
+ await processJobRequest(request, response, agentID);
658
689
  }
659
690
  // Otherwise, if the service is report acquisition:
660
691
  else if (service === 'report') {
@@ -664,91 +695,73 @@ const requestHandler = async (request, response) => {
664
695
  const [timeStamp, jobID] = id?.split('-') ?? ['', ''];
665
696
  // If the request is valid:
666
697
  if (id && isTimeStamp(timeStamp) && isJobID(jobID) && what && url) {
667
- // Acknowledge receipt.
668
- response.setHeader('content-type', 'application/json; charset=utf-8');
669
- response.end(JSON.stringify({status: 'ok'}));
670
- console.log(`Testaro report ${id} was received from Testaro agent ${agentID}`);
671
698
  const [timeStamp, jobID] = id.split('-');
672
699
  // Save the report.
673
700
  await fs.writeFile(getReportPath(timeStamp, jobID), getJSON(report));
674
- // Create a log for the report.
675
- const log = {
676
- what,
677
- url
678
- };
679
- // Save the log.
680
- await fs.writeFile(getLogPath(timeStamp, jobID), getJSON(log));
681
- // Annotate the report and mark it as annotated in the log.
701
+ // Update the data on the available reports.
702
+ await makeReportsExtract();
703
+ // Annotate the report.
682
704
  await annotateReport(ruleIDs, timeStamp, jobID);
683
- console.log(`Testaro report ${id} was annotated, saved, and logged`);
705
+ console.log(`Testaro report ${id} was annotated, saved, and indexed`);
684
706
  // Check the monetary balances and send alerts if nearing exhaustion.
685
707
  await checkBalancesForAlerts(report);
686
708
  // Delete the job.
687
709
  await fs.unlink(path.join(claimedPath, `${id}.json`));
688
710
  console.log(`Completed job ${id} deleted`);
711
+ // Acknowledge receipt.
712
+ response.setHeader('content-type', 'application/json; charset=utf-8');
713
+ response.end(JSON.stringify({status: 'ok'}));
714
+ console.log(`Testaro report ${id} was received from Testaro agent ${agentID}`);
689
715
  }
690
716
  // Otherwise, i.e. if the request is invalid:
691
717
  else {
692
- await serveError({message: 'ERROR: Report invalid'}, response, false);
718
+ await serveError({message: 'ERROR: Request invalid'}, response, false);
693
719
  }
694
720
  }
695
- // Otherwise, if the service is not valid:
721
+ // Otherwise, i.e. if the service is invalid:
696
722
  else {
697
723
  await serveError(
698
724
  {message: 'ERROR: Invalid service request from Testaro agent'}, response, false
699
725
  );
700
726
  }
701
727
  }
702
- // Otherwise, if the first segment is the report finding service:
703
- else if (segments[0] === 'target') {
704
- const {description = '', hostname = ''} = postData;
705
- // If the payload contains a description or hostname:
706
- if (description || hostname) {
707
- // Process the request and get the response data.
708
- const responseData = await require(path.join(__dirname, 'target', 'api'))
709
- .response([description, hostname]);
710
- // Send them.
728
+ // Otherwise, if the first segment is not the ID of the Testaro agent:
729
+ else if (segments[0] !== testaroAgent) {
730
+ // If the service is to receive a test request:
731
+ if (segments[0] === 'requestTest') {
732
+ const {description, URL, reason} = postData;
733
+ // Get the response body.
734
+ const responseBody = await require(path.join(__dirname, 'api', 'requestTest'))
735
+ .response([description, URL, reason]);
736
+ // Send it.
711
737
  setHeaders('application/json', null, 'ultra');
712
- response.end(JSON.stringify(responseData));
738
+ response.end(JSON.stringify(responseBody));
713
739
  }
714
- // Otherwise, i.e. if it is not a valid test recommendation:
715
- else {
716
- // Report this.
717
- await serveError({message: 'ERROR: Request has neither a description fragment nor a URL fragment'}, response, false);
740
+ // Otherwise, if the service is to receive a retest request:
741
+ else if (segments[0] === 'requestRetest') {
742
+ const {reason} = postData;
743
+ // Get the response body.
744
+ const responseBody = await require(path.join(__dirname, 'api', 'requestRetest'))
745
+ .response(segments.slice(1).concat(reason));
746
+ // Send it.
747
+ setHeaders('application/json', null, 'ultra');
748
+ response.end(JSON.stringify(responseBody));
718
749
  }
719
- }
720
- // Otherwise, if the first segment is the test recommendation service:
721
- else if (segments[0] === 'testRecForm') {
722
- const {
723
- 'description of the web page': what,
724
- 'URL of the web page': url,
725
- 'reason for testing the web page': why
726
- } = postData;
727
- // If the payload is a valid test recommendation:
728
- if (what && isURL(url) && why) {
729
- let responseData;
730
- // If a report on the page is already available:
731
- if (await isReportAvailable(what, url)) {
732
- // Report this.
733
- responseData = {
734
- status: 'warning',
735
- message: 'A report on the page is already available'
736
- }
737
- }
738
- // Otherwise, i.e. if no report on the page is available:
739
- else {
740
- // Process the recommendation and get the response data.
741
- responseData = await require(path.join(__dirname, 'testRecForm', 'api'))
742
- .response(what, url, why);
743
- }
744
- // Send the response data.
750
+ // Otherwise, if the service is to receive a feature request:
751
+ else if (segments[0] === 'requestFeature') {
752
+ const {request} = postData;
753
+ // Get the response body.
754
+ const responseBody = await require(path.join(__dirname, 'api', 'requestFeature'))
755
+ .response(segments.slice(1).concat(request));
756
+ // Send it.
745
757
  setHeaders('application/json', null, 'ultra');
746
- response.end(JSON.stringify(responseData));
758
+ response.end(JSON.stringify(responseBody));
747
759
  }
748
- // Otherwise, i.e. if it is not a valid test recommendation:
760
+ // Otherwise, i.e. if the service is invalid:
749
761
  else {
750
- // Report this.
751
- await serveError({message: 'ERROR: Invalid test recommendation'}, response, false);
762
+ await serveError(
763
+ {message: 'ERROR: Invalid request'}, response, false
764
+ );
752
765
  }
753
766
  }
754
767
  // Otherwise, i.e. if the request is invalid:
@@ -790,7 +803,7 @@ const requestHandler = async (request, response) => {
790
803
 
791
804
  const serve = async (protocolModule, options) => {
792
805
  // Create any missing directories.
793
- for (const path of [queuePath, claimedPath, failedPath, logsPath, reportsPath]) {
806
+ for (const path of [queuePath, claimedPath, failedPath, reportsPath]) {
794
807
  await fs.mkdir(path, {recursive: true});
795
808
  }
796
809
  const server = protocolModule === 'https'
package/issues/index.js CHANGED
@@ -7,14 +7,12 @@
7
7
 
8
8
  const {sendAlert} = require('../alerts');
9
9
  const {
10
- annotateReport,
10
+ getLatestReportExtracts,
11
11
  getReport,
12
12
  getToolNamesString,
13
- getLogs,
14
13
  getWCAGLink,
15
14
  getWeightName,
16
15
  objectSort,
17
- ruleIDs
18
16
  } = require('../util');
19
17
  const {issues} = require('testilo/procs/score/tic');
20
18
  const fs = require('fs/promises');
@@ -23,20 +21,19 @@ const path = require('path');
23
21
  // FUNCTIONS
24
22
 
25
23
  // Gets summary data on the issues reported in a set of reports.
26
- const getIssuesSummary = async logs => {
24
+ const getIssuesSummary = async () => {
27
25
  // Initialize the summary.
28
- const summary = {};
26
+ const summary = {
27
+ totalCount: 0,
28
+ issues: []
29
+ };
29
30
  // Initialize data for a summary.
30
31
  const issuesData = {};
31
- // For each log of a report to be inspected:
32
- for (const log of logs) {
33
- const {annotated, jobName} = log;
34
- const [timeStamp, jobID] = jobName.split('-');
35
- // If the corresponding report is not yet annotated:
36
- if (! annotated) {
37
- // Annotate it and mark it as annotated in the log.
38
- await annotateReport(ruleIDs, timeStamp, jobID);
39
- }
32
+ // Get extracts of the latest available report on each page.
33
+ const latestReportExtracts = await getLatestReportExtracts();
34
+ // For each of them:
35
+ for (const reportExtract of latestReportExtracts) {
36
+ const {timeStamp, jobID} = reportExtract;
40
37
  // Get the corresponding report.
41
38
  const report = await getReport(timeStamp, jobID);
42
39
  const {acts = [], error} = report;
@@ -68,9 +65,6 @@ const getIssuesSummary = async logs => {
68
65
  }
69
66
  });
70
67
  }
71
- // Initialize the summary properties.
72
- summary.totalCount = 0;
73
- summary.issues = [];
74
68
  // For each issue:
75
69
  Object.entries(issuesData).forEach(([issueID, data]) => {
76
70
  const {count, reporters} = data;
@@ -109,10 +103,8 @@ const getIssuesSummary = async logs => {
109
103
  };
110
104
  // Adds parameters to a query for the answer page.
111
105
  const populateQuery = async query => {
112
- // Get the logs of the latest reports on the tested targets.
113
- const targetLogs = (await getLogs()).filter(log => ! log.superseded);
114
106
  // Get summary data on the issues.
115
- const issuesSummary = await getIssuesSummary(targetLogs);
107
+ const issuesSummary = await getIssuesSummary();
116
108
  // If this failed:
117
109
  if (issuesSummary.error) {
118
110
  // Populate the query with the reason.
@@ -160,7 +152,7 @@ const populateQuery = async query => {
160
152
  }
161
153
  });
162
154
  lines.push(`${margin}</ul>`);
163
- if (! existsIssue) {
155
+ if (!existsIssue) {
164
156
  lines.push(`${margin}<p>No issues with this priority.</p>`);
165
157
  }
166
158
  });
package/jsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": true,
4
+ "checkJs": true,
5
+ "noEmit": true,
6
+ "strict": false
7
+ }
8
+ }
package/llms-full.txt CHANGED
@@ -90,7 +90,7 @@ The implementation is currently in an alpha phase and offers these tools:
90
90
  - `summarizeOneReport`
91
91
  - method: `GET`
92
92
  - purpose: summarize one report
93
- - path: `/api/reportSummary/{timeStamp}/{jobID}`
93
+ - path: `/api/reportFacts/{timeStamp}/{jobID}`
94
94
  - parameters
95
95
  - `timeStamp`: initial segment of report identifier
96
96
  - `jobID`: final segment of report identifier
@@ -156,7 +156,7 @@ More information about Kilotest features and internals:
156
156
 
157
157
  ### For language models and AI platforms
158
158
 
159
- - [AI-TOOL file](https://github.com/jrpool/kilotest/blob/main/docs/AI-TOOL.md)
159
+ - [AI-TOOLS file](https://github.com/jrpool/kilotest/blob/main/docs/AI-TOOLS.md)
160
160
  - [OpenAPI specification for Kilotest](https://kilotest.com/openapi.yaml)
161
161
  - [Tool caller for testing](https://github.com/jrpool/kilotest/blob/main/researchAgent.js)
162
162