@jrpool/kilotest 55.0.0 → 57.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/api/awaitTest.test.ts +114 -0
- package/api/awaitTest.ts +108 -0
- package/api/listReports.ts +2 -2
- package/api/orderNewTest.test.ts +213 -0
- package/api/orderNewTest.ts +145 -0
- package/api/orderRetest.test.ts +179 -0
- package/api/orderRetest.ts +119 -0
- package/api/{requestTest.test.ts → requestNewTest.test.ts} +25 -25
- package/api/{requestTest.ts → requestNewTest.ts} +16 -39
- package/api/requestRetest.test.ts +6 -6
- package/api/requestRetest.ts +10 -33
- package/api/routes.test.ts +7 -4
- package/api/routes.ts +45 -7
- package/api/schemas.ts +120 -10
- package/api/util.ts +59 -2
- package/api/version.ts +6 -2
- package/capability.md +12 -5
- package/docs/SERVICE.md +6 -8
- package/env.example +15 -0
- package/index.html +1 -0
- package/index.test.ts +95 -24
- package/index.ts +83 -16
- package/lhm.plugin.json +1 -1
- package/linkValidation.test.ts +2 -2
- package/llms-full.txt +34 -3
- package/llms.txt +6 -2
- package/mcp.test.ts +72 -12
- package/mcp.ts +81 -11
- package/memory/MEMORY.md +1 -0
- package/memory/plan_issue_111_instant_test.md +198 -0
- package/openapi.yaml +451 -4
- package/package.json +1 -1
- package/routes.test.ts +1 -1
- package/routing.test.ts +2 -2
- package/server.json +1 -1
- package/sitemap.xml +2 -1
- package/smokeTest.ts +2 -2
- package/util.test.ts +214 -2
- package/util.ts +175 -7
- package/web/listReports/index.html +1 -1
- package/web/listReports/listReports.test.ts +1 -1
- package/web/privacy/index.html +37 -0
- package/web/privacy/index.ts +20 -0
- package/web/privacy/privacy.test.ts +18 -0
- package/web/{requestTest → requestNewTest}/index.ts +1 -1
- package/web/{requestTest/requestTest.test.ts → requestNewTest/requestNewTest.test.ts} +2 -2
- package/web/{requestTestForm → requestNewTestForm}/index.html +1 -1
- package/web/{requestTestForm/requestTestForm.test.ts → requestNewTestForm/requestNewTestForm.test.ts} +6 -6
- package/web/tutorialAI/index.html +26 -39
- /package/web/{requestTest → requestNewTest}/index.html +0 -0
- /package/web/{requestTestForm → requestNewTestForm}/index.ts +0 -0
package/api/requestRetest.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
8
|
import {z} from 'zod';
|
|
9
|
-
import {getResponseMetadata, getThisHost, getToolsFacts} from './util.ts';
|
|
9
|
+
import {buildRequestDisposition, getResponseMetadata, getThisHost, getToolsFacts} from './util.ts';
|
|
10
10
|
import {checkLength, isJobID, isTimeStamp, processTestRequest} from '../util.ts';
|
|
11
11
|
import {requestRetestResponseSchema} from './schemas.ts';
|
|
12
12
|
|
|
@@ -60,38 +60,15 @@ export const response = async (args: string[]) => {
|
|
|
60
60
|
'reason why the page should be retested': reason
|
|
61
61
|
};
|
|
62
62
|
// Add information about the disposition of the request.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
'
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
let failureReason: string;
|
|
73
|
-
if (requestResult === 'description') {
|
|
74
|
-
failureReason = 'a request to test a page with the same description is already approved.'
|
|
75
|
-
}
|
|
76
|
-
else if (requestResult === 'url') {
|
|
77
|
-
failureReason = 'a request to test a page with the same URL is already approved.'
|
|
78
|
-
}
|
|
79
|
-
else if (requestResult === 'superseded') {
|
|
80
|
-
failureReason = 'a later report about a page with the same description is available.'
|
|
81
|
-
}
|
|
82
|
-
else if (requestResult === 'queueFull') {
|
|
83
|
-
failureReason = 'too many requests are awaiting approval right now. Please try again later, ' +
|
|
84
|
-
'or post your request at https://github.com/jrpool/kilotest/issues or email info@kilotest.com.'
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
failureReason = 'an identical request is already awaiting approval.'
|
|
88
|
-
}
|
|
89
|
-
requestDisposition = {
|
|
90
|
-
'what happens next': `${failureFact}${failureReason}`,
|
|
91
|
-
'how you can check for completion': 'Not applicable.',
|
|
92
|
-
'how a web user can check for completion': 'Not applicable.'
|
|
93
|
-
}
|
|
94
|
-
}
|
|
63
|
+
requestDisposition = buildRequestDisposition(
|
|
64
|
+
requestResult,
|
|
65
|
+
{
|
|
66
|
+
whatHappensNext: 'Your request is likely to be approved and processed within 1 hour to 1 day.',
|
|
67
|
+
howToCheck: 'You can call the listReports tool to learn whether the page has been retested and a new report is available.',
|
|
68
|
+
howWebUserChecks: `A web user can visit ${thisHost}/listReports.html to learn whether the page has been retested and a new report is available.`
|
|
69
|
+
},
|
|
70
|
+
{result: 'superseded', reason: 'a later report about a page with the same description is available.'}
|
|
71
|
+
);
|
|
95
72
|
}
|
|
96
73
|
}
|
|
97
74
|
// Create the response content.
|
package/api/routes.test.ts
CHANGED
|
@@ -11,22 +11,25 @@ import {routes} from './routes.ts';
|
|
|
11
11
|
|
|
12
12
|
// TESTS
|
|
13
13
|
|
|
14
|
-
test('routes exports an array with
|
|
14
|
+
test('routes exports an array with 11 operations', () => {
|
|
15
15
|
assert.ok(Array.isArray(routes));
|
|
16
|
-
assert.equal(routes.length,
|
|
16
|
+
assert.equal(routes.length, 11);
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
test('routes includes all expected operation IDs', () => {
|
|
20
20
|
const ids = routes.map(r => r.operationId).sort();
|
|
21
21
|
assert.deepEqual(ids, [
|
|
22
|
+
'awaitTest',
|
|
22
23
|
'getReport',
|
|
23
24
|
'listDiagnoses',
|
|
24
25
|
'listIssues',
|
|
25
26
|
'listReports',
|
|
26
27
|
'listViolators',
|
|
28
|
+
'orderNewTest',
|
|
29
|
+
'orderRetest',
|
|
27
30
|
'requestFeature',
|
|
28
|
-
'
|
|
29
|
-
'
|
|
31
|
+
'requestNewTest',
|
|
32
|
+
'requestRetest'
|
|
30
33
|
]);
|
|
31
34
|
});
|
|
32
35
|
|
package/api/routes.ts
CHANGED
|
@@ -11,17 +11,23 @@ import {
|
|
|
11
11
|
listViolatorsSchema,
|
|
12
12
|
listDiagnosesSchema,
|
|
13
13
|
getReportSchema,
|
|
14
|
-
|
|
14
|
+
requestNewTestSchema,
|
|
15
15
|
requestRetestSchema,
|
|
16
16
|
requestFeatureSchema,
|
|
17
|
+
orderNewTestSchema,
|
|
18
|
+
orderRetestSchema,
|
|
19
|
+
awaitTestSchema,
|
|
17
20
|
listReportsResponseSchema,
|
|
18
21
|
listIssuesResponseSchema,
|
|
19
22
|
listViolatorsResponseSchema,
|
|
20
23
|
listDiagnosesResponseSchema,
|
|
21
24
|
getReportResponseSchema,
|
|
22
|
-
|
|
25
|
+
requestNewTestResponseSchema,
|
|
23
26
|
requestRetestResponseSchema,
|
|
24
|
-
requestFeatureResponseSchema
|
|
27
|
+
requestFeatureResponseSchema,
|
|
28
|
+
orderNewTestResponseSchema,
|
|
29
|
+
orderRetestResponseSchema,
|
|
30
|
+
awaitTestResponseSchema
|
|
25
31
|
} from './schemas.ts';
|
|
26
32
|
|
|
27
33
|
// ROUTES
|
|
@@ -67,12 +73,12 @@ export const routes = [
|
|
|
67
73
|
responseSchema: getReportResponseSchema
|
|
68
74
|
},
|
|
69
75
|
{
|
|
70
|
-
operationId: '
|
|
76
|
+
operationId: 'requestNewTest',
|
|
71
77
|
method: 'post',
|
|
72
|
-
path: '/api/
|
|
78
|
+
path: '/api/requestNewTest',
|
|
73
79
|
summary: 'Request that a page be tested',
|
|
74
|
-
bodySchema: z.object(
|
|
75
|
-
responseSchema:
|
|
80
|
+
bodySchema: z.object(requestNewTestSchema),
|
|
81
|
+
responseSchema: requestNewTestResponseSchema
|
|
76
82
|
},
|
|
77
83
|
{
|
|
78
84
|
operationId: 'requestRetest',
|
|
@@ -93,5 +99,37 @@ export const routes = [
|
|
|
93
99
|
summary: 'Request a feature improvement or new feature',
|
|
94
100
|
bodySchema: z.object(requestFeatureSchema),
|
|
95
101
|
responseSchema: requestFeatureResponseSchema
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
operationId: 'orderNewTest',
|
|
105
|
+
method: 'post',
|
|
106
|
+
path: '/api/orderNewTest',
|
|
107
|
+
summary: 'Order that a page be tested immediately, without manual approval',
|
|
108
|
+
bodySchema: z.object(orderNewTestSchema),
|
|
109
|
+
responseSchema: orderNewTestResponseSchema
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
operationId: 'orderRetest',
|
|
113
|
+
method: 'post',
|
|
114
|
+
path: '/api/orderRetest/{timeStamp}/{jobID}',
|
|
115
|
+
summary: 'Order that a page be retested immediately, without manual approval',
|
|
116
|
+
pathParamsSchema: z.object({
|
|
117
|
+
timeStamp: orderRetestSchema.timeStamp,
|
|
118
|
+
jobID: orderRetestSchema.jobID
|
|
119
|
+
}),
|
|
120
|
+
bodySchema: z.object({reason: orderRetestSchema.reason}),
|
|
121
|
+
responseSchema: orderRetestResponseSchema
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
operationId: 'awaitTest',
|
|
125
|
+
method: 'post',
|
|
126
|
+
path: '/api/awaitTest/{timeStamp}/{jobID}',
|
|
127
|
+
summary: 'Wait for a job ordered via orderNewTest or orderRetest to complete',
|
|
128
|
+
pathParamsSchema: z.object(awaitTestSchema),
|
|
129
|
+
// No caller-supplied fields beyond the path parameters; an empty body schema (rather
|
|
130
|
+
// than omitting bodySchema) keeps this route consistent with the "every POST route
|
|
131
|
+
// has a bodySchema" invariant the rest of this table and its own test maintain.
|
|
132
|
+
bodySchema: z.object({}),
|
|
133
|
+
responseSchema: awaitTestResponseSchema
|
|
96
134
|
}
|
|
97
135
|
];
|
package/api/schemas.ts
CHANGED
|
@@ -41,8 +41,8 @@ export const getReportSchema = {
|
|
|
41
41
|
jobID: z.string().describe('Job identifier of the report (example: x9z)')
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
//
|
|
45
|
-
export const
|
|
44
|
+
// requestNewTest: POST /api/requestNewTest
|
|
45
|
+
export const requestNewTestSchema = {
|
|
46
46
|
description: z.string().describe('1- to 100-character description of the page conforming to the naming convention used in the listReports output'),
|
|
47
47
|
URL: z.string().describe('12- to 300-character URL of the page, including the https:// scheme and any query'),
|
|
48
48
|
reason: z.string().describe('20- to 100-character reason why the page should be tested')
|
|
@@ -60,6 +60,26 @@ export const requestFeatureSchema = {
|
|
|
60
60
|
feature: z.string().describe('20- to 1000-character description of requested feature improvement or new feature')
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
+
// orderNewTest: POST /api/orderNewTest
|
|
64
|
+
export const orderNewTestSchema = {
|
|
65
|
+
description: z.string().describe('1- to 100-character description of the page conforming to the naming convention used in the listReports output'),
|
|
66
|
+
URL: z.string().describe('12- to 300-character URL of the page, including the https:// scheme and any query'),
|
|
67
|
+
reason: z.string().describe('20- to 100-character reason why the page should be tested')
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// orderRetest: POST /api/orderRetest/{timeStamp}/{jobID}
|
|
71
|
+
export const orderRetestSchema = {
|
|
72
|
+
timeStamp: z.string().describe('Timestamp of the latest report about the page in YYMMDDTHHmm format (example: 260503T0432)'),
|
|
73
|
+
jobID: z.string().describe('Job identifier of the latest report about the page (example: x9z)'),
|
|
74
|
+
reason: z.string().describe('20- to 100-character reason why the page should be retested')
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// awaitTest: POST /api/awaitTest
|
|
78
|
+
export const awaitTestSchema = {
|
|
79
|
+
timeStamp: z.string().describe('Timestamp of the ordered job, from the report identifier returned by orderNewTest, in YYMMDDTHHmm format (example: 260503T0432)'),
|
|
80
|
+
jobID: z.string().describe('Job identifier of the ordered job, from the report identifier returned by orderNewTest (example: x9z)')
|
|
81
|
+
};
|
|
82
|
+
|
|
63
83
|
// listReports takes no input; omitted (mcp.ts already uses inputSchema: {}).
|
|
64
84
|
|
|
65
85
|
// RESPONSE SCHEMAS
|
|
@@ -93,32 +113,40 @@ const toolsFactsSchema = z.object({
|
|
|
93
113
|
'web users can obtain similar functionalities at': z.string()
|
|
94
114
|
}).meta({id: 'ToolsFacts'});
|
|
95
115
|
|
|
96
|
-
// Identifies a related request (e.g., the closest ancestor request in the drill-down
|
|
116
|
+
// Identifies a related request (e.g., the closest ancestor request in the drill-down
|
|
117
|
+
// hierarchy). method is usually GET (every drill-down tool's ancestor is a read), except
|
|
118
|
+
// for orderNewTest/orderRetest, whose own ancestor is a GET, and awaitTest, whose ancestors
|
|
119
|
+
// (orderNewTest and orderRetest) are both POST (they create a job).
|
|
97
120
|
const requestReferenceSchema = z.object({
|
|
98
121
|
'tool name': z.string(),
|
|
99
122
|
description: z.string(),
|
|
100
|
-
method: z.
|
|
123
|
+
method: z.enum(['GET', 'POST']),
|
|
101
124
|
URL: z.string()
|
|
102
125
|
}).meta({id: 'RequestReference'});
|
|
103
126
|
|
|
104
127
|
// GET endpoints have no 'body'; POST endpoints get a request-specific body schema.
|
|
105
|
-
|
|
128
|
+
// ancestorSchema overrides the default single-ancestor shape for a tool whose closest
|
|
129
|
+
// ancestor is not unique: awaitTest can be reached from either orderNewTest or orderRetest,
|
|
130
|
+
// and the identifier alone does not say which, so it reports both as an array rather
|
|
131
|
+
// than arbitrarily picking one.
|
|
132
|
+
const thisRequestSchema = (method: string, bodySchema?: ZodTypeAny, ancestorSchema: ZodTypeAny = requestReferenceSchema.nullable()) => z.object({
|
|
106
133
|
description: z.string(),
|
|
107
134
|
method: z.literal(method),
|
|
108
135
|
URL: z.string(),
|
|
109
136
|
...(bodySchema ? {body: bodySchema} : {}),
|
|
110
|
-
'closest ancestor request':
|
|
137
|
+
'closest ancestor request': ancestorSchema
|
|
111
138
|
});
|
|
112
139
|
|
|
113
140
|
const envelope = <T extends ZodTypeAny>(
|
|
114
141
|
method: string,
|
|
115
142
|
responseContentSchema: T,
|
|
116
143
|
bodySchema?: ZodTypeAny,
|
|
117
|
-
similarWebSchema: ZodTypeAny = similarWebRequestsSchema
|
|
144
|
+
similarWebSchema: ZodTypeAny = similarWebRequestsSchema,
|
|
145
|
+
ancestorSchema?: ZodTypeAny
|
|
118
146
|
) => z.object({
|
|
119
147
|
'tool collection': toolsFactsSchema,
|
|
120
148
|
'tool name': z.string(),
|
|
121
|
-
'this request': thisRequestSchema(method, bodySchema),
|
|
149
|
+
'this request': thisRequestSchema(method, bodySchema, ancestorSchema),
|
|
122
150
|
'URLs of similar requests for web users': similarWebSchema,
|
|
123
151
|
'response metadata': responseMetadataSchema,
|
|
124
152
|
'response content': responseContentSchema
|
|
@@ -268,7 +296,7 @@ export const getReportResponseSchema = envelope('GET', z.object({
|
|
|
268
296
|
'full report': z.unknown().describe('The full raw Testaro/Testilo report JSON, copied verbatim, or an error object if it could not be retrieved.')
|
|
269
297
|
}));
|
|
270
298
|
|
|
271
|
-
export const
|
|
299
|
+
export const requestNewTestResponseSchema = envelope(
|
|
272
300
|
'POST',
|
|
273
301
|
z.object({
|
|
274
302
|
'details about your request': z.union([
|
|
@@ -285,7 +313,7 @@ export const requestTestResponseSchema = envelope(
|
|
|
285
313
|
'how a web user can check for completion': z.string()
|
|
286
314
|
}).nullable()
|
|
287
315
|
}),
|
|
288
|
-
z.object(
|
|
316
|
+
z.object(requestNewTestSchema)
|
|
289
317
|
);
|
|
290
318
|
|
|
291
319
|
export const requestRetestResponseSchema = envelope(
|
|
@@ -320,3 +348,85 @@ export const requestFeatureResponseSchema = envelope(
|
|
|
320
348
|
z.object(requestFeatureSchema),
|
|
321
349
|
z.null()
|
|
322
350
|
);
|
|
351
|
+
|
|
352
|
+
// Identifies the report a successfully ordered or awaited job will produce, once complete.
|
|
353
|
+
const reportIdentifierSchema = z.object({
|
|
354
|
+
timeStamp: z.string().describe('Timestamp to pass to listIssues once the report is ready.'),
|
|
355
|
+
jobID: z.string().describe('Job identifier to pass to listIssues once the report is ready.')
|
|
356
|
+
}).meta({id: 'ReportIdentifier'});
|
|
357
|
+
|
|
358
|
+
export const orderNewTestResponseSchema = envelope(
|
|
359
|
+
'POST',
|
|
360
|
+
z.object({
|
|
361
|
+
'details about your order': z.union([
|
|
362
|
+
z.object({error: z.string()}),
|
|
363
|
+
z.object({
|
|
364
|
+
'date and time received': z.string(),
|
|
365
|
+
'page to be tested': z.object({description: z.string(), URL: z.string()}),
|
|
366
|
+
'reason why the page should be tested': z.string()
|
|
367
|
+
})
|
|
368
|
+
]),
|
|
369
|
+
'disposition of your order': z.object({
|
|
370
|
+
'what happens next': z.string(),
|
|
371
|
+
'report identifier': reportIdentifierSchema.nullable(),
|
|
372
|
+
'how you can check for completion': z.string(),
|
|
373
|
+
'do you want to wait for completion': z.string()
|
|
374
|
+
}).nullable()
|
|
375
|
+
}),
|
|
376
|
+
z.object(orderNewTestSchema),
|
|
377
|
+
z.object({
|
|
378
|
+
'this request': z.null(),
|
|
379
|
+
'closest ancestor request': z.string().nullable()
|
|
380
|
+
})
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
export const orderRetestResponseSchema = envelope(
|
|
384
|
+
'POST',
|
|
385
|
+
z.object({
|
|
386
|
+
'details about your order': z.union([
|
|
387
|
+
z.object({error: z.string()}),
|
|
388
|
+
z.object({
|
|
389
|
+
'date and time received': z.string(),
|
|
390
|
+
'page to be retested': z.object({description: z.string(), URL: z.string()}),
|
|
391
|
+
'reason why the page should be retested': z.string()
|
|
392
|
+
})
|
|
393
|
+
]),
|
|
394
|
+
'disposition of your order': z.object({
|
|
395
|
+
'what happens next': z.string(),
|
|
396
|
+
'report identifier': reportIdentifierSchema.nullable(),
|
|
397
|
+
'how you can check for completion': z.string(),
|
|
398
|
+
'do you want to wait for completion': z.string()
|
|
399
|
+
}).nullable()
|
|
400
|
+
}),
|
|
401
|
+
z.object({reason: orderRetestSchema.reason}),
|
|
402
|
+
z.object({
|
|
403
|
+
'this request': z.null(),
|
|
404
|
+
'closest ancestor request': z.string().nullable()
|
|
405
|
+
})
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
export const awaitTestResponseSchema = envelope(
|
|
409
|
+
'POST',
|
|
410
|
+
z.object({
|
|
411
|
+
'details about your wait': z.union([
|
|
412
|
+
z.object({error: z.string()}),
|
|
413
|
+
z.object({
|
|
414
|
+
'report identifier': reportIdentifierSchema
|
|
415
|
+
})
|
|
416
|
+
]),
|
|
417
|
+
'disposition of your wait': z.object({
|
|
418
|
+
outcome: z.enum(['completed', 'failed', 'timedOut']),
|
|
419
|
+
'what happened': z.string(),
|
|
420
|
+
'how you can check for completion': z.string()
|
|
421
|
+
}).nullable()
|
|
422
|
+
}),
|
|
423
|
+
undefined,
|
|
424
|
+
z.object({
|
|
425
|
+
'this request': z.null(),
|
|
426
|
+
'closest ancestor request': z.null()
|
|
427
|
+
}),
|
|
428
|
+
// awaitTest can be reached from either orderNewTest or orderRetest, and its own
|
|
429
|
+
// parameters (timeStamp/jobID) do not say which one produced them, so both possible
|
|
430
|
+
// ancestors are reported, rather than arbitrarily naming just one as "the" ancestor.
|
|
431
|
+
z.array(requestReferenceSchema).length(2)
|
|
432
|
+
);
|
package/api/util.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
objectSort,
|
|
16
16
|
ruleEngines
|
|
17
17
|
} from '../util.ts';
|
|
18
|
-
import type {ReportExtract} from '../util.ts';
|
|
18
|
+
import type {ReportExtract, TestRequestResult} from '../util.ts';
|
|
19
19
|
import {issues as issueSpecs} from 'testaro-issues';
|
|
20
20
|
|
|
21
21
|
// TYPES
|
|
@@ -29,6 +29,15 @@ export type ReportBasics = {
|
|
|
29
29
|
'whether a later report about the same page exists': boolean;
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
+
// The disposition of a test/retest/instant-test request, as returned by
|
|
33
|
+
// buildRequestDisposition, or null when the request itself was invalid (in which case
|
|
34
|
+
// details about your request already carries the error, and no disposition applies).
|
|
35
|
+
export type RequestDisposition = {
|
|
36
|
+
'what happens next': string;
|
|
37
|
+
'how you can check for completion': string;
|
|
38
|
+
'how a web user can check for completion': string;
|
|
39
|
+
} | null;
|
|
40
|
+
|
|
32
41
|
// FUNCTIONS
|
|
33
42
|
|
|
34
43
|
// Returns the base URL of this Kilotest host.
|
|
@@ -50,7 +59,7 @@ export const getToolsFacts = () => ({
|
|
|
50
59
|
'level 4': 'Use the listDiagnoses tool to get a list of diagnoses of how one element on one page exhibited one issue in one report.',
|
|
51
60
|
},
|
|
52
61
|
'how to generate more findings': {
|
|
53
|
-
'new testing': 'If no report is available yet about a page, use the
|
|
62
|
+
'new testing': 'If no report is available yet about a page, use the requestNewTest tool to request that it be tested.',
|
|
54
63
|
'retesting': 'If the listIssues tool shows that the latest report about a page is obsolete, because the page has been revised or for another reason, use the requestRetest tool to request that the page be retested.',
|
|
55
64
|
'latency': 'Requests for testing and retesting are usually approved and fulfilled within one day.',
|
|
56
65
|
'confirmation': 'Use the listReports tool to determine whether a requested new report exists. There is currently no process for notification of the outcome of requests.'
|
|
@@ -59,6 +68,54 @@ export const getToolsFacts = () => ({
|
|
|
59
68
|
'URL': `${getThisHost()}/mcp`,
|
|
60
69
|
'web users can obtain similar functionalities at': getThisHost()
|
|
61
70
|
});
|
|
71
|
+
// Returns the human-readable reason a test/retest/instant-test request was not
|
|
72
|
+
// processed, given the result processTestRequest returned. requestResult is always a
|
|
73
|
+
// rejection reason here ('description', 'url', 'queueFull', or an unrecognized value
|
|
74
|
+
// meaning 'duplicate'); callers pass their own extra branch for a requestResult value
|
|
75
|
+
// specific to their own request type ('reportExists' for requestNewTest, 'superseded' for
|
|
76
|
+
// requestRetest), since that is the one branch not shared between them.
|
|
77
|
+
export const getRequestFailureReason = (
|
|
78
|
+
requestResult: Exclude<TestRequestResult, 'ok'>,
|
|
79
|
+
extraBranch?: {result: TestRequestResult; reason: string}
|
|
80
|
+
): string => {
|
|
81
|
+
if (requestResult === 'description') {
|
|
82
|
+
return 'a request to test a page with the same description is already approved.';
|
|
83
|
+
}
|
|
84
|
+
if (requestResult === 'url') {
|
|
85
|
+
return 'a request to test a page with the same URL is already approved.';
|
|
86
|
+
}
|
|
87
|
+
if (extraBranch && requestResult === extraBranch.result) {
|
|
88
|
+
return extraBranch.reason;
|
|
89
|
+
}
|
|
90
|
+
if (requestResult === 'queueFull') {
|
|
91
|
+
return 'too many requests are awaiting approval right now. Please try again later, ' +
|
|
92
|
+
'or post your request at https://github.com/jrpool/kilotest/issues or email info@kilotest.com.';
|
|
93
|
+
}
|
|
94
|
+
return 'an identical request is already awaiting approval.';
|
|
95
|
+
};
|
|
96
|
+
// Returns the disposition of a test/retest/instant-test request: an 'ok' disposition
|
|
97
|
+
// describing how to check for completion, or a rejection disposition naming why the
|
|
98
|
+
// request was not processed. Shared by requestNewTest, requestRetest, and orderNewTest,
|
|
99
|
+
// whose only differences are the wording of the 3 completion-related sentences and, for
|
|
100
|
+
// a rejection, the one extra requestResult branch getRequestFailureReason takes.
|
|
101
|
+
export const buildRequestDisposition = (
|
|
102
|
+
requestResult: TestRequestResult,
|
|
103
|
+
okText: {whatHappensNext: string; howToCheck: string; howWebUserChecks: string},
|
|
104
|
+
extraBranch?: {result: TestRequestResult; reason: string}
|
|
105
|
+
): RequestDisposition => {
|
|
106
|
+
if (requestResult === 'ok') {
|
|
107
|
+
return {
|
|
108
|
+
'what happens next': okText.whatHappensNext,
|
|
109
|
+
'how you can check for completion': okText.howToCheck,
|
|
110
|
+
'how a web user can check for completion': okText.howWebUserChecks
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
'what happens next': `Your request will not be processed, because ${getRequestFailureReason(requestResult, extraBranch)}`,
|
|
115
|
+
'how you can check for completion': 'Not applicable.',
|
|
116
|
+
'how a web user can check for completion': 'Not applicable.'
|
|
117
|
+
};
|
|
118
|
+
};
|
|
62
119
|
// Returns the facts about a rule engine.
|
|
63
120
|
export const getRuleEngineFacts = (ruleEngineID: string) => {
|
|
64
121
|
const ruleEngineData = ruleEngines[ruleEngineID] || [null, null];
|
package/api/version.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
/*
|
|
2
2
|
version.ts
|
|
3
|
-
Single source of truth for the version of the Kilotest API and MCP tool contract, consumed by mcp.ts and scripts/generate-openapi.ts.
|
|
3
|
+
Single source of truth for the version of the Kilotest API and MCP tool contract, consumed by mcp.ts and scripts/generate-openapi.ts. Re-exports package.json's version field verbatim, rather than hand-maintaining a separate literal here, so MCP and OpenAPI consumers see package.json's version exactly, including minor/patch bumps from changes that do not affect the API contract; this is a deliberate choice, not an oversight.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
import pkg from '../package.json' with {type: 'json'};
|
|
9
|
+
|
|
6
10
|
// CONSTANTS
|
|
7
11
|
|
|
8
|
-
export const version =
|
|
12
|
+
export const version = pkg.version;
|
package/capability.md
CHANGED
|
@@ -6,7 +6,7 @@ Web-page front-end quality audit. Kilotest tests web pages for front-end quality
|
|
|
6
6
|
|
|
7
7
|
## Purpose
|
|
8
8
|
|
|
9
|
-
Language models and AI platforms often need accurate, page-specific data about the front-end quality of a web page to answer user questions. Rule engines, such as `axe-core`, `QualWeb`, and `WAVE`, provide such data, but with large coverage gaps. Orchestrating and reconciling numerous complementary rule engines would be difficult. Kilotest solves that problem by testing pages with an ensemble of numerous independent rule engines, making their results comparable, saving the results as JSON reports, mining the reports for facts at selectable levels of detail.
|
|
9
|
+
Language models and AI platforms often need accurate, page-specific data about the front-end quality of a web page to answer user questions. Rule engines, such as `axe-core`, `QualWeb`, and `WAVE`, provide such data, but with large coverage gaps. Orchestrating and reconciling numerous complementary rule engines would be difficult. Kilotest solves that problem by testing pages with an ensemble of numerous independent rule engines, making their results comparable, saving the results as JSON reports, and mining the reports for facts at selectable levels of detail.
|
|
10
10
|
|
|
11
11
|
## Interfaces
|
|
12
12
|
|
|
@@ -26,17 +26,20 @@ A list of available reports.
|
|
|
26
26
|
|
|
27
27
|
## Subsequent inputs
|
|
28
28
|
|
|
29
|
-
- If there is no report about the wanted page, a request
|
|
30
|
-
- If there is a report about the wanted page, requests for findings from the report at successive levels of detail.
|
|
29
|
+
- If there is no report about the wanted page, a request or order for a new test, with a page description, its URL, and a reason for testing it.
|
|
30
|
+
- If there is already a report about the wanted page, requests for findings from the report at successive levels of detail, or, if the report is deemed obsolete, a request or order for a retest, with a reason for retesting it.
|
|
31
|
+
- Complaints and suggestions about features of Kilotest.
|
|
31
32
|
|
|
32
33
|
## Subsequent outputs
|
|
33
34
|
|
|
34
|
-
- From a request
|
|
35
|
+
- From a **request** for a new test or a retest: if valid, confirmation of receipt. If the request is manager-approved and executed, the list of available reports will include the new report, typically in 1 hour to 1 day.
|
|
36
|
+
- From an **order** for a new test or a retest: if valid, confirmation of receipt and immediate approval or rejection. If the request is approved, the output will include the identifier of the forthcoming report and two methods for getting the report: (1) allowing time (2 to 4 minutes) for completion of the report and then requesting it, and (2) making a request whose response will await the report and then link to it. Orders are enabled in the MCP and API interfaces only.
|
|
35
37
|
- From a request for findings: Details about issues reported, related WCAG standards, what rules were violated, and which DOM elements violated them.
|
|
38
|
+
- From complaints and suggestions about features: acknowledgment of receipt and of the manager being notified.
|
|
36
39
|
|
|
37
40
|
## Side effects
|
|
38
41
|
|
|
39
|
-
Kilotest reads the specified web page during testing. It does not modify the target site.
|
|
42
|
+
Kilotest reads the specified web page during testing. It does not modify the target site.
|
|
40
43
|
|
|
41
44
|
## Cost
|
|
42
45
|
|
|
@@ -49,3 +52,7 @@ MIT.
|
|
|
49
52
|
## Repository
|
|
50
53
|
|
|
51
54
|
`https://github.com/jrpool/kilotest`
|
|
55
|
+
|
|
56
|
+
## Privacy
|
|
57
|
+
|
|
58
|
+
Kilotest requires no account. See the [privacy policy](https://kilotest.com/privacy.html).
|
package/docs/SERVICE.md
CHANGED
|
@@ -270,7 +270,7 @@ kilotest.com {
|
|
|
270
270
|
}
|
|
271
271
|
@allowedPOST {
|
|
272
272
|
method POST
|
|
273
|
-
path /mcp /requestRetest.html/* /
|
|
273
|
+
path /mcp /requestRetest.html/* /requestNewTest.html /requestAction.html /reannotate.html /renewWCAG.html /ai0BalanceForm.html /deleteNotesForm.html /expungeReportsForm.html /hideReportForm.html /metrics.html /pruneReportsForm.html /rewindReportsForm.html /showHiddenReportsForm.html /unhideReportForm.html /api/* /tutorialWebComment.html /tutorialAIComment.html /worker/job /worker/report
|
|
274
274
|
}
|
|
275
275
|
# Respond to OPTIONS requests.
|
|
276
276
|
@allowedOPTIONS method OPTIONS
|
|
@@ -395,15 +395,13 @@ Alerts are sent to the maintainer in the following conditions:
|
|
|
395
395
|
|
|
396
396
|
5. **Requested full report unavailable**: When a user requests a full report via the API but the report cannot be retrieved (e.g., the report file is missing from disk), an alert is sent with subject `"Kilotest: requested full report unavailable"`, including details on which report was requested and why it could not be retrieved.
|
|
397
397
|
|
|
398
|
-
6. **
|
|
398
|
+
6. **Request for a test or retest received**: When a user submits a request to test or retest a page, whether through the web interface (`/requestNewTest.html` or `/requestRetest.html`) or the API (the `requestNewTest` or `requestRetest` operation), an alert is sent with subject `"Kilotest: request for a new test awaits approval"` or `"Kilotest: request for a retest awaits approval"`, including the target URL, description, and the user's stated reason for the request. Both the UI and API paths funnel through the same underlying request-queuing function, so the subject and the rest of this behavior are identical regardless of which path the request arrived through. A request is treated as a duplicate, and no alert is sent for it, only if a request already pending in `db/jobs/testRequests.json`, i.e., submitted but not yet manually approved by the maintainer into an actual job, for the same URL has the identical description; this check ignores the stated reason and the test-versus-retest distinction, and is not limited to a recent time window, it compares against every request still pending approval for that URL. Once the maintainer approves a pending request (turning it into a job via `/enqueue.html`), all pending requests for that URL are cleared, so this check cannot detect a duplicate against a request that has already been approved, is currently running, or has already completed; an identical request submitted after approval alerts again.
|
|
399
399
|
|
|
400
|
-
7. **
|
|
400
|
+
7. **MCP feature request received**: When a user submits a feature request via the MCP server's `requestFeature` tool, an alert is sent with subject `"MCP feature request received"`, including the text of the feature request, and the request is also appended to `db/featureRequests.json`, the same way tutorial comments are appended to their own files (see item 1), so that a manager can review or delete it later via `/deleteNotesForm.html`.
|
|
401
401
|
|
|
402
|
-
8. **
|
|
402
|
+
8. **WAVE balance low**: When Kilotest detects that the account balance for the WAVE API service (used for accessibility testing) has fallen below a threshold, an alert is sent with subject `"Kilotest: WAVE balance low"`, indicating the number of credits remaining.
|
|
403
403
|
|
|
404
|
-
9. **
|
|
405
|
-
|
|
406
|
-
10. **AI service balance low**: When Kilotest detects that the account balance for an AI service account (such as Anthropic) has fallen below a threshold, an alert is sent with subject `"Kilotest: AI service [N] balance low"`, indicating the approximate remaining balance in dollars and the estimated cost per job.
|
|
404
|
+
9. **AI service balance low**: When Kilotest detects that the account balance for an AI service account (such as Anthropic) has fallen below a threshold, an alert is sent with subject `"Kilotest: AI service [N] balance low"`, indicating the approximate remaining balance in dollars and the estimated cost per job.
|
|
407
405
|
|
|
408
406
|
### Alert configuration
|
|
409
407
|
|
|
@@ -502,7 +500,7 @@ Kilotest records basic counts of how it is used, so the maintainer can answer qu
|
|
|
502
500
|
|
|
503
501
|
- The generic `.html` GET dispatch in `index.ts`, once per successfully served web page, recorded under the `pageViews` category by page name (for example `tutorialWeb`, `listReports`), unless the page is a manager-only page (see "Manager page activity" below), in which case it is recorded there instead.
|
|
504
502
|
- The home page (`/` and `/index.html`), served by its own branch in `index.ts` rather than through the generic `.html` dispatch, recorded under `pageViews` as `index`.
|
|
505
|
-
- Each of the 9 MCP tool handlers in `mcp.ts`, once per successful tool call, recorded under the `mcpToolCalls` category by tool name (for example `listReports`, `
|
|
503
|
+
- Each of the 9 MCP tool handlers in `mcp.ts`, once per successful tool call, recorded under the `mcpToolCalls` category by tool name (for example `listReports`, `requestNewTest`).
|
|
506
504
|
- The `/api/*` GET and POST service branches in `index.ts`, once per call, recorded under the `apiOperations` category by operation name.
|
|
507
505
|
|
|
508
506
|
`db/metrics.json` also stores a `since` time stamp, set when the file is first created, so the counts can be read as "since this date" rather than assumed to cover Kilotest's entire history. `getMetrics()` backfills any category absent from an existing `db/metrics.json` (for example one written before a category such as `managerActivity` existed), so an older file remains readable rather than causing every subsequent request to error.
|
package/env.example
CHANGED
|
@@ -47,6 +47,21 @@ TUTORIAL_WEB_COMMENTS_MAX=__placeholder__
|
|
|
47
47
|
TUTORIAL_AI_COMMENTS_MAX=__placeholder__
|
|
48
48
|
# Maximum number of feature requests held via the MCP server's requestFeature tool.
|
|
49
49
|
FEATURE_REQUESTS_MAX=__placeholder__
|
|
50
|
+
# Maximum number of jobs allowed in the job queue (db/jobs/queue) at once. Shared by every
|
|
51
|
+
# path that enqueues a job: manager approval of a pending test/retest request, and the
|
|
52
|
+
# orderNewTest/orderRetest MCP tools, which enqueue directly.
|
|
53
|
+
JOB_QUEUE_MAX=__placeholder__
|
|
54
|
+
|
|
55
|
+
# Timing for the awaitTest MCP tool's wait on a job ordered via orderNewTest/orderRetest.
|
|
56
|
+
# Defaults: 2000ms poll interval, 300000ms (5 minutes) maximum wait. The maximum wait
|
|
57
|
+
# deliberately exceeds the "occasionally up to 4 minutes" callers are told to expect, to
|
|
58
|
+
# leave margin so a job that is genuinely still running is not mistaken for one that
|
|
59
|
+
# never will finish. Lower values are useful for local testing.
|
|
60
|
+
|
|
61
|
+
# Milliseconds between polls while awaitTest waits for a job to complete.
|
|
62
|
+
AWAIT_JOB_POLL_MS=__placeholder__
|
|
63
|
+
# Maximum milliseconds awaitTest waits before giving up.
|
|
64
|
+
AWAIT_JOB_TIMEOUT_MS=__placeholder__
|
|
50
65
|
|
|
51
66
|
# Alert configuration:
|
|
52
67
|
|
package/index.html
CHANGED
|
@@ -90,6 +90,7 @@
|
|
|
90
90
|
<li><a href="listReports.html">Which web pages have been tested?</a></li>
|
|
91
91
|
<li><a href="listTopIssues.html">Which issues have been reported?</a></li>
|
|
92
92
|
<li><a href="manage.html">What can Kilotest managers do?</a></li>
|
|
93
|
+
<li><a href="privacy.html">What is the privacy policy?</a></li>
|
|
93
94
|
</ul>
|
|
94
95
|
</main>
|
|
95
96
|
</body>
|