@jrpool/kilotest 52.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.
- package/.devin/config.local.json +4 -1
- package/.github/workflows/ci.yml +3 -0
- 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/requestFeature.test.ts +36 -0
- package/api/requestFeature.ts +26 -4
- package/api/{requestTest.test.ts → requestNewTest.test.ts} +57 -23
- package/api/{requestTest.ts → requestNewTest.ts} +31 -36
- package/api/requestRetest.test.ts +24 -6
- package/api/requestRetest.ts +10 -29
- 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 +9 -6
- package/docs/AI-TOOLS.md +3 -3
- package/docs/SERVICE.md +19 -8
- package/env.example +39 -0
- package/index.test.ts +146 -24
- package/index.ts +129 -36
- package/lhm.plugin.json +1 -1
- package/linkValidation.test.ts +2 -2
- package/llms-full.txt +35 -4
- package/llms.txt +6 -3
- package/mcp.test.ts +109 -12
- package/mcp.ts +146 -26
- package/memory/MEMORY.md +1 -0
- package/memory/plan_issue_111_instant_test.md +198 -0
- package/openapi.yaml +456 -9
- package/package.json +2 -1
- package/routes.test.ts +1 -1
- package/routing.test.ts +2 -2
- package/scripts/generate-openapi.ts +1 -1
- package/server.json +1 -1
- package/sitemap.xml +1 -1
- package/smokeTest.ts +2 -2
- package/util.test.ts +486 -2
- package/util.ts +276 -45
- package/web/listReports/index.html +1 -1
- package/web/listReports/listReports.test.ts +1 -1
- package/web/{requestTest → requestNewTest}/index.ts +10 -1
- package/web/{requestTest/requestTest.test.ts → requestNewTest/requestNewTest.test.ts} +18 -2
- package/web/{requestTestForm → requestNewTestForm}/index.html +1 -1
- package/web/{requestTestForm/requestTestForm.test.ts → requestNewTestForm/requestNewTestForm.test.ts} +6 -6
- package/web/requestRetest/index.ts +9 -0
- package/web/requestRetest/requestRetest.test.ts +14 -0
- package/web/tutorialAI/index.ts +25 -3
- package/web/tutorialAI/tutorialAI.test.ts +32 -0
- package/web/tutorialWeb/index.ts +25 -3
- package/web/tutorialWeb/tutorialWeb.test.ts +32 -0
- /package/web/{requestTest → requestNewTest}/index.html +0 -0
- /package/web/{requestTestForm → requestNewTestForm}/index.ts +0 -0
|
@@ -100,6 +100,38 @@ test('handleComment saves a sanitized comment and returns ok', {timeout: 500}, a
|
|
|
100
100
|
assert.ok(comments[0].timeStamp);
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
+
test('handleComment returns an error with a fallback channel when the comments cap is reached', {timeout: 500}, async () => {
|
|
104
|
+
// Fill comments.json with 20 distinct comments, reaching the cap.
|
|
105
|
+
const filler = Array.from({length: 20}, (_, i) => ({
|
|
106
|
+
timeStamp: '260101T0000', content: `Filler comment number ${i}, long enough to pass the length check.`
|
|
107
|
+
}));
|
|
108
|
+
await fs.writeFile(getCommentsPath(), JSON.stringify(filler));
|
|
109
|
+
const result = await handleComment('One comment too many, long enough to pass the length check.');
|
|
110
|
+
assert.equal(result.status, 'error');
|
|
111
|
+
assert.ok(result.message?.startsWith('Too many comments are awaiting review right now.'));
|
|
112
|
+
assert.ok(result.message?.includes('https://github.com/jrpool/kilotest/issues'));
|
|
113
|
+
assert.ok(result.message?.includes('info@kilotest.com'));
|
|
114
|
+
// Clean up.
|
|
115
|
+
await fs.writeFile(getCommentsPath(), '[]\n');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('handleComment never rejects for a full inbox when TUTORIAL_AI_COMMENTS_MAX is 0 (no limit)', {timeout: 500}, async () => {
|
|
119
|
+
process.env.TUTORIAL_AI_COMMENTS_MAX = '0';
|
|
120
|
+
try {
|
|
121
|
+
// Fill comments.json past the default cap.
|
|
122
|
+
const filler = Array.from({length: 25}, (_, i) => ({
|
|
123
|
+
timeStamp: '260101T0000', content: `Filler comment number ${i}, long enough to pass the length check.`
|
|
124
|
+
}));
|
|
125
|
+
await fs.writeFile(getCommentsPath(), JSON.stringify(filler));
|
|
126
|
+
const result = await handleComment('One more comment, long enough to pass the length check.');
|
|
127
|
+
assert.equal(result.status, 'ok');
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
delete process.env.TUTORIAL_AI_COMMENTS_MAX;
|
|
131
|
+
await fs.writeFile(getCommentsPath(), '[]\n');
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
103
135
|
test('handleComment strips HTML tags and control characters', {timeout: 500}, async () => {
|
|
104
136
|
await fs.writeFile(getCommentsPath(), '[]\n');
|
|
105
137
|
const result = await handleComment('<img src=x onerror=alert(1)>\x00\x07Hello');
|
package/web/tutorialWeb/index.ts
CHANGED
|
@@ -6,13 +6,20 @@
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
8
|
import {sendAlert} from '../../alerts.ts';
|
|
9
|
-
import {checkCommentDuplicate, checkCommentLength, getJSON, getNowStamp} from '../../util.ts';
|
|
9
|
+
import {checkCommentDuplicate, checkCommentLength, describeMax, getEnvMax, getJSON, getNowStamp} from '../../util.ts';
|
|
10
10
|
import fs from 'node:fs/promises';
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
|
|
13
13
|
// FUNCTIONS (helpers)
|
|
14
14
|
|
|
15
15
|
const getCommentsPath = () => process.env.TUTORIAL_WEB_COMMENTS_PATH || path.join(import.meta.dirname, '../../db/comments/tutorialWeb.json');
|
|
16
|
+
// Maximum number of comments held at once. Once reached, a new, distinct comment is
|
|
17
|
+
// rejected rather than stored, so the comments file and the manager alert emails it
|
|
18
|
+
// generates cannot grow without bound (modeled on a full mailbox rejecting new mail; see
|
|
19
|
+
// GitHub issue #3, abuse type 3). Configurable via TUTORIAL_WEB_COMMENTS_MAX, since
|
|
20
|
+
// different Kilotest deployments' maintainers may want a different size; a value of 0
|
|
21
|
+
// means no limit.
|
|
22
|
+
const getCommentsMax = () => getEnvMax('TUTORIAL_WEB_COMMENTS_MAX', 20);
|
|
16
23
|
|
|
17
24
|
// FUNCTIONS
|
|
18
25
|
|
|
@@ -63,6 +70,17 @@ export const handleComment = async (content: unknown) => {
|
|
|
63
70
|
// Report this.
|
|
64
71
|
return duplicateCheck;
|
|
65
72
|
}
|
|
73
|
+
// If the comments already on file have reached the cap (a cap of 0 means no limit, so
|
|
74
|
+
// the comments are never full):
|
|
75
|
+
const commentsMax = getCommentsMax();
|
|
76
|
+
if (commentsMax > 0 && comments.length >= commentsMax) {
|
|
77
|
+
// Report this, with a fallback channel, since this comment cannot be stored here.
|
|
78
|
+
return {
|
|
79
|
+
status: 'error',
|
|
80
|
+
message: 'Too many comments are awaiting review right now. Please try again later, ' +
|
|
81
|
+
'or post your comment at https://github.com/jrpool/kilotest/issues or email info@kilotest.com.'
|
|
82
|
+
};
|
|
83
|
+
}
|
|
66
84
|
// Add the comment to the existing ones.
|
|
67
85
|
comments.push({
|
|
68
86
|
timeStamp: getNowStamp(),
|
|
@@ -72,7 +90,11 @@ export const handleComment = async (content: unknown) => {
|
|
|
72
90
|
await fs.mkdir(path.dirname(commentsPath), {recursive: true});
|
|
73
91
|
// Save the revised comments.
|
|
74
92
|
await fs.writeFile(commentsPath, getJSON(comments));
|
|
75
|
-
// Send an alert to the manager
|
|
76
|
-
|
|
93
|
+
// Send an alert to the manager, including the resulting count, so a maintainer who has
|
|
94
|
+
// been away sees at a glance how urgently the comments need review.
|
|
95
|
+
await sendAlert(
|
|
96
|
+
'Kilotest: New web tutorial comment received',
|
|
97
|
+
`A new web tutorial comment has been received.\nComments now awaiting review: ${comments.length} of ${describeMax(commentsMax)}`
|
|
98
|
+
);
|
|
77
99
|
return {status: 'ok'};
|
|
78
100
|
};
|
|
@@ -103,6 +103,38 @@ test('handleComment saves a sanitized comment and returns ok', {timeout: 500}, a
|
|
|
103
103
|
assert.ok(comments[0].timeStamp);
|
|
104
104
|
});
|
|
105
105
|
|
|
106
|
+
test('handleComment returns an error with a fallback channel when the comments cap is reached', {timeout: 500}, async () => {
|
|
107
|
+
// Fill comments.json with 20 distinct comments, reaching the cap.
|
|
108
|
+
const filler = Array.from({length: 20}, (_, i) => ({
|
|
109
|
+
timeStamp: '260101T0000', content: `Filler comment number ${i}, long enough to pass the length check.`
|
|
110
|
+
}));
|
|
111
|
+
await fs.writeFile(getCommentsPath(), JSON.stringify(filler));
|
|
112
|
+
const result = await handleComment('One comment too many, long enough to pass the length check.');
|
|
113
|
+
assert.equal(result.status, 'error');
|
|
114
|
+
assert.ok(result.message?.startsWith('Too many comments are awaiting review right now.'));
|
|
115
|
+
assert.ok(result.message?.includes('https://github.com/jrpool/kilotest/issues'));
|
|
116
|
+
assert.ok(result.message?.includes('info@kilotest.com'));
|
|
117
|
+
// Clean up.
|
|
118
|
+
await fs.writeFile(getCommentsPath(), '[]\n');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('handleComment never rejects for a full inbox when TUTORIAL_WEB_COMMENTS_MAX is 0 (no limit)', {timeout: 500}, async () => {
|
|
122
|
+
process.env.TUTORIAL_WEB_COMMENTS_MAX = '0';
|
|
123
|
+
try {
|
|
124
|
+
// Fill comments.json past the default cap.
|
|
125
|
+
const filler = Array.from({length: 25}, (_, i) => ({
|
|
126
|
+
timeStamp: '260101T0000', content: `Filler comment number ${i}, long enough to pass the length check.`
|
|
127
|
+
}));
|
|
128
|
+
await fs.writeFile(getCommentsPath(), JSON.stringify(filler));
|
|
129
|
+
const result = await handleComment('One more comment, long enough to pass the length check.');
|
|
130
|
+
assert.equal(result.status, 'ok');
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
delete process.env.TUTORIAL_WEB_COMMENTS_MAX;
|
|
134
|
+
await fs.writeFile(getCommentsPath(), '[]\n');
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
106
138
|
test('handleComment strips HTML tags and control characters', {timeout: 500}, async () => {
|
|
107
139
|
await fs.writeFile(getCommentsPath(), '[]\n');
|
|
108
140
|
const result = await handleComment('<img src=x onerror=alert(1)>\x00\x07Hello');
|
|
File without changes
|
|
File without changes
|