@jrpool/kilotest 55.0.0 → 57.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 (47) hide show
  1. package/README.md +1 -1
  2. package/api/awaitTest.test.ts +114 -0
  3. package/api/awaitTest.ts +108 -0
  4. package/api/listReports.ts +2 -2
  5. package/api/orderNewTest.test.ts +213 -0
  6. package/api/orderNewTest.ts +145 -0
  7. package/api/orderRetest.test.ts +179 -0
  8. package/api/orderRetest.ts +119 -0
  9. package/api/{requestTest.test.ts → requestNewTest.test.ts} +25 -25
  10. package/api/{requestTest.ts → requestNewTest.ts} +16 -39
  11. package/api/requestRetest.test.ts +6 -6
  12. package/api/requestRetest.ts +10 -33
  13. package/api/routes.test.ts +7 -4
  14. package/api/routes.ts +45 -7
  15. package/api/schemas.ts +120 -10
  16. package/api/util.ts +59 -2
  17. package/api/version.ts +6 -2
  18. package/capability.md +8 -5
  19. package/docs/SERVICE.md +6 -8
  20. package/env.example +15 -0
  21. package/index.test.ts +95 -24
  22. package/index.ts +80 -16
  23. package/lhm.plugin.json +1 -1
  24. package/linkValidation.test.ts +2 -2
  25. package/llms-full.txt +34 -3
  26. package/llms.txt +5 -2
  27. package/mcp.test.ts +72 -12
  28. package/mcp.ts +81 -11
  29. package/memory/MEMORY.md +1 -0
  30. package/memory/plan_issue_111_instant_test.md +198 -0
  31. package/openapi.yaml +451 -4
  32. package/package.json +1 -1
  33. package/routes.test.ts +1 -1
  34. package/routing.test.ts +2 -2
  35. package/server.json +1 -1
  36. package/sitemap.xml +1 -1
  37. package/smokeTest.ts +2 -2
  38. package/util.test.ts +214 -2
  39. package/util.ts +175 -7
  40. package/web/listReports/index.html +1 -1
  41. package/web/listReports/listReports.test.ts +1 -1
  42. package/web/{requestTest → requestNewTest}/index.ts +1 -1
  43. package/web/{requestTest/requestTest.test.ts → requestNewTest/requestNewTest.test.ts} +2 -2
  44. package/web/{requestTestForm → requestNewTestForm}/index.html +1 -1
  45. package/web/{requestTestForm/requestTestForm.test.ts → requestNewTestForm/requestNewTestForm.test.ts} +6 -6
  46. /package/web/{requestTest → requestNewTest}/index.html +0 -0
  47. /package/web/{requestTestForm → requestNewTestForm}/index.ts +0 -0
package/index.test.ts CHANGED
@@ -24,7 +24,7 @@ process.env.TESTARO_WORKERS = JSON.stringify({
24
24
  // isAllowedTarget performs. Tests of the check itself (below) override this per test.
25
25
  process.env.ALLOW_INTERNAL_TARGETS = 'true';
26
26
  // Blank the alert configuration unconditionally, so this file sends no real alert
27
- // emails (e.g. from the requestTest/requestRetest/tutorial-comment tests below) even
27
+ // emails (e.g. from the requestNewTest/requestRetest/tutorial-comment tests below) even
28
28
  // when run directly (e.g. `npx tsx --test index.test.ts`) rather than via `npm test`,
29
29
  // which normally guards against this by --require-ing test/setup.ts first. dotenv does
30
30
  // not override an already-set env var, so setting these here, before index.ts is ever
@@ -459,8 +459,8 @@ test('GET /forbidden-path returns an invalid GET request error', async () => {
459
459
 
460
460
  // TESTS: POST routes
461
461
 
462
- test('POST /requestTest.html with valid data returns an HTML page', async () => {
463
- const res = await formRequest('POST', '/requestTest.html', {
462
+ test('POST /requestNewTest.html with valid data returns an HTML page', async () => {
463
+ const res = await formRequest('POST', '/requestNewTest.html', {
464
464
  description: `Unique Test Page ${uniqueStamp}`,
465
465
  url: `https://example.com/unique-${uniqueStamp}`,
466
466
  why: 'Because accessibility matters'
@@ -469,8 +469,8 @@ test('POST /requestTest.html with valid data returns an HTML page', async () =>
469
469
  assert.ok(res.headers['content-type'].includes('text/html'));
470
470
  });
471
471
 
472
- test('POST /requestTest.html with an already-tested URL returns an error', async () => {
473
- const res = await formRequest('POST', '/requestTest.html', {
472
+ test('POST /requestNewTest.html with an already-tested URL returns an error', async () => {
473
+ const res = await formRequest('POST', '/requestNewTest.html', {
474
474
  description: 'Mixed Outcomes Page',
475
475
  url: 'https://example.com/mixed',
476
476
  why: 'Because accessibility matters'
@@ -479,8 +479,8 @@ test('POST /requestTest.html with an already-tested URL returns an error', async
479
479
  assert.ok(res.body.includes('already been tested'));
480
480
  });
481
481
 
482
- test('POST /requestTest.html with invalid data returns an error', async () => {
483
- const res = await formRequest('POST', '/requestTest.html', {
482
+ test('POST /requestNewTest.html with invalid data returns an error', async () => {
483
+ const res = await formRequest('POST', '/requestNewTest.html', {
484
484
  description: '',
485
485
  url: 'not-a-url',
486
486
  why: ''
@@ -489,8 +489,8 @@ test('POST /requestTest.html with invalid data returns an error', async () => {
489
489
  assert.ok(res.body.includes('Invalid test request'));
490
490
  });
491
491
 
492
- test('POST /requestTest.html with an unreadable body returns an error', async () => {
493
- const res = await request('POST', '/requestTest.html', 'rawbody', {'content-type': 'text/plain'});
492
+ test('POST /requestNewTest.html with an unreadable body returns an error', async () => {
493
+ const res = await request('POST', '/requestNewTest.html', 'rawbody', {'content-type': 'text/plain'});
494
494
  assert.equal(res.statusCode, 400);
495
495
  assert.ok(res.body.includes('Unreadable request body'));
496
496
  });
@@ -513,8 +513,8 @@ test('POST /requestRetest.html/invalid/invalid with invalid data returns an erro
513
513
  assert.ok(res.body.includes('Invalid retest request'));
514
514
  });
515
515
 
516
- test('POST /api/requestTest with valid JSON returns a JSON response', {timeout: 500}, async () => {
517
- const res = await request('POST', '/api/requestTest', {
516
+ test('POST /api/requestNewTest with valid JSON returns a JSON response', {timeout: 500}, async () => {
517
+ const res = await request('POST', '/api/requestNewTest', {
518
518
  description: `API Test Page ${uniqueStamp}`,
519
519
  URL: `https://example.com/api-test-${uniqueStamp}`,
520
520
  reason: 'Because accessibility matters'
@@ -544,6 +544,45 @@ test('POST /api/requestFeature with valid JSON returns a JSON response and recor
544
544
  assert.equal(await getMetricCount('apiOperations', 'requestFeature'), countBefore + 1);
545
545
  });
546
546
 
547
+ test('POST /api/orderNewTest with valid JSON enqueues a job directly and records an API-operation metric', {timeout: 500}, async () => {
548
+ const countBefore = await getMetricCount('apiOperations', 'orderNewTest');
549
+ const res = await request('POST', '/api/orderNewTest', {
550
+ description: `API Ordered Page ${uniqueStamp}`,
551
+ URL: `https://example.com/api-ordered-${uniqueStamp}`,
552
+ reason: 'Because accessibility matters here'
553
+ });
554
+ assert.equal(res.statusCode, 200);
555
+ const body = jsonBody(res);
556
+ assert.ok(body['tool name'] || body['response content']);
557
+ assert.equal(await getMetricCount('apiOperations', 'orderNewTest'), countBefore + 1);
558
+ const reportIdentifier = body['response content']['disposition of your order']['report identifier'];
559
+ assert.ok(reportIdentifier);
560
+ await fs.unlink(path.join(fixtureDBDir, 'jobs', 'queue', `${reportIdentifier.timeStamp}-${reportIdentifier.jobID}.json`));
561
+ });
562
+
563
+ test('POST /api/orderRetest/260202T0000/new with valid JSON enqueues a job directly and records an API-operation metric', async () => {
564
+ const countBefore = await getMetricCount('apiOperations', 'orderRetest');
565
+ const res = await request('POST', '/api/orderRetest/260202T0000/new', {
566
+ reason: 'Because the report is obsolete and needs refreshing'
567
+ });
568
+ assert.equal(res.statusCode, 200);
569
+ const body = jsonBody(res);
570
+ assert.ok(body['tool name'] || body['response content']);
571
+ assert.equal(await getMetricCount('apiOperations', 'orderRetest'), countBefore + 1);
572
+ const reportIdentifier = body['response content']['disposition of your order']['report identifier'];
573
+ assert.ok(reportIdentifier);
574
+ await fs.unlink(path.join(fixtureDBDir, 'jobs', 'queue', `${reportIdentifier.timeStamp}-${reportIdentifier.jobID}.json`));
575
+ });
576
+
577
+ test('POST /api/awaitTest/260101T0000/mix with an already-completed report records an API-operation metric', async () => {
578
+ const countBefore = await getMetricCount('apiOperations', 'awaitTest');
579
+ const res = await request('POST', '/api/awaitTest/260101T0000/mix', {});
580
+ assert.equal(res.statusCode, 200);
581
+ const body = jsonBody(res);
582
+ assert.equal(body['response content']['disposition of your wait'].outcome, 'completed');
583
+ assert.equal(await getMetricCount('apiOperations', 'awaitTest'), countBefore + 1);
584
+ });
585
+
547
586
  test('POST /api/invalidService returns an error', async () => {
548
587
  const res = await request('POST', '/api/invalidService', {data: 'test'});
549
588
  assert.equal(res.statusCode, 400);
@@ -697,7 +736,7 @@ test('POST /requestAction.html with invalid auth code returns an error and recor
697
736
  });
698
737
 
699
738
  test('POST /requestAction.html with valid auth code and rejection (no description) returns HTML and records a managerActivity success', async () => {
700
- await formRequest('POST', '/requestTest.html', {
739
+ await formRequest('POST', '/requestNewTest.html', {
701
740
  description: `Reject Test Page ${uniqueStamp}`,
702
741
  url: `https://example.com/reject-${uniqueStamp}`,
703
742
  why: 'Because accessibility matters'
@@ -740,7 +779,7 @@ test('GET /fullReport.json/260101T0007/hid returns an error for an unavailable r
740
779
 
741
780
  test('POST /requestAction.html with valid auth code and approval returns HTML', {timeout: 500}, async () => {
742
781
  await fs.writeFile(testRequestsPath, '{}\n');
743
- await formRequest('POST', '/requestTest.html', {
782
+ await formRequest('POST', '/requestNewTest.html', {
744
783
  description: `Approval Test Page ${uniqueStamp}`,
745
784
  url: `https://example.com/approval-${uniqueStamp}`,
746
785
  why: 'Because accessibility matters'
@@ -899,8 +938,40 @@ test('GET /targets.html redirects to /listReports.html', async () => {
899
938
  assert.equal(res.headers['location'], '/listReports.html');
900
939
  });
901
940
 
902
- test('POST /requestTest.html with non-https URL returns an error', async () => {
903
- const res = await formRequest('POST', '/requestTest.html', {
941
+ test('GET /requestTest.html redirects to /requestNewTest.html', async () => {
942
+ const res = await request('GET', '/requestTest.html', {}, {followRedirects: false});
943
+ assert.equal(res.statusCode, 301);
944
+ assert.equal(res.headers['location'], '/requestNewTest.html');
945
+ });
946
+
947
+ test('GET /requestTestForm.html redirects to /requestNewTestForm.html', async () => {
948
+ const res = await request('GET', '/requestTestForm.html', {}, {followRedirects: false});
949
+ assert.equal(res.statusCode, 301);
950
+ assert.equal(res.headers['location'], '/requestNewTestForm.html');
951
+ });
952
+
953
+ test('POST /api/requestTest redirects (308, preserving method) to /api/requestNewTest', async () => {
954
+ const res = await request('POST', '/api/requestTest', {
955
+ description: `Old Path Page ${uniqueStamp}`,
956
+ URL: `https://example.com/old-path-${uniqueStamp}`,
957
+ reason: 'Because accessibility matters'
958
+ }, {followRedirects: false});
959
+ assert.equal(res.statusCode, 308);
960
+ assert.equal(res.headers['location'], '/api/requestNewTest');
961
+ });
962
+
963
+ test('POST /api/orderTest redirects (308, preserving method) to /api/orderNewTest', async () => {
964
+ const res = await request('POST', '/api/orderTest', {
965
+ description: `Old Path Ordered Page ${uniqueStamp}`,
966
+ URL: `https://example.com/old-path-ordered-${uniqueStamp}`,
967
+ reason: 'Because accessibility matters'
968
+ }, {followRedirects: false});
969
+ assert.equal(res.statusCode, 308);
970
+ assert.equal(res.headers['location'], '/api/orderNewTest');
971
+ });
972
+
973
+ test('POST /requestNewTest.html with non-https URL returns an error', async () => {
974
+ const res = await formRequest('POST', '/requestNewTest.html', {
904
975
  description: 'Test Page',
905
976
  url: 'http://example.com',
906
977
  why: 'Because accessibility matters'
@@ -909,10 +980,10 @@ test('POST /requestTest.html with non-https URL returns an error', async () => {
909
980
  assert.ok(res.body.includes('Invalid test request'));
910
981
  });
911
982
 
912
- test('POST /requestTest.html with a private-address URL returns an error unless internal targets are allowed', async () => {
983
+ test('POST /requestNewTest.html with a private-address URL returns an error unless internal targets are allowed', async () => {
913
984
  delete process.env.ALLOW_INTERNAL_TARGETS;
914
985
  try {
915
- const res = await formRequest('POST', '/requestTest.html', {
986
+ const res = await formRequest('POST', '/requestNewTest.html', {
916
987
  description: 'Internal Page',
917
988
  url: 'https://192.168.1.1/page',
918
989
  why: 'Because accessibility matters'
@@ -1370,14 +1441,14 @@ test('GET /style.css with a read error returns an error page', async () => {
1370
1441
  }
1371
1442
  });
1372
1443
 
1373
- // requestTest.html and requestRetest.html are POST-only routes (see the
1444
+ // requestNewTest.html and requestRetest.html are POST-only routes (see the
1374
1445
  // `routes.POST` list above). They also match the generic '*.html*' GET pattern,
1375
1446
  // but the generic `pageName.endsWith('.html')` branch of the GET dispatcher
1376
1447
  // excludes any path that is POST-only, since such a path's answer handler
1377
1448
  // expects POST's argument list and performs no GET-appropriate rendering.
1378
1449
 
1379
- test('GET /requestTest.html without arguments is rejected as an invalid GET request', async () => {
1380
- const res = await request('GET', '/requestTest.html');
1450
+ test('GET /requestNewTest.html without arguments is rejected as an invalid GET request', async () => {
1451
+ const res = await request('GET', '/requestNewTest.html');
1381
1452
  assert.equal(res.statusCode, 400);
1382
1453
  });
1383
1454
 
@@ -1731,18 +1802,18 @@ test('POST /mcp with a valid initialize request returns server info via SSE', as
1731
1802
 
1732
1803
  // TESTS: answer error branches
1733
1804
 
1734
- test('POST /requestTest.html with valid format but duplicate URL returns an answer error', {timeout: 500}, async () => {
1805
+ test('POST /requestNewTest.html with valid format but duplicate URL returns an answer error', {timeout: 500}, async () => {
1735
1806
  await fs.writeFile(testRequestsPath, '{}\n');
1736
1807
  // First request to create the request. processTestRequest treats a request as a
1737
1808
  // duplicate only when description, URL, AND reason all match an existing pending
1738
1809
  // request, so the reason must be identical between the two requests here.
1739
- await formRequest('POST', '/requestTest.html', {
1810
+ await formRequest('POST', '/requestNewTest.html', {
1740
1811
  description: `Dup Test Page ${uniqueStamp}`,
1741
1812
  url: `https://example.com/dup-${uniqueStamp}`,
1742
1813
  why: 'Because accessibility matters'
1743
1814
  });
1744
1815
  // Second, identical request should get a duplicate error.
1745
- const res = await formRequest('POST', '/requestTest.html', {
1816
+ const res = await formRequest('POST', '/requestNewTest.html', {
1746
1817
  description: `Dup Test Page ${uniqueStamp}`,
1747
1818
  url: `https://example.com/dup-${uniqueStamp}`,
1748
1819
  why: 'Because accessibility matters'
@@ -1770,7 +1841,7 @@ test('POST /requestRetest.html with valid format but duplicate retest returns an
1770
1841
  test('POST /requestAction.html with valid auth code approves the same target twice without error', {timeout: 500}, async () => {
1771
1842
  await fs.writeFile(testRequestsPath, '{}\n');
1772
1843
  // Create a request.
1773
- await formRequest('POST', '/requestTest.html', {
1844
+ await formRequest('POST', '/requestNewTest.html', {
1774
1845
  description: `Action Dup Page ${uniqueStamp}`,
1775
1846
  url: `https://example.com/action-dup-${uniqueStamp}`,
1776
1847
  why: 'Because accessibility matters'
package/index.ts CHANGED
@@ -67,8 +67,8 @@ import {answer as renewWCAG} from './web/renewWCAG/index.ts';
67
67
  import {answer as renewWCAGForm} from './web/renewWCAGForm/index.ts';
68
68
  import {answer as requestRetestPage} from './web/requestRetest/index.ts';
69
69
  import {answer as requestRetestForm} from './web/requestRetestForm/index.ts';
70
- import {answer as requestTestPage} from './web/requestTest/index.ts';
71
- import {answer as requestTestForm} from './web/requestTestForm/index.ts';
70
+ import {answer as requestNewTestPage} from './web/requestNewTest/index.ts';
71
+ import {answer as requestNewTestForm} from './web/requestNewTestForm/index.ts';
72
72
  import {answer as rewindReportsForm} from './web/rewindReportsForm/index.ts';
73
73
  import {answer as showHiddenReportsForm} from './web/showHiddenReportsForm/index.ts';
74
74
  import {answer as unhideReportForm} from './web/unhideReportForm/index.ts';
@@ -79,7 +79,10 @@ import {response as listReportsAPI} from './api/listReports.ts';
79
79
  import {response as listViolatorsAPI} from './api/listViolators.ts';
80
80
  import {response as requestFeatureAPI} from './api/requestFeature.ts';
81
81
  import {response as requestRetestAPI} from './api/requestRetest.ts';
82
- import {response as requestTestAPI} from './api/requestTest.ts';
82
+ import {response as requestNewTestAPI} from './api/requestNewTest.ts';
83
+ import {response as orderNewTestAPI} from './api/orderNewTest.ts';
84
+ import {response as orderRetestAPI} from './api/orderRetest.ts';
85
+ import {response as awaitTestAPI} from './api/awaitTest.ts';
83
86
 
84
87
  // ENVIRONMENT
85
88
 
@@ -124,8 +127,8 @@ const answer: {
124
127
  renewWCAGForm: PageHandler;
125
128
  requestRetest: PageHandler;
126
129
  requestRetestForm: PageHandler;
127
- requestTest: PageHandler;
128
- requestTestForm: PageHandler;
130
+ requestNewTest: PageHandler;
131
+ requestNewTestForm: PageHandler;
129
132
  rewindReportsForm: PageHandler;
130
133
  showHiddenReportsForm: PageHandler;
131
134
  unhideReportForm: PageHandler;
@@ -154,8 +157,8 @@ const answer: {
154
157
  renewWCAGForm,
155
158
  requestRetest: requestRetestPage,
156
159
  requestRetestForm,
157
- requestTest: requestTestPage,
158
- requestTestForm,
160
+ requestNewTest: requestNewTestPage,
161
+ requestNewTestForm,
159
162
  rewindReportsForm,
160
163
  showHiddenReportsForm,
161
164
  unhideReportForm
@@ -170,7 +173,10 @@ const apiRespond: {
170
173
  listViolators: ApiResponder;
171
174
  requestFeature: ApiResponder;
172
175
  requestRetest: ApiResponder;
173
- requestTest: ApiResponder;
176
+ requestNewTest: ApiResponder;
177
+ orderNewTest: ApiResponder;
178
+ orderRetest: ApiResponder;
179
+ awaitTest: ApiResponder;
174
180
  } = {
175
181
  getReport: getReportAPI,
176
182
  listDiagnoses: listDiagnosesAPI,
@@ -179,7 +185,10 @@ const apiRespond: {
179
185
  listViolators: listViolatorsAPI,
180
186
  requestFeature: requestFeatureAPI,
181
187
  requestRetest: requestRetestAPI,
182
- requestTest: requestTestAPI
188
+ requestNewTest: requestNewTestAPI,
189
+ orderNewTest: orderNewTestAPI,
190
+ orderRetest: orderRetestAPI,
191
+ awaitTest: awaitTestAPI
183
192
  };
184
193
 
185
194
  // CONSTANTS
@@ -226,7 +235,7 @@ export const routes = {
226
235
  '/requestAction.html',
227
236
  '/renewWCAG.html',
228
237
  '/requestRetest.html/*',
229
- '/requestTest.html',
238
+ '/requestNewTest.html',
230
239
  '/tutorialWebComment.html',
231
240
  '/unhideReportForm.html',
232
241
  '/worker/job',
@@ -276,12 +285,18 @@ const noDirectGetPages = new Set(['unhideReportForm']);
276
285
  // engines and other crawlers are still requesting. Redirecting them lets crawlers update
277
286
  // their own indexes, rather than leaving them to repeat the same dead request indefinitely.
278
287
  // TEMPORARY: retire this redirect after 2027-04-01 once crawlers have re-indexed.
288
+ // requestTest and requestTestForm were renamed to requestNewTest and requestNewTestForm
289
+ // on 2026-09-28 (see the newTest/retest naming-ambiguity fix), to distinguish the
290
+ // new-test-only page from bare "test" language used elsewhere.
291
+ // TEMPORARY: retire this redirect after 2027-03-28 once crawlers have re-indexed.
279
292
  const renamedPagePrefixes: Record<string, string> = {
280
293
  diagnoses: 'listDiagnoses',
281
294
  reportIssue: 'listViolators',
282
295
  reportIssues: 'listIssues',
283
296
  rules: 'listRules',
284
- targets: 'listReports'
297
+ targets: 'listReports',
298
+ requestTest: 'requestNewTest',
299
+ requestTestForm: 'requestNewTestForm'
285
300
  };
286
301
  const jobLock = createLock();
287
302
 
@@ -927,7 +942,7 @@ const handleRequest = async (request: IncomingMessage, response: ServerResponse)
927
942
  await serveError({message: 'ERROR: Unreadable request body'}, response, true);
928
943
  }
929
944
  // If the request is a test request:
930
- else if (pageName === 'requestTest.html') {
945
+ else if (pageName === 'requestNewTest.html') {
931
946
  const {description, url, why} = postData as {description?: string; url: string; why?: string};
932
947
  // If the request is valid:
933
948
  if (description && isURL(url) && why && await isAllowedTarget(url)) {
@@ -941,7 +956,7 @@ const handleRequest = async (request: IncomingMessage, response: ServerResponse)
941
956
  // Serve headers for a response.
942
957
  setHeaders('text/html', pathname, 'ultra');
943
958
  // Get the answer data.
944
- const answerData = await answer.requestTest(description, url, why);
959
+ const answerData = await answer.requestNewTest(description, url, why);
945
960
  // If they are valid:
946
961
  if (answerData.status === 'ok') {
947
962
  // Serve the answer page.
@@ -1226,15 +1241,26 @@ const handleRequest = async (request: IncomingMessage, response: ServerResponse)
1226
1241
  // Get the segments of the path after api.
1227
1242
  const segments = pathTail.split('/');
1228
1243
  // If the service is to receive a test request:
1229
- if (segments[0] === 'requestTest') {
1244
+ if (segments[0] === 'requestNewTest') {
1230
1245
  const {description, URL, reason} = postData as {description: string; URL: string; reason: string};
1231
1246
  // Get the response body.
1232
- const responseBody = await apiRespond.requestTest([description, URL, reason]);
1233
- await recordPageMetric('apiOperations', 'requestTest');
1247
+ const responseBody = await apiRespond.requestNewTest([description, URL, reason]);
1248
+ await recordPageMetric('apiOperations', 'requestNewTest');
1234
1249
  // Send it.
1235
1250
  setHeaders('application/json', null, 'ultra');
1236
1251
  response.end(JSON.stringify(responseBody));
1237
1252
  }
1253
+ // Otherwise, if the request uses the old, pre-rename requestTest path name: redirect
1254
+ // permanently to the new path name. requestTest takes no path parameters (its
1255
+ // arguments are body fields), so there is no further path to preserve, unlike the
1256
+ // renamedPagePrefixes GET-page redirects above. 308 (not 301) is used here because
1257
+ // 301 is defined to let clients rewrite a POST redirect as a GET, which would drop
1258
+ // this request's body; 308 preserves both the method and the body.
1259
+ // TEMPORARY: retire this redirect after 2027-03-28 once callers have updated.
1260
+ else if (segments[0] === 'requestTest') {
1261
+ response.writeHead(308, {Location: '/api/requestNewTest'});
1262
+ response.end();
1263
+ }
1238
1264
  // Otherwise, if the service is to receive a retest request:
1239
1265
  else if (segments[0] === 'requestRetest') {
1240
1266
  const {reason} = postData as {reason: string};
@@ -1255,6 +1281,44 @@ const handleRequest = async (request: IncomingMessage, response: ServerResponse)
1255
1281
  setHeaders('application/json', null, 'ultra');
1256
1282
  response.end(JSON.stringify(responseBody));
1257
1283
  }
1284
+ // Otherwise, if the service is to order an immediate, automatically approved test:
1285
+ else if (segments[0] === 'orderNewTest') {
1286
+ const {description, URL, reason} = postData as {description: string; URL: string; reason: string};
1287
+ // Get the response body.
1288
+ const responseBody = await apiRespond.orderNewTest([description, URL, reason]);
1289
+ await recordPageMetric('apiOperations', 'orderNewTest');
1290
+ // Send it.
1291
+ setHeaders('application/json', null, 'ultra');
1292
+ response.end(JSON.stringify(responseBody));
1293
+ }
1294
+ // Otherwise, if the request uses the old, pre-rename orderTest path name: redirect
1295
+ // permanently to the new path name. orderTest likewise takes no path parameters.
1296
+ // See the requestTest redirect above for why 308 (not 301) is used for these
1297
+ // POST-only API redirects.
1298
+ // TEMPORARY: retire this redirect after 2027-03-28 once callers have updated.
1299
+ else if (segments[0] === 'orderTest') {
1300
+ response.writeHead(308, {Location: '/api/orderNewTest'});
1301
+ response.end();
1302
+ }
1303
+ // Otherwise, if the service is to order an immediate, automatically approved retest:
1304
+ else if (segments[0] === 'orderRetest') {
1305
+ const {reason} = postData as {reason: string};
1306
+ // Get the response body.
1307
+ const responseBody = await apiRespond.orderRetest(segments.slice(1).concat(reason));
1308
+ await recordPageMetric('apiOperations', 'orderRetest');
1309
+ // Send it.
1310
+ setHeaders('application/json', null, 'ultra');
1311
+ response.end(JSON.stringify(responseBody));
1312
+ }
1313
+ // Otherwise, if the service is to wait for an ordered job to complete:
1314
+ else if (segments[0] === 'awaitTest') {
1315
+ // Get the response body.
1316
+ const responseBody = await apiRespond.awaitTest(segments.slice(1));
1317
+ await recordPageMetric('apiOperations', 'awaitTest');
1318
+ // Send it.
1319
+ setHeaders('application/json', null, 'ultra');
1320
+ response.end(JSON.stringify(responseBody));
1321
+ }
1258
1322
  // Otherwise, i.e. if the service is invalid:
1259
1323
  else {
1260
1324
  await serveError(
package/lhm.plugin.json CHANGED
@@ -2297,7 +2297,7 @@
2297
2297
  }
2298
2298
  },
2299
2299
  {
2300
- "name": "requestTest",
2300
+ "name": "requestNewTest",
2301
2301
  "description": "Process my request to test a page about which no report is available yet.",
2302
2302
  "inputSchema": {
2303
2303
  "type": "object",
@@ -48,8 +48,8 @@ const htmlRoutes = [
48
48
  '/listViolators.html',
49
49
  '/listRules.html',
50
50
  '/manage.html',
51
- '/requestTest.html',
52
- '/requestTestForm.html',
51
+ '/requestNewTest.html',
52
+ '/requestNewTestForm.html',
53
53
  '/requestRetest.html',
54
54
  '/requestRetestForm.html',
55
55
  '/reannotate.html',
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/listIssues.html/{timeStamp}/{jobID}`
64
64
  - Get details about one issue reported in one report: `https://kilotest.com/listViolators.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/listDiagnoses.html/{issueID}/{timeStamp}/{jobID}/{catalogIndex}`
66
- - [Recommend the testing of a not-yet-tested page](https://kilotest.com/requestTestForm.html)
66
+ - [Recommend the testing of a not-yet-tested page](https://kilotest.com/requestNewTestForm.html)
67
67
  - Recommend the retesting of a previously tested page: `https://kilotest.com/requestRetestForm.html/{timeStamp}/{jobID}`
68
68
  - [Get statistics about frequently reported issues across all pages](https://kilotest.com/listTopIssues.html)
69
69
  - [Get a list of the operations that Kilotest managers are authorized to request](https://kilotest.com/manage.html)
@@ -108,10 +108,10 @@ Kilotest offers these tools:
108
108
  - `timeStamp`: initial segment of report identifier
109
109
  - `jobID`: final segment of report identifier
110
110
  - source of parameters: response to a `listViolators` request
111
- - `requestTest`
111
+ - `requestNewTest`
112
112
  - method: `POST`
113
113
  - purpose: request testing of one web page about which no report is available
114
- - path: `/api/requestTest`
114
+ - path: `/api/requestNewTest`
115
115
  - payload properties
116
116
  - `description`: description of the page to be tested
117
117
  - `URL`: URL of the page to be tested
@@ -132,6 +132,35 @@ Kilotest offers these tools:
132
132
  - path: `/api/requestFeature`
133
133
  - payload properties
134
134
  - `feature`: description and motivation of the feature improvement or the new feature
135
+ - `orderNewTest`
136
+ - method: `POST`
137
+ - purpose: order immediate, automatically approved testing of one web page about which no report is available, instead of waiting for manual approval
138
+ - path: `/api/orderNewTest`
139
+ - payload properties
140
+ - `description`: description of the page to be tested
141
+ - `URL`: URL of the page to be tested
142
+ - `reason`: reason for testing the page
143
+ - response includes a report identifier (`timeStamp` and `jobID`) once the job is enqueued
144
+ - `orderRetest`
145
+ - method: `POST`
146
+ - purpose: order immediate, automatically approved retesting of one web page about which a report is available, instead of waiting for manual approval
147
+ - path: `/api/orderRetest/{timeStamp}/{jobID}`
148
+ - parameters
149
+ - `timeStamp`: initial segment of report identifier
150
+ - `jobID`: final segment of report identifier
151
+ - source of parameters: response to a `listIssues` request
152
+ - payload properties
153
+ - `reason`: reason for retesting the page
154
+ - response includes a report identifier (`timeStamp` and `jobID`) once the job is enqueued
155
+ - `awaitTest`
156
+ - method: `POST`
157
+ - purpose: wait for a job ordered via `orderNewTest` or `orderRetest` to complete; blocks until the report is ready, the job fails, or a maximum wait time elapses, then returns
158
+ - path: `/api/awaitTest/{timeStamp}/{jobID}`
159
+ - parameters
160
+ - `timeStamp`: initial segment of the report identifier returned by `orderNewTest` or `orderRetest`
161
+ - `jobID`: final segment of the report identifier returned by `orderNewTest` or `orderRetest`
162
+ - source of parameters: response to an `orderNewTest` or `orderRetest` request
163
+ - caveat: testing typically takes about 2 minutes and occasionally up to 4 minutes; sends no interim notices while waiting
135
164
 
136
165
  ## After a test, retest, or feature request
137
166
 
@@ -147,6 +176,8 @@ When you request a test or a retest:
147
176
  - check test completion by calling `listReports` and looking for the page and completion date in the list
148
177
  - you advise the user to check in approximately 1 day for the test results on the Kilotest website, starting at `https://kilotest.com/listReports.html`
149
178
 
179
+ For immediate testing without waiting for manual approval, use `orderNewTest` or `orderRetest` instead of `requestNewTest` or `requestRetest`. Each enqueues the job right away and returns a report identifier along with a question about whether to wait for completion. To wait, call `awaitTest` with that identifier; it blocks until the report is ready, the job fails, or a maximum wait time elapses. To check later instead, call `listIssues` with the same identifier.
180
+
150
181
  An [OpenAPI specification for Kilotest](https://kilotest.com/openapi.yaml) is available.
151
182
 
152
183
  ## How to report results honestly to users
package/llms.txt CHANGED
@@ -9,9 +9,12 @@ Kilotest helps language models and AI platforms to give thorough, accurate and t
9
9
  - List all elements exhibiting one issue in one report: `/api/listViolators/{issueID}/{timeStamp}/{jobID}` (`GET`)
10
10
  - List all diagnoses of one element exhibiting one issue in one report: `/api/listDiagnoses/{catalogIndex}/{issueID}/{timeStamp}/{jobID}` (`GET`)
11
11
  - Get one full report in JSON: `/api/getReport/{timeStamp}/{jobID}` (`GET`)
12
- - Request testing of a web page: `/api/requestTest` (`POST`)
12
+ - Request testing of a web page: `/api/requestNewTest` (`POST`)
13
13
  - Request retesting of a web page: `/api/requestRetest/{timeStamp}/{jobID}` (`POST`)
14
14
  - Request adding or improving a feature: `/api/requestFeature` (`POST`)
15
+ - Order immediate, automatically approved testing of a web page: `/api/orderNewTest` (`POST`)
16
+ - Order immediate, automatically approved retesting of a web page: `/api/orderRetest/{timeStamp}/{jobID}` (`POST`)
17
+ - Wait for a job ordered via orderNewTest or orderRetest to complete: `/api/awaitTest/{timeStamp}/{jobID}` (`POST`)
15
18
 
16
19
  ## MCP Server
17
20
 
@@ -24,7 +27,7 @@ Kilotest helps language models and AI platforms to give thorough, accurate and t
24
27
  - List all elements exhibiting one issue in one report: `https://kilotest.com/listViolators.html/{issueID}/{timeStamp}/{jobID}`
25
28
  - List all diagnoses of one element exhibiting one issue in one report: `https://kilotest.com/listDiagnoses.html/{issueID}/{timeStamp}/{jobID}/{catalogIndex}`
26
29
  - Get one full report in JSON: `https://kilotest.com/fullReport.json/{timeStamp}/{jobID}`
27
- - [Request testing of a web page](https://kilotest.com/requestTestForm.html)
30
+ - [Request testing of a web page](https://kilotest.com/requestNewTestForm.html)
28
31
  - Request retesting of a web page: `https://kilotest.com/requestRetestForm.html/{timeStamp}/{jobID}`
29
32
  - [List the operations that Kilotest managers are authorized to request](https://kilotest.com/manage.html)
30
33
  - [Provide statistics about frequently reported issues across all pages](https://kilotest.com/listTopIssues.html)