@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.
Files changed (59) hide show
  1. package/.devin/config.local.json +4 -1
  2. package/.github/workflows/ci.yml +3 -0
  3. package/README.md +1 -1
  4. package/api/awaitTest.test.ts +114 -0
  5. package/api/awaitTest.ts +108 -0
  6. package/api/listReports.ts +2 -2
  7. package/api/orderNewTest.test.ts +213 -0
  8. package/api/orderNewTest.ts +145 -0
  9. package/api/orderRetest.test.ts +179 -0
  10. package/api/orderRetest.ts +119 -0
  11. package/api/requestFeature.test.ts +36 -0
  12. package/api/requestFeature.ts +26 -4
  13. package/api/{requestTest.test.ts → requestNewTest.test.ts} +57 -23
  14. package/api/{requestTest.ts → requestNewTest.ts} +31 -36
  15. package/api/requestRetest.test.ts +24 -6
  16. package/api/requestRetest.ts +10 -29
  17. package/api/routes.test.ts +7 -4
  18. package/api/routes.ts +45 -7
  19. package/api/schemas.ts +120 -10
  20. package/api/util.ts +59 -2
  21. package/api/version.ts +6 -2
  22. package/capability.md +9 -6
  23. package/docs/AI-TOOLS.md +3 -3
  24. package/docs/SERVICE.md +19 -8
  25. package/env.example +39 -0
  26. package/index.test.ts +146 -24
  27. package/index.ts +129 -36
  28. package/lhm.plugin.json +1 -1
  29. package/linkValidation.test.ts +2 -2
  30. package/llms-full.txt +35 -4
  31. package/llms.txt +6 -3
  32. package/mcp.test.ts +109 -12
  33. package/mcp.ts +146 -26
  34. package/memory/MEMORY.md +1 -0
  35. package/memory/plan_issue_111_instant_test.md +198 -0
  36. package/openapi.yaml +456 -9
  37. package/package.json +2 -1
  38. package/routes.test.ts +1 -1
  39. package/routing.test.ts +2 -2
  40. package/scripts/generate-openapi.ts +1 -1
  41. package/server.json +1 -1
  42. package/sitemap.xml +1 -1
  43. package/smokeTest.ts +2 -2
  44. package/util.test.ts +486 -2
  45. package/util.ts +276 -45
  46. package/web/listReports/index.html +1 -1
  47. package/web/listReports/listReports.test.ts +1 -1
  48. package/web/{requestTest → requestNewTest}/index.ts +10 -1
  49. package/web/{requestTest/requestTest.test.ts → requestNewTest/requestNewTest.test.ts} +18 -2
  50. package/web/{requestTestForm → requestNewTestForm}/index.html +1 -1
  51. package/web/{requestTestForm/requestTestForm.test.ts → requestNewTestForm/requestNewTestForm.test.ts} +6 -6
  52. package/web/requestRetest/index.ts +9 -0
  53. package/web/requestRetest/requestRetest.test.ts +14 -0
  54. package/web/tutorialAI/index.ts +25 -3
  55. package/web/tutorialAI/tutorialAI.test.ts +32 -0
  56. package/web/tutorialWeb/index.ts +25 -3
  57. package/web/tutorialWeb/tutorialWeb.test.ts +32 -0
  58. /package/web/{requestTest → requestNewTest}/index.html +0 -0
  59. /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');
@@ -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
- await sendAlert('Kilotest: New web tutorial comment received', 'A new web tutorial comment has been received.');
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