@jrpool/kilotest 46.1.2 → 48.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +13 -1
- package/.devin/config.local.json +5 -1
- package/.github/workflows/ci.yml +0 -3
- package/.github/workflows/periodic-smoke-tests.yml +30 -0
- package/.github/workflows/publish-mcp.yml +4 -4
- package/{alerts.test.js → alerts.test.ts} +17 -29
- package/{alerts.js → alerts.ts} +10 -13
- package/api/{getReport.test.js → getReport.test.ts} +9 -10
- package/api/{getReport.js → getReport.ts} +23 -24
- package/api/{listDiagnoses.test.js → listDiagnoses.test.ts} +26 -27
- package/api/{listDiagnoses.js → listDiagnoses.ts} +46 -70
- package/api/{listIssues.test.js → listIssues.test.ts} +21 -14
- package/api/{listIssues.js → listIssues.ts} +75 -74
- package/api/{listReports.test.js → listReports.test.ts} +17 -16
- package/api/listReports.ts +87 -0
- package/api/{listViolators.test.js → listViolators.test.ts} +19 -20
- package/api/{listViolators.js → listViolators.ts} +45 -70
- package/api/requestFeature.test.ts +54 -0
- package/api/{requestFeature.js → requestFeature.ts} +18 -15
- package/api/requestRetest.test.ts +144 -0
- package/api/requestRetest.ts +119 -0
- package/api/requestTest.test.ts +153 -0
- package/api/requestTest.ts +123 -0
- package/api/{routes.test.js → routes.test.ts} +5 -5
- package/api/{routes.js → routes.ts} +6 -6
- package/api/{schemas.js → schemas.ts} +69 -50
- package/api/{util.test.js → util.test.ts} +35 -14
- package/api/{util.js → util.ts} +54 -51
- package/api/version.ts +8 -0
- package/{apiIntegration.test.js → apiIntegration.test.ts} +12 -13
- package/balances.ts +87 -0
- package/{checkinfo.js → checkinfo.ts} +24 -20
- package/docs/AI-TOOLS.md +4 -4
- package/docs/BACKLOG.md +360 -0
- package/docs/SERVICE.md +170 -17
- package/docs/decisions/002-coverage-tool.md +40 -0
- package/docs/decisions/003-adr-threshold.md +34 -0
- package/docs/decisions/004-docs-directory.md +23 -0
- package/docs/decisions/005-observability.md +28 -0
- package/docs/decisions/006-git-workflow.md +28 -0
- package/docs/decisions/007-internal-testing.md +34 -0
- package/docs/decisions/008-dependencies.md +28 -0
- package/docs/decisions/009-qai-integration.md +40 -0
- package/docs/decisions/QAI-HISTORICAL-CONTEXT.md +42 -0
- package/eslint.config.mjs +27 -0
- package/index.html +3 -3
- package/{index.test.js → index.test.ts} +397 -152
- package/{index.js → index.ts} +386 -285
- package/{indexBalances.test.js → indexBalances.test.ts} +21 -17
- package/{indexServe.test.js → indexServe.test.ts} +9 -9
- package/linkValidation.test.ts +190 -0
- package/{mcp.test.js → mcp.test.ts} +53 -26
- package/{mcp.js → mcp.ts} +29 -20
- package/memory/MEMORY.md +7 -0
- package/memory/user_maintainer_context.md +24 -0
- package/openapi.yaml +115 -68
- package/package.json +35 -15
- package/pm2.config.cjs +29 -0
- package/{routes.test.js → routes.test.ts} +8 -7
- package/{routing.test.js → routing.test.ts} +11 -12
- package/scripts/{generate-openapi.js → generate-openapi.ts} +13 -13
- package/serve.ts +15 -0
- package/smokeTest.ts +234 -0
- package/test/dbFixture.ts +21 -0
- package/test/fixtures/{buildFixtures.js → buildFixtures.ts} +14 -13
- package/test/{setup.cjs → setup.ts} +2 -2
- package/tsconfig.json +16 -0
- package/util.test.ts +1129 -0
- package/util.ts +1017 -0
- package/web/ai0BalanceForm/{ai0BalanceForm.test.js → ai0BalanceForm.test.ts} +15 -15
- package/web/ai0BalanceForm/{index.js → index.ts} +10 -18
- package/web/enqueue/index.ts +56 -0
- package/web/enqueueForm/index.html +9 -9
- package/web/enqueueForm/index.ts +43 -0
- package/web/expungeReportsForm/{expungeReportsForm.test.js → expungeReportsForm.test.ts} +18 -20
- package/web/expungeReportsForm/index.ts +18 -0
- package/web/hideReportForm/{hideReportForm.test.js → hideReportForm.test.ts} +14 -14
- package/web/hideReportForm/{index.js → index.ts} +17 -21
- package/web/listDiagnoses/{index.js → index.ts} +48 -58
- package/web/listDiagnoses/{listDiagnoses.test.js → listDiagnoses.test.ts} +39 -34
- package/web/listIssues/{index.js → index.ts} +67 -92
- package/web/listIssues/{listIssues.test.js → listIssues.test.ts} +45 -51
- package/web/listReports/index.html +3 -3
- package/web/listReports/{index.js → index.ts} +74 -58
- package/web/listReports/{listReports.test.js → listReports.test.ts} +41 -43
- package/web/listRules/{index.js → index.ts} +18 -23
- package/web/listRules/{listRules.test.js → listRules.test.ts} +26 -19
- package/web/listTopIssues/{index.js → index.ts} +36 -47
- package/web/listTopIssues/{listTopIssues.test.js → listTopIssues.test.ts} +26 -17
- package/web/listViolators/{index.js → index.ts} +51 -71
- package/web/listViolators/{listViolators.test.js → listViolators.test.ts} +43 -36
- package/web/manage/index.ts +22 -0
- package/web/pruneReportsForm/index.ts +18 -0
- package/web/pruneReportsForm/{pruneReportsForm.test.js → pruneReportsForm.test.ts} +11 -11
- package/web/reannotate/index.ts +71 -0
- package/web/reannotate/{reannotate.test.js → reannotate.test.ts} +19 -22
- package/web/reannotateForm/{index.js → index.ts} +41 -50
- package/web/reannotateForm/{reannotateForm.test.js → reannotateForm.test.ts} +13 -16
- package/web/renewWCAG/{index.js → index.ts} +9 -9
- package/web/renewWCAG/{renewWCAG.test.js → renewWCAG.test.ts} +17 -20
- package/web/renewWCAGForm/index.ts +22 -0
- package/web/{expungeReportsForm/index.js → reportDeletion.ts} +57 -39
- package/web/requestRetest/index.html +7 -7
- package/web/requestRetest/index.ts +62 -0
- package/web/requestRetest/requestRetest.test.ts +130 -0
- package/web/requestRetestForm/{index.js → index.ts} +10 -16
- package/web/requestRetestForm/{requestRetestForm.test.js → requestRetestForm.test.ts} +11 -12
- package/web/requestTest/index.html +7 -7
- package/web/requestTest/index.ts +52 -0
- package/web/requestTest/{requestTest.test.js → requestTest.test.ts} +39 -24
- package/web/requestTestForm/index.html +4 -4
- package/web/requestTestForm/index.ts +21 -0
- package/web/requestTestForm/{requestTestForm.test.js → requestTestForm.test.ts} +9 -9
- package/web/rewindReportsForm/index.ts +18 -0
- package/web/rewindReportsForm/{rewindReportsForm.test.js → rewindReportsForm.test.ts} +11 -11
- package/web/tutorialAI/index.html +184 -0
- package/web/tutorialAI/index.ts +80 -0
- package/web/tutorialAI/tutorialAI.test.ts +192 -0
- package/web/{tutorial → tutorialWeb}/index.html +9 -8
- package/web/tutorialWeb/index.ts +78 -0
- package/web/tutorialWeb/tutorialWeb.test.ts +195 -0
- package/web/unhideReportForm/{index.js → index.ts} +18 -22
- package/web/unhideReportForm/{unhideReportForm.test.js → unhideReportForm.test.ts} +16 -16
- package/aceconfig.js +0 -28
- package/api/listReports.js +0 -89
- package/api/requestFeature.test.js +0 -58
- package/api/requestRetest.js +0 -96
- package/api/requestRetest.test.js +0 -80
- package/api/requestTest.js +0 -106
- package/api/requestTest.test.js +0 -92
- package/api/version.js +0 -8
- package/indexFallback.test.js +0 -33
- package/indexWorkers.test.js +0 -79
- package/jsconfig.json +0 -8
- package/pm2.config.js +0 -18
- package/smokeTest.js +0 -128
- package/util.js +0 -744
- package/util.test.js +0 -968
- package/web/enqueue/index.js +0 -67
- package/web/enqueueForm/index.js +0 -49
- package/web/manage/index.js +0 -22
- package/web/pruneReportsForm/index.js +0 -123
- package/web/reannotate/index.js +0 -58
- package/web/renewWCAGForm/index.js +0 -22
- package/web/requestRetest/index.js +0 -31
- package/web/requestRetest/requestRetest.test.js +0 -119
- package/web/requestTest/index.js +0 -24
- package/web/requestTestForm/index.js +0 -21
- package/web/rewindReportsForm/index.js +0 -121
- package/web/tutorial/index.js +0 -63
- package/web/tutorial/tutorial.test.js +0 -97
- /package/test/fixtures/db/jobs/{recs.json → testRequests.json} +0 -0
- /package/web/{tutorial → tutorialWeb}/images/newsletter-form.png +0 -0
package/docs/SERVICE.md
CHANGED
|
@@ -16,24 +16,29 @@ Connection to the host is made with `ssh linuxuser@kilotest.com`. Periodically t
|
|
|
16
16
|
|
|
17
17
|
Kilotest is installed at `/opt/jpdev/kilotest` on the server.
|
|
18
18
|
|
|
19
|
-
Any other application `xyz` can be installed at `/opt/jpdev/xyz` on the server.
|
|
19
|
+
Any other application `xyz` can be installed at `/opt/jpdev/xyz` on the server. (Previously, the QAI tutorial application was deployed as a separate service at `/opt/jpdev/qai`; it has been integrated into Kilotest itself and no longer runs as a separate process, though the documentation structure remains generic to accommodate future independent applications should they be needed.)
|
|
20
20
|
|
|
21
21
|
## Process management
|
|
22
22
|
|
|
23
23
|
### Process manager
|
|
24
24
|
|
|
25
|
-
Kilotest is managed with [PM2](https://pm2.keymetrics.io) on the server (not on the local development host). The PM2 configuration is specified in the repository as `pm2.config.
|
|
25
|
+
Kilotest is managed with [PM2](https://pm2.keymetrics.io) on the server (not on the local development host). The PM2 configuration is specified in the repository as `pm2.config.cjs` (CommonJS is required because PM2 loads configuration files with `require()`, which cannot load ES modules):
|
|
26
26
|
|
|
27
27
|
```javascript
|
|
28
28
|
module.exports = {
|
|
29
29
|
apps: [
|
|
30
30
|
{
|
|
31
31
|
name: 'kilotest',
|
|
32
|
-
script:
|
|
32
|
+
// index.ts cannot be the PM2 script: its autostart guard (import.meta.main) is
|
|
33
|
+
// false inside PM2's process container, so the server would never start.
|
|
34
|
+
script: 'serve.ts',
|
|
35
|
+
// PM2 maps the .ts extension to the bun interpreter, so node must be specified explicitly.
|
|
36
|
+
interpreter: 'node',
|
|
33
37
|
instances: 1,
|
|
34
38
|
autorestart: true,
|
|
35
39
|
watch: false,
|
|
36
40
|
max_memory_restart: '500M',
|
|
41
|
+
time: true,
|
|
37
42
|
env: {
|
|
38
43
|
NODE_ENV: 'production',
|
|
39
44
|
BASE_PATH: '/',
|
|
@@ -51,6 +56,14 @@ pm2 restart kilotest --time --update-env
|
|
|
51
56
|
pm2 save
|
|
52
57
|
```
|
|
53
58
|
|
|
59
|
+
If the `script` entry point itself has changed, `pm2 restart` reuses the stored path and cannot pick up the new one; delete and recreate the process instead:
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
pm2 delete kilotest
|
|
63
|
+
pm2 start pm2.config.cjs
|
|
64
|
+
pm2 save
|
|
65
|
+
```
|
|
66
|
+
|
|
54
67
|
The management of the PM2 logs is [documented by PM2](https://pm2.keymetrics.io/docs/usage/log-management/).
|
|
55
68
|
|
|
56
69
|
### Process tuning
|
|
@@ -132,6 +145,26 @@ The DNS records for `kilotest.com` are configured as follows. In each case, `TTL
|
|
|
132
145
|
- **Host**: _acme-challenge.kilotest.com
|
|
133
146
|
- **Answers**: validation tokens provided by Porkbun ACME client
|
|
134
147
|
|
|
148
|
+
6. **TXT** (SPF for Porkbun-handled mail)
|
|
149
|
+
- **Host**: kilotest.com
|
|
150
|
+
- **Answer**: `v=spf1 include:_spf.porkbun.com ~all`
|
|
151
|
+
|
|
152
|
+
7. **MX and TXT** (Resend MAIL FROM domain; provides SPF alignment for Resend mail)
|
|
153
|
+
- **Host**: send.kilotest.com
|
|
154
|
+
- **Answers**: MX `feedback-smtp.us-east-1.amazonses.com` (priority 10); TXT `v=spf1 include:amazonses.com ~all`
|
|
155
|
+
|
|
156
|
+
8. **TXT** (Resend DKIM public key)
|
|
157
|
+
- **Host**: resend._domainkey.kilotest.com
|
|
158
|
+
- **Answer**: an RSA public key in `p=...` form, issued by Resend
|
|
159
|
+
|
|
160
|
+
9. **TXT** (DMARC policy)
|
|
161
|
+
- **Host**: _dmarc.kilotest.com
|
|
162
|
+
- **Answer**: `v=DMARC1; p=none;`
|
|
163
|
+
|
|
164
|
+
10. **TXT** (site verification)
|
|
165
|
+
- **Host**: kilotest.com
|
|
166
|
+
- **Answers**: `google-site-verification=...` and `smithery-verification=...` tokens issued by Google and Smithery
|
|
167
|
+
|
|
135
168
|
The DNS configuration as CSV:
|
|
136
169
|
|
|
137
170
|
```csv
|
|
@@ -140,7 +173,13 @@ kilotest.com,kilotest.com,A,149.28.208.106,3600,0
|
|
|
140
173
|
kilotest.com,www.kilotest.com,CNAME,kilotest.com,3600,0
|
|
141
174
|
kilotest.com,kilotest.com,MX,fwd1.porkbun.com,3600,10
|
|
142
175
|
kilotest.com,kilotest.com,MX,fwd2.porkbun.com,3600,20
|
|
176
|
+
kilotest.com,send.kilotest.com,MX,feedback-smtp.us-east-1.amazonses.com,3600,10
|
|
143
177
|
kilotest.com,kilotest.com,TXT,"v=spf1 include:_spf.porkbun.com ~all",3600,0
|
|
178
|
+
kilotest.com,send.kilotest.com,TXT,"v=spf1 include:amazonses.com ~all",3600,0
|
|
179
|
+
kilotest.com,resend._domainkey.kilotest.com,TXT,"p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDWjPkYLQ+xYcmMRODdXoXl7QTXK5TkhNQXjl5HcPvk1D5A5eqtjuw17lgZOQy1wiUq8BGId+Iq3BWO32kYTJo+lWMQJ8/Me4M0xQ0qaYPmtTJ9cQ8oyrFUuheu7T0MqTYEwYRbHwE74WkQSQTHnCx0Uy98pcXqOaucA3n1v3YkjQIDAQAB",3600,0
|
|
180
|
+
kilotest.com,_dmarc.kilotest.com,TXT,"v=DMARC1; p=none;",3600,0
|
|
181
|
+
kilotest.com,kilotest.com,TXT,google-site-verification=EnNe2chUyUaV645ENd3c6pHs1PZ_Zb3u5516HSirvrA,3600,0
|
|
182
|
+
kilotest.com,kilotest.com,TXT,smithery-verification=6a3a2327c26bc478be748041f340e14fa50c4386eea3b2692eeffda703b8b3f9,3600,0
|
|
144
183
|
kilotest.com,_acme-challenge.kilotest.com,TXT,GIzAHOFW416fHp7cuTkg4gvJDsyuPZvsPlSsnyViFLQ,3600,0
|
|
145
184
|
kilotest.com,_acme-challenge.kilotest.com,TXT,qQdIIiUSC76PvYuku-AxPsRPY-fJV7T7i3b8fimlFjU,3600,0
|
|
146
185
|
```
|
|
@@ -187,7 +226,7 @@ curl -sv https://example.com/ -o /dev/null
|
|
|
187
226
|
|
|
188
227
|
Requests to `https://kilotest.com` are received on port 443 and processed by [Caddy](https://caddyserver.com/), which forwards them via HTTP to the application at `localhost:3000`. Caddy manages, provisions, and renews a TLS certificate via Let’s Encrypt. Any request to `http://kilotest.com` is received on port 80, and Caddy redirects it to an `https` request. Caddy forwards `https` requests to the [local server](http://localhost:3000), where it is processed by the Kilotest service.
|
|
189
228
|
|
|
190
|
-
The Caddy configuration is maintained and tracked in `/etc/caddy/Caddyfile`. Leading 2 spaces below represent 1 Tab character.
|
|
229
|
+
The Caddy configuration is maintained and tracked in `/etc/caddy/Caddyfile`. Leading 2 spaces below represent 1 Tab character. Requests are forwarded to Kilotest on port 3000, which handles all paths including tutorial content and backward-compatibility redirects.
|
|
191
230
|
|
|
192
231
|
```caddyfile
|
|
193
232
|
# Configuration of the Caddy web server.
|
|
@@ -196,32 +235,25 @@ The Caddy configuration is maintained and tracked in `/etc/caddy/Caddyfile`. Lea
|
|
|
196
235
|
kilotest.com {
|
|
197
236
|
# Enable Zstandard and Gzip compression of responses.
|
|
198
237
|
encode zstd gzip
|
|
199
|
-
# Specify the only paths of forwardable requests.
|
|
238
|
+
# Specify the only paths of forwardable requests. UptimeRobet uses HEAD.
|
|
200
239
|
@allowedGET {
|
|
201
|
-
method GET
|
|
202
|
-
path /mcp / /index.html /robots.txt /openapi.yaml /openapi.json /swagger.yaml /swagger.json /api-docs /llms.txt /llms-full.txt /capability.md *.html* /fullReport.json/* /api/* /
|
|
240
|
+
method GET HEAD
|
|
241
|
+
path /mcp / /index.html /robots.txt /openapi.yaml /openapi.json /swagger.yaml /swagger.json /api-docs /llms.txt /llms-full.txt /capability.md *.html* /fullReport.json/* /api/* /tutorialWeb/images/* /tutorialAI/images/* /qai /qai/comments /favicon.* /style.css /sitemap.xml
|
|
203
242
|
}
|
|
204
243
|
@allowedPOST {
|
|
205
244
|
method POST
|
|
206
|
-
path /mcp /requestRetest.html/* /requestTest.html /
|
|
245
|
+
path /mcp /requestRetest.html/* /requestTest.html /requestAction.html /reannotate.html /renewWCAG.html /api/* /tutorialWebComment.html /tutorialAIComment.html /worker/job /worker/report
|
|
207
246
|
}
|
|
208
247
|
# Respond to OPTIONS requests.
|
|
209
248
|
@allowedOPTIONS method OPTIONS
|
|
210
249
|
handle @allowedOPTIONS {
|
|
211
250
|
header {
|
|
212
251
|
Access-Control-Allow-Origin *
|
|
213
|
-
Access-Control-Allow-Methods GET,POST,OPTIONS
|
|
252
|
+
Access-Control-Allow-Methods GET,HEAD,POST,OPTIONS
|
|
214
253
|
Access-Control-Allow-Headers Content-Type,Authorization
|
|
215
254
|
}
|
|
216
255
|
respond 204
|
|
217
256
|
}
|
|
218
|
-
# Redirect any /qai request to /qai/.
|
|
219
|
-
redir /qai /qai/ 301
|
|
220
|
-
# Truncate any initial /qai from the forwarded path.
|
|
221
|
-
handle_path /qai* {
|
|
222
|
-
# Forward any QAI request to port 3001.
|
|
223
|
-
reverse_proxy localhost:3001
|
|
224
|
-
}
|
|
225
257
|
# Forward any other GET, POST, or OPTIONS request, if allowed, to port 3000.
|
|
226
258
|
handle @allowedGET {
|
|
227
259
|
reverse_proxy localhost:3000 {
|
|
@@ -283,6 +315,127 @@ When a pull request adds a new route to `index.js`, the Caddyfile at `/etc/caddy
|
|
|
283
315
|
1. The smoke test workflow runs and verifies that all paths, including the new one, are forwarded by Caddy.
|
|
284
316
|
1. If the Caddyfile was not updated, the smoke test reports a bare 404 for the new path, the required status check fails, and the pull request cannot merge.
|
|
285
317
|
|
|
318
|
+
## Smoke testing
|
|
319
|
+
|
|
320
|
+
A periodic GitHub Actions workflow runs smoke tests daily against the deployed service. This workflow is defined in `.github/workflows/periodic-smoke-tests.yml` and runs on a daily schedule at 14:14 UTC. It can also be triggered manually from the GitHub Actions interface.
|
|
321
|
+
|
|
322
|
+
The periodic smoke tests validate that the deployed Kilotest service is functioning correctly end-to-end, independent of code deployments. Code and infrastructure are deployed together, and this periodic check validates them independently. If a deployment introduces a regression—for example, a missing Caddyfile update or a broken route—the periodic tests will detect it within 24 hours.
|
|
323
|
+
|
|
324
|
+
The workflow checks all valid GET and POST paths by running `smokeTest.ts` against the deployed service at `kilotest.com`, verifying that Caddy forwards each path to Kilotest rather than returning a bare 404.
|
|
325
|
+
|
|
326
|
+
## Health monitoring
|
|
327
|
+
|
|
328
|
+
The deployment is monitored for external health and availability using [UptimeRobot](https://dashboard.uptimerobot.com/). The monitoring target is the site root `/`. UptimeRobot checks this target hourly with a `HEAD` request and sends an email alert to the maintainer in either of two cases:
|
|
329
|
+
|
|
330
|
+
- The response status code is 502 (Bad Gateway). This typically indicates that Caddy cannot reach the Kilotest application on `localhost:3000`, suggesting the application process has crashed or become unresponsive.
|
|
331
|
+
- The request times out (connection timeout). This suggests a network issue, a host down, or a severely degraded application.
|
|
332
|
+
|
|
333
|
+
UptimeRobot also sends a recovery message when the service recovers and a subsequent check succeeds after a prior failure.
|
|
334
|
+
|
|
335
|
+
This monitoring service requires `Caddyfile` to permit `HEAD` requests, not only `GET` requests, to the root path.
|
|
336
|
+
|
|
337
|
+
## Alerting
|
|
338
|
+
|
|
339
|
+
Kilotest sends email alerts to the maintainer when certain events occur during normal operation. This section describes when and how those alerts are configured and sent.
|
|
340
|
+
|
|
341
|
+
### When alerts are sent
|
|
342
|
+
|
|
343
|
+
Alerts are sent to the maintainer in the following conditions:
|
|
344
|
+
|
|
345
|
+
1. **Tutorial comment received**: When a user submits a comment on either the web tutorial page (`/tutorialWeb.html`) or the AI agent tutorial page (`/tutorialAI.html`), an alert is sent with subject `"New web tutorial comment received"` or `"New AI tutorial comment received"`.
|
|
346
|
+
|
|
347
|
+
2. **Annotations become obsolete**: The `/listTopIssues.html` page displays no individual report; it aggregates violation counts, across all stored reports (one per tested page, its latest only), into a single cross-report summary of the most frequently reported issues. To build that summary, Kilotest reads the `issueID` field that it previously wrote onto each violating standard instance during annotation, and looks that `issueID` up as a key in the current `testaro-issues` package's issue definitions. The alert fires only if that key is entirely absent, i.e., the issue that this violation was previously classified under has since been removed (or renamed to a different key) in `testaro-issues`. It does not fire, and this check cannot detect, a rule being *reclassified* to a different, still-existing issue; that broader discrepancy (any stored `issueID` that no longer matches what `testaro-issues` currently assigns the rule, whether because the issue vanished or because the rule was moved to a different issue) is instead surfaced separately, without an alert, on the `/reannotateForm.html` page, which lists both such "reclassified" rules and rules that remain wholly unclassified. Each violating instance is checked independently, so a single stored report can trigger this alert once per distinct missing issue ID it contains, and the alert body names one such issue ID at a time. Despite naming a specific issue, the alert does not point at a way to reannotate just that issue: Kilotest's only reannotation mechanism, reached via `/reannotateForm.html` and submitted at `/reannotate.html`, always re-annotates every standard instance of every currently stored report in one bulk operation (see `web/reannotate/index.ts`); there is no per-issue or per-report reannotation. The named issue ID is diagnostic detail only, telling the maintainer why reannotation is needed, not a parameter to a targeted fix.
|
|
348
|
+
|
|
349
|
+
3. **Unclassified rules violated**: When a Testaro report is received and annotated, Kilotest attempts to classify every rule violation by looking up each rule in the `testaro-issues` package, which Kilotest depends on (`testaro-issues` in `package.json`) but does not itself define. If a rule has no classification in the currently installed `testaro-issues` package (i.e., the rule has never been assigned to any issue), that rule is added to an `issuelessRules` list in the report, and an alert is sent with subject `"Kilotest: unclassified rules violated"`, listing which rules in which rule engines lack classifications. Because Kilotest can only look up classifications that `testaro-issues` already provides, it has no means to give such a violation an issue ID until `testaro-issues` is updated to supply one; the remedy is two-fold and cannot be done in Kilotest alone: first update `testaro-issues` to add the missing rule-to-issue mapping, then, once Kilotest's dependency on `testaro-issues` is updated to that new version, reannotate the stored reports (`/reannotateForm.html`, see item 2 above) so their violating instances of that rule are written with the new issue ID. The two packages currently share a maintainer, so coordinating the two steps is not an external obstacle, but both steps remain necessary.
|
|
350
|
+
|
|
351
|
+
4. **Unusable report received**: When a Testaro worker submits a report that Kilotest cannot parse or use (e.g., malformed data), an alert is sent with subject `"Kilotest: unusable report received"`, indicating which job produced the report and which worker submitted it.
|
|
352
|
+
|
|
353
|
+
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.
|
|
354
|
+
|
|
355
|
+
6. **Test request received via UI**: When a user submits a test request through the web interface, an alert is sent with subject `"Kilotest: new test request in the UI"` (via `/requestTest.html`) or `"Kilotest: new retest request in the UI"` (via `/requestRetest.html`), including the target URL, description, and the user's stated reason for the request. `test` and `retest` here are the only two literal values Kilotest passes as the request's type; they are hardcoded at each call site, not user-supplied.
|
|
356
|
+
|
|
357
|
+
7. **Test request received via API**: When a test request is submitted via the API, an alert is sent with subject `"Kilotest: new test request in the API"` (via the `requestTest` API operation) or `"Kilotest: new retest request in the API"` (via the `requestRetest` API operation), including the target URL, description, and the user's stated reason. 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. The check and its dedup key are identical for the UI and API paths, since both funnel through the same underlying request-queuing function.
|
|
358
|
+
|
|
359
|
+
8. **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.
|
|
360
|
+
|
|
361
|
+
9. **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.
|
|
362
|
+
|
|
363
|
+
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.
|
|
364
|
+
|
|
365
|
+
### Alert configuration
|
|
366
|
+
|
|
367
|
+
Alerts are sent via the `sendAlert()` function in [alerts.ts](../alerts.ts), which requires five environment variables to be configured:
|
|
368
|
+
|
|
369
|
+
1. **`MANAGER_EMAIL`**: The email address to which alerts should be sent. This is typically the maintainer's email address.
|
|
370
|
+
2. **`ALERT_API_HOST`**: The hostname of the email delivery service. For Kilotest, this is configured to use [Resend](https://resend.com/), a transactional email service.
|
|
371
|
+
3. **`ALERT_API_PATH`**: The API endpoint path on the email delivery service. For Resend, this is typically `/emails` or similar, depending on the API version.
|
|
372
|
+
4. **`ALERT_API_KEY`**: An API key issued by the email delivery service for authentication. For Resend, this is the API key generated in the Resend dashboard.
|
|
373
|
+
5. **`ALERT_FROM`**: The sender email address for outgoing alerts. This must be a verified sender address on the email delivery service.
|
|
374
|
+
|
|
375
|
+
If any of these five variables is missing or empty at alert time, the alert is not sent. Instead, a warning message is logged to the console, and the application continues normal operation. The warning includes the alert subject and body so the maintainer can manually review what would have been sent.
|
|
376
|
+
|
|
377
|
+
### Resend integration
|
|
378
|
+
|
|
379
|
+
Kilotest uses [Resend](https://resend.com/) as its transactional email delivery service. Resend is configured through the environment variables above.
|
|
380
|
+
|
|
381
|
+
**Resend configuration for Kilotest**: `ALERT_API_HOST` and `ALERT_API_PATH` together identify the Resend API endpoint Kilotest posts to; `ALERT_API_KEY` is a Resend-issued API key; `ALERT_FROM` is the sender address Kilotest sends as. The live values of these environment variables are set in the server's `.env` file, not its PM2 configuration (see “Setting environment variables in production” below), and are not duplicated here.
|
|
382
|
+
|
|
383
|
+
### DNS configuration for Resend
|
|
384
|
+
|
|
385
|
+
Resend authenticates outgoing mail using DKIM, and mail receivers additionally consult SPF and DMARC records for the sending domain. The `kilotest.com` DNS zone (managed at [porkbun.com](https://porkbun.com/); see the DNS Configuration section above for the full record set) currently contains the following records relevant to Resend, confirmed live on 2026-09-17 by querying DNS directly and by inspecting the full headers of a delivered alert message:
|
|
386
|
+
|
|
387
|
+
1. **DKIM (DomainKeys Identified Mail)**
|
|
388
|
+
- **Record type**: TXT (not a CNAME)
|
|
389
|
+
- **Host**: `resend._domainkey.kilotest.com`
|
|
390
|
+
- **Value**: an RSA public key in `p=...` form, issued by Resend for this domain.
|
|
391
|
+
|
|
392
|
+
2. **SPF (Sender Policy Framework)**
|
|
393
|
+
- **Record types**: MX and TXT
|
|
394
|
+
- **Host**: `send.kilotest.com` (a dedicated MAIL FROM subdomain, not the root domain)
|
|
395
|
+
- **Values**: MX `feedback-smtp.us-east-1.amazonses.com` (priority 10) and TXT `v=spf1 include:amazonses.com ~all`
|
|
396
|
+
- This pair is what Resend’s “Enable SPF” option creates. It makes Resend send with a MAIL FROM on `send.kilotest.com`, which aligns with the `kilotest.com` From domain under relaxed DMARC alignment. Because Resend sends through Amazon SES, the include target is `amazonses.com`, not `resend.com`. The separate root-domain TXT record `v=spf1 include:_spf.porkbun.com ~all` authorizes Porkbun’s servers for mail sent as `@kilotest.com` through Porkbun; SPF is evaluated against the MAIL FROM domain, so the root record plays no role in Resend authentication and needs no Resend mechanism.
|
|
397
|
+
|
|
398
|
+
3. **DMARC (Domain-based Message Authentication, Reporting and Conformance)**
|
|
399
|
+
- **Record type**: TXT
|
|
400
|
+
- **Host**: `_dmarc.kilotest.com`
|
|
401
|
+
- **Value**: `v=DMARC1; p=none;`
|
|
402
|
+
- The `p=none` policy is monitoring-only: it asks receivers to take no special action on mail that fails alignment, rather than quarantining or rejecting it. No `rua=` reporting address is configured, so no aggregate DMARC reports are being sent anywhere.
|
|
403
|
+
|
|
404
|
+
A delivered alert message inspected on 2026-09-17 confirms this configuration end to end: `spf=pass` with `smtp.mailfrom` on `send.kilotest.com`, `dkim=pass` with `d=kilotest.com` (`s=resend`), and `dmarc=pass` at both the Porkbun forwarding hop and the final mailbox. Any future change to Resend’s configuration (for example, rotating the DKIM key, adding a `rua=` reporting address to the DMARC record, or tightening the DMARC policy from `p=none` to `p=quarantine`) should be reflected here and in the DNS Configuration section’s CSV.
|
|
405
|
+
|
|
406
|
+
### Setting environment variables in production
|
|
407
|
+
|
|
408
|
+
The deployed `pm2.config.cjs` on the server (`/opt/jpdev/kilotest/pm2.config.cjs`) does not list any of the five alert variables in its `env` object; it matches the repository's own copy of that file, which contains only `NODE_ENV`, `BASE_PATH`, and `DEMO_SSE_DELAY_MS`. Alerting is nonetheless working in production, so the five alert variables are being supplied another way.
|
|
409
|
+
|
|
410
|
+
`index.ts` calls `dotenv.config({quiet: true})` at startup, which reads a `.env` file from the process's working directory (`/opt/jpdev/kilotest/.env`) and loads its contents into `process.env`, in addition to whatever PM2 itself already set. `.env` is listed in `.gitignore`, so it is never committed and exists only on the server; this is the actual mechanism supplying `MANAGER_EMAIL`, `ALERT_API_HOST`, `ALERT_API_PATH`, `ALERT_API_KEY`, and `ALERT_FROM` to the running process. Its expected contents, including these five variables, are documented by the repository's own [env.example](../env.example) (see also the setup instructions in the project [README](../README.md)); `env.example` already records `ALERT_API_HOST=api.resend.com` and `ALERT_API_PATH=/emails` as committed values (also confirmed live: a direct request to `https://api.resend.com/emails` returns `401 Unauthorized`, i.e., a real endpoint that requires an API key, not a routing failure), while `MANAGER_EMAIL`, `ALERT_API_KEY`, and `ALERT_FROM` are left as placeholders there, to be filled in per deployment with server-side secrets not recorded in this document.
|
|
411
|
+
|
|
412
|
+
To change any of the five alert variables:
|
|
413
|
+
|
|
414
|
+
1. Edit `/opt/jpdev/kilotest/.env` on the server directly.
|
|
415
|
+
2. Restart PM2 so the process starts fresh and `dotenv.config()` re-reads the file:
|
|
416
|
+
|
|
417
|
+
```bash
|
|
418
|
+
pm2 restart kilotest --time --update-env
|
|
419
|
+
pm2 save
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Troubleshooting
|
|
423
|
+
|
|
424
|
+
**Alerts are not being sent**:
|
|
425
|
+
|
|
426
|
+
1. Check that all five alert environment variables are set in `/opt/jpdev/kilotest/.env` on the server (not `pm2.config.cjs`, which does not carry them): `MANAGER_EMAIL`, `ALERT_API_HOST`, `ALERT_API_PATH`, `ALERT_API_KEY`, `ALERT_FROM`.
|
|
427
|
+
2. Check the PM2 logs for warning or error messages: `pm2 logs kilotest | grep -i alert`.
|
|
428
|
+
3. Verify that the Resend API key is valid and has not been revoked in the Resend dashboard.
|
|
429
|
+
4. Verify that the sender email address (`ALERT_FROM`) is verified in the Resend dashboard.
|
|
430
|
+
5. Verify that the DNS records (DKIM, SPF, DMARC) are correctly configured for `kilotest.com`.
|
|
431
|
+
|
|
432
|
+
**Alerts are bouncing or not reaching the maintainer**:
|
|
433
|
+
|
|
434
|
+
1. Check the Resend dashboard logs for delivery errors.
|
|
435
|
+
2. Verify that the `MANAGER_EMAIL` address is correct and actively monitored.
|
|
436
|
+
3. Check the maintainer's email spam folder; some email providers filter unfamiliar senders.
|
|
437
|
+
4. Verify DMARC, SPF, and DKIM records are correctly configured.
|
|
438
|
+
|
|
286
439
|
## Performance
|
|
287
440
|
|
|
288
441
|
The Cloud Compute host, in initial testing, took about 2.5 as long to process an example job as an Apple M2 Pro MacBook Pro with 16GB of memory. After tuning, the ratio was reduced to about 1.7.
|
|
@@ -308,7 +461,7 @@ Experimentation revealed that a high-frequency instance could decrease the elaps
|
|
|
308
461
|
|
|
309
462
|
Jobs and reports are not tracked, so there are no duplicates in any other copy of the repository. Jobs are typically ephemeral, but reports typically remain in existence until deemed obsolete and useless even for historical comparison. Therefore, reports risk deletion unless duplicates are made externally. Other files, including application code, dependencies, and `db/reportsExtract.json`, are not at risk, because they can be pushed from the local repository or regenerated.
|
|
310
463
|
|
|
311
|
-
Reports created on the deployed server are currently protected with an external archive at the [Cloudflare R2 object storage service](https://developers.cloudflare.com/r2/). On that service, the current Kilotest maintainer has an account, subscribes to the R2 service, has created two
|
|
464
|
+
Reports created on the deployed server are currently protected with an external archive at the [Cloudflare R2 object storage service](https://developers.cloudflare.com/r2/). On that service, the current Kilotest maintainer has an account, subscribes to the R2 service, has created two *buckets* named `kilotest-reports` and `kilotest-hidden-reports`, and has created an API token scoped to those buckets with object read-write permissions, access and secret access keys for S3 clients, and a restriction to the IPv4 and IPv6 addresses of the server.
|
|
312
465
|
|
|
313
466
|
The server host uses `rclone` for file synchronization with external storage locations. Two files on the server host enable `rclone` for use by Kilotest. One file is `/home/linuxuser/.config/rclone/rclone.conf`. Its content, with secrets replaced, is:
|
|
314
467
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
date: 2026-09-13
|
|
3
|
+
status: accepted
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Coverage tool: keep c8, revisit when Node.js experimental coverage stabilizes
|
|
7
|
+
|
|
8
|
+
## Context and problem
|
|
9
|
+
|
|
10
|
+
Kilotest enforces 100% line, branch, and statement coverage on the production source files in its test suite. During the TypeScript/ESM migration (step 12), the question arose whether to replace the `c8` coverage tool with Node.js's built-in `--experimental-test-coverage` reporter.
|
|
11
|
+
|
|
12
|
+
## Considered options
|
|
13
|
+
|
|
14
|
+
1. **Keep `c8`.**
|
|
15
|
+
2. **Switch to Node.js built-in coverage.**
|
|
16
|
+
|
|
17
|
+
## Decision
|
|
18
|
+
|
|
19
|
+
**Keep `c8`** for now.
|
|
20
|
+
|
|
21
|
+
## Rationale
|
|
22
|
+
|
|
23
|
+
### Why `c8` works
|
|
24
|
+
|
|
25
|
+
There is exactly one `c8` exclusion in the codebase: the `/* c8 ignore start */` … `/* c8 ignore stop */` block around the import statements at the top of `util.ts`. A comment there explains that `c8` intermittently misreports import lines as uncovered against the installed Node.js version. The exclusion was re-verified empirically during step 12 by temporarily removing it and confirming the failure. It is still necessary.
|
|
26
|
+
|
|
27
|
+
### Why Node.js built-in coverage does not yet work cleanly
|
|
28
|
+
|
|
29
|
+
The switch was tested against the full suite. Two problems arose:
|
|
30
|
+
|
|
31
|
+
1. **`mock.module()` corrupts coverage accounting.** Five test files use `t.mock.module()`. Under `--experimental-test-coverage`, one of them (`web/requestRetest`) reproducibly shows its success-path lines (28–30) as uncovered, despite a passing test that exercises exactly that path. The gap appears only after a later test in the same file runs `import('./index.ts?mockNoExtracts')` alongside `t.mock.module()`. This matches a family of actively-tracked Node.js core bugs where `mock.module()` combined with `--experimental-test-coverage` corrupts coverage accounting (nodejs/node#59112, #61709, #58119). The failure is inconsistent across runs, making it hard to audit or suppress reliably.
|
|
32
|
+
|
|
33
|
+
2. **Ignore comments do not suppress branch coverage.** The standard `/* node:coverage ignore next [n] */` syntax suppresses line coverage but not branch coverage for the same lines, per nodejs/node#61586. This matters because any exclusion added to work around problem 1 would sit on real, exercised logic (`requestRetest`'s success path), and the standard workaround is documented not to fully apply in exactly that case. So the switch would trade one well-understood, narrowly-scoped exclusion (import lines in `util.ts`) for a new, less predictable one on real logic with a broken workaround.
|
|
34
|
+
|
|
35
|
+
## Revisit condition
|
|
36
|
+
|
|
37
|
+
Revisit this decision once `--experimental-test-coverage` stabilizes and the following upstream bugs are resolved:
|
|
38
|
+
|
|
39
|
+
- nodejs/node#59112, #61709, #58119 — `mock.module()` + coverage accounting
|
|
40
|
+
- nodejs/node#61586 — ignore comments do not suppress branch coverage
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
date: 2026-06-30
|
|
3
|
+
status: accepted
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ADR threshold
|
|
7
|
+
|
|
8
|
+
## Context and problem
|
|
9
|
+
|
|
10
|
+
Several decisions are being made early in the life of the project. What importance threshold should merit the documentation of a decision in an architectural decision record (ADR)?
|
|
11
|
+
|
|
12
|
+
## Considered options
|
|
13
|
+
|
|
14
|
+
- Lower: Create an ADR for each nontrivial decision.
|
|
15
|
+
- Higher: Create an ADR for each decision that is judged strategic. Deem a decision strategic if there are substantially different alternatives and the choice of an alternative is expected to have substantial effects on the maintainability or quality of the project.
|
|
16
|
+
|
|
17
|
+
## Decision
|
|
18
|
+
|
|
19
|
+
Higher, because:
|
|
20
|
+
|
|
21
|
+
- Formally documenting tactical decisions slows development with little benefit.
|
|
22
|
+
- Persons who want to read the ADRs for the project history can better digest the content if it remains parsimonious and strategically focused.
|
|
23
|
+
- Minor decisions are often understandable with inspection of the codebase.
|
|
24
|
+
- Decisions affecting single points in a codebase are more efficiently explained with adjacent comments that do not require navigation to a separate file.
|
|
25
|
+
|
|
26
|
+
This decision will cause the count of ADRs to be moderate, making a 3-digit naming prefix practical.
|
|
27
|
+
|
|
28
|
+
### Confirmation
|
|
29
|
+
|
|
30
|
+
To confirm that the decision is appropriately implemented, ask LLMs and human colleagues to judge whether the ADRs describe all and only the strategic decisions manifested in the codebase.
|
|
31
|
+
|
|
32
|
+
## ADR mutability convention
|
|
33
|
+
|
|
34
|
+
Unlike the strict Nygard convention of never editing an ADR once written, Kilotest maintains ADRs as kept-current documents. An ADR describes the strategic decision *as currently implemented in the codebase*, and may be edited to reflect how that decision has evolved. Historical context (for example, "approach X was tried but had defect Y, so it was replaced with Z") belongs inside the ADR when it helps prevent future maintainers from reconsidering a decision that was already evaluated and rejected. This convention allows the ADR set to remain authoritative throughout the project lifecycle without requiring separate, frozen ADRs to document every historical iteration.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
date: 2026-06-30
|
|
3
|
+
status: accepted
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# `docs` directory
|
|
7
|
+
|
|
8
|
+
## Context and problem
|
|
9
|
+
|
|
10
|
+
The project began with several files of planning documentation at the root and is now acquiring ADRs as well. Where should all those documents be located?
|
|
11
|
+
|
|
12
|
+
## Considered options
|
|
13
|
+
|
|
14
|
+
- Minimal change: Create a `docs/decisions` directory for ADRs but leave the planning documents at the root.
|
|
15
|
+
- Maximal coherence: Create a `docs` directory for all of those documents, with ADRs in a `decisions` subdirectory.
|
|
16
|
+
|
|
17
|
+
## Decision
|
|
18
|
+
|
|
19
|
+
Maximal coherence, because it substantially simplifies navigation for people perusing the filesystem, and its cost is only revising a few links in documentation and README.
|
|
20
|
+
|
|
21
|
+
### Confirmation
|
|
22
|
+
|
|
23
|
+
To confirm the correctness of the implementation, find all internal links in the codebase and verify that each link to a file that is now in the `docs` directory has the current file location as its destination.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
date: 2026-07-01
|
|
3
|
+
status: accepted
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Observability
|
|
7
|
+
|
|
8
|
+
## Context and problem
|
|
9
|
+
|
|
10
|
+
The maintainer will want to review some data about the operation of Kilotest and will want to be alerted to some activities. What data should be collected, and what activities should merit alerts?
|
|
11
|
+
|
|
12
|
+
## Considered options
|
|
13
|
+
|
|
14
|
+
- Rich: Collect abundant data and send alerts on all significant events.
|
|
15
|
+
- Minimal: Collect obviously important data and send alerts on events that require maintainer action.
|
|
16
|
+
|
|
17
|
+
## Decision
|
|
18
|
+
|
|
19
|
+
Minimal, because:
|
|
20
|
+
|
|
21
|
+
- Rich data and frequent alerts cause inattention, dismissal, or even blocking.
|
|
22
|
+
- The anticipated interactions between users and Kilotest will gradually increase in complexity and unpredictability, but by the first release only two event types are expected to justify alerts: (1) server errors and (2) comment submissions by users.
|
|
23
|
+
|
|
24
|
+
### Confirmation
|
|
25
|
+
|
|
26
|
+
To confirm that the implementation is correct, launch the application, submit requests to it, and verify that any required logs appear. Then temporarily corrupt a request handler to make Kilotest return an error response. Verify that an alert from the alerting module with the expected content has arrived at the expected destination.
|
|
27
|
+
|
|
28
|
+
Which events merit logging, what the contents of alerts should be, and what packages should be chosen as observability and alerting modules are among the decisions classified as "tactical" in [ADR 003](./003-adr-threshold.md). Therefore, those decisions are not described in this ADR or other ADRs.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
date: 2026-07-02
|
|
3
|
+
status: accepted
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Git workflow
|
|
7
|
+
|
|
8
|
+
## Context and problem
|
|
9
|
+
|
|
10
|
+
The project uses Git for version control and GitHub as the host of the origin repository. For simplicity, the process of revision and versioning should be consistent. What Git workflow should be chosen?
|
|
11
|
+
|
|
12
|
+
## Considered options
|
|
13
|
+
|
|
14
|
+
- Push to main: Merge feature branches into the main branch locally and then push the main branch to the origin.
|
|
15
|
+
- PR to main: Merge feature branches into the main branch at the origin by means of approval of pull requests, and never push from the local to the origin main branch.
|
|
16
|
+
|
|
17
|
+
In each case, the decision includes mandatory updates to the dependencies and mandatory internal testing before every push or merge to the origin main branch.
|
|
18
|
+
|
|
19
|
+
## Decision
|
|
20
|
+
|
|
21
|
+
PR to main, because:
|
|
22
|
+
|
|
23
|
+
- It is more secure. If pushes from the local to the origin main branch are permitted, a developer can bypass required prerequisites with the `--no-verify` flag.
|
|
24
|
+
- It is more team-friendly. Although initially there is only 1 developer, local main branches could diverge if other developers began to contribute, and pushes could conflict or lose changes.
|
|
25
|
+
|
|
26
|
+
### Confirmation
|
|
27
|
+
|
|
28
|
+
To confirm that the implementation is correct, try to push to the origin main branch and verify that the push attempt is rejected.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
date: 2026-07-02
|
|
3
|
+
status: accepted
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Internal testing
|
|
7
|
+
|
|
8
|
+
## Context and problem
|
|
9
|
+
|
|
10
|
+
Internal testing creates a tradeoff between speed of development and risk of debugging effort. Under what conditions should internal testing be required?
|
|
11
|
+
|
|
12
|
+
## Considered options
|
|
13
|
+
|
|
14
|
+
Make testing a prerequisite for any:
|
|
15
|
+
|
|
16
|
+
- push to an origin feature branch
|
|
17
|
+
- merge to the origin main branch
|
|
18
|
+
|
|
19
|
+
Any required testing must take place automatically upon a request to perform the merging or pushing action. The choice of test-triggering mechanisms is a tactical decision.
|
|
20
|
+
|
|
21
|
+
## Decision
|
|
22
|
+
|
|
23
|
+
Require testing only before merges to the origin main branch, because:
|
|
24
|
+
|
|
25
|
+
- Merges to the origin main branch are infrequent, so mandatory testing before them is not burdensome.
|
|
26
|
+
- The risk of failure is excessive if the deployed server is permitted to relaunch with code of an untested main branch.
|
|
27
|
+
- Mandatory testing before pushes to the origin of a feature branch would deter routine pushes that protect the local code from accidental deletion.
|
|
28
|
+
- The absence of a local-branch testing requirement still allows developers to perform voluntary testing of any local branches at any time.
|
|
29
|
+
|
|
30
|
+
Although this decision governs only mandatory testing, developers are advised to precede any merge to an origin feature branch with voluntary testing unless they have not yet corrected known bugs in it.
|
|
31
|
+
|
|
32
|
+
### Confirmation
|
|
33
|
+
|
|
34
|
+
To confirm that the implementation is correct, try to merge a defective origin feature branch to the origin main branch. Verify that testing is automatically performed and the merge is prevented by a test failure.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
date: 2026-07-02
|
|
3
|
+
status: accepted
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Dependencies
|
|
7
|
+
|
|
8
|
+
## Context and problem
|
|
9
|
+
|
|
10
|
+
The project will install dependencies. How should their updates be handled?
|
|
11
|
+
|
|
12
|
+
## Considered options
|
|
13
|
+
|
|
14
|
+
- Pin to major version: Specify the major version of each dependency and, when dependencies are updated, update it only to the latest minor and patch version of that major version.
|
|
15
|
+
- Stay with latest: As a general rule, when dependencies are updated, update them to their latest versions. Specify a version range only when the latest version has been found incompatible.
|
|
16
|
+
|
|
17
|
+
## Decision
|
|
18
|
+
|
|
19
|
+
Stay with latest, because:
|
|
20
|
+
|
|
21
|
+
- It gives the project access to the latest features and corrections.
|
|
22
|
+
- It eliminates the drudgery of monitoring dependency updates.
|
|
23
|
+
- It makes the reporting of dependency bugs simpler by eliminating the need to retest with the latest version and thereby encourages participation in open-source communities.
|
|
24
|
+
- It does not introduce a risk of malfunction in production, because merges to the main branch require internal testing.
|
|
25
|
+
|
|
26
|
+
### Confirmation
|
|
27
|
+
|
|
28
|
+
To confirm that the implementation is correct, update the dependencies and spot-check that the installed versions are the latest published ones.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
date: 2026-09-14
|
|
3
|
+
status: accepted
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# QAI integration
|
|
7
|
+
|
|
8
|
+
## Context and problem
|
|
9
|
+
|
|
10
|
+
The QAI application (at `https://github.com/jrpool/qai`) is a small standalone Node.js server deployed at `https://kilotest.com/qai`. QAI is a tutorial showing users how to connect AI platforms (specifically, Claude on `claude.ai`) to Kilotest's MCP server. The `kilotest` repository already provides a tutorial showing users how to use Kilotest itself. The two-repository split was required by an organizational constraint that no longer applies. QAI's own purpose (teaching users how to connect an AI platform to Kilotest) makes it naturally a part of Kilotest's tutorial content. Should QAI remain a separate deployment, or be folded into Kilotest?
|
|
11
|
+
|
|
12
|
+
## Considered options
|
|
13
|
+
|
|
14
|
+
1. **Keep separate:** QAI remains an independent repository with its own deployment, continuous integration, and process management.
|
|
15
|
+
2. **Merge into Kilotest:** Port QAI's tutorial content and comment-collection infrastructure into Kilotest as a second tutorial module, retire QAI as a separate deployment, and simplify the Caddyfile and process management.
|
|
16
|
+
|
|
17
|
+
## Decision
|
|
18
|
+
|
|
19
|
+
**Merge into Kilotest**, because:
|
|
20
|
+
|
|
21
|
+
- QAI is fundamentally tutorial content for Kilotest, not an independent application. Keeping it in a separate repository obscures this relationship.
|
|
22
|
+
- Merging eliminates duplicate infrastructure: both repositories use the same comment-storage pattern, alert environment variables, build configuration, and type-checking standards. A single repository reduces maintenance burden.
|
|
23
|
+
- The existing `web/tutorial/` pattern in Kilotest provides a proven, documented template for modules combining an HTML page with a comment-collection form and email alerts. QAI's infrastructure was originally designed by copying this pattern; reusing it directly instead of maintaining a reimplementation aligns the codebase with DRY principles.
|
|
24
|
+
- Simplifying the Caddyfile and process management reduces the risk of configuration drift, and makes the deployment easier to reason about.
|
|
25
|
+
- Merging improves the documentation standard. QAI includes 9 ADRs and architectural/requirements/deployment documentation that enriches Kilotest's own documentation, which initially had only 2 ADRs.
|
|
26
|
+
|
|
27
|
+
### Confirmation
|
|
28
|
+
|
|
29
|
+
To confirm that the implementation is correct:
|
|
30
|
+
|
|
31
|
+
1. All tests pass with 100% coverage, and linting and type-checking produce no errors.
|
|
32
|
+
2. The new QAI tutorial page and comment form are reachable and functional at their new `/tutorialAI.html` path.
|
|
33
|
+
3. The old QAI paths (`/qai`, `/qai/comments`) redirect correctly to the new paths.
|
|
34
|
+
4. Comments submitted to the new form are stored correctly and trigger email alerts as configured.
|
|
35
|
+
|
|
36
|
+
## Historical context
|
|
37
|
+
|
|
38
|
+
QAI was initially developed as an independent deployment partly because Kilotest had not yet implemented monitoring and alerting infrastructure. By the time QAI reached deployment, both QAI and Kilotest had similar comment-collection and alerting patterns. Keeping QAI separate after this convergence created unnecessary duplication. This merge resolves that architectural drift and reestablishes Kilotest as the canonical location for all Kilotest-related tutorial content.
|
|
39
|
+
|
|
40
|
+
The separate `jrpool/qai` repository is preserved as a historical archive in `jrpool`'s account and can be kept as read-only history for reference.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# QAI historical context
|
|
2
|
+
|
|
3
|
+
This document preserves pointers to documentation and development history from the `jrpool/qai` repository, which was integrated into Kilotest as part of the decision documented in [ADR 009](./009-qai-integration.md).
|
|
4
|
+
|
|
5
|
+
## QAI architectural decisions (superseded or historical)
|
|
6
|
+
|
|
7
|
+
The following ADRs from QAI are not ported to Kilotest, but their reasoning and historical context are preserved in the `jrpool/qai` repository for reference:
|
|
8
|
+
|
|
9
|
+
- **003-initial-routing.md**: QAI originally chose to deploy as an independent service on a separate port, separate from Kilotest's port 3000. This was driven by course requirements and the need for QAI to have evaluable infrastructure of its own. ADR 009 documents the superseding decision to integrate QAI into Kilotest's deployment.
|
|
10
|
+
|
|
11
|
+
- **004-prerelease-versions.md**: QAI used prerelease versioning (0.1.0, etc.) during initial development. This versioning scheme is not carried forward to the integrated Kilotest; Kilotest's own version scheme applies to all content.
|
|
12
|
+
|
|
13
|
+
- **005-infrastructure-health.md**: QAI originally required its own external monitoring with UptimeRobot because Kilotest lacked health monitoring infrastructure. This constraint is now resolved; Kilotest's unified monitoring and alerting (covered in ADR 005-observability.md) applies to both the tutorial content and any other components.
|
|
14
|
+
|
|
15
|
+
## QAI development history
|
|
16
|
+
|
|
17
|
+
Sprint reports, peer reviews, and development progress documentation are preserved in the `jrpool/qai` repository:
|
|
18
|
+
|
|
19
|
+
- `docs/progress-reports/` contains sprint reviews and reflections from the course development of QAI (sprints 3–5).
|
|
20
|
+
- `docs/reviews/` contains peer review feedback on QAI's initial implementation.
|
|
21
|
+
|
|
22
|
+
This historical record is retained in `jrpool/qai`'s git history and is not duplicated in Kilotest's codebase.
|
|
23
|
+
|
|
24
|
+
## Branch protection and CI configuration
|
|
25
|
+
|
|
26
|
+
QAI's GitHub branch-protection ruleset (`docs/rulesets/qai-main.json`) enforced requirements similar to those already in place for Kilotest. Kilotest's own branch protection, documented in `docs/SERVICE.md`, supersedes QAI's configuration, and the separate QAI ruleset file is not ported.
|
|
27
|
+
|
|
28
|
+
## QAI domain-specific documentation
|
|
29
|
+
|
|
30
|
+
The following documents from QAI are superseded by the integration:
|
|
31
|
+
|
|
32
|
+
- **`requirements.md`**: Documented QAI's specific functional requirements. These remain valid for the integrated tutorial, but are now framed as part of Kilotest's tutorial offerings rather than QAI-specific product requirements. Tutorial requirements are documented in the code comments and inline in `web/tutorialAI/index.ts` rather than as a separate file.
|
|
33
|
+
|
|
34
|
+
- **`architecture.md`**: Described QAI as an independent deployment. With integration, QAI's architecture is now part of Kilotest's architecture, and the former QAI-specific architectural overview is superseded by Kilotest's architecture and API design documented in the main codebase and in [ADR 001 (API design)](./001-api-design.md).
|
|
35
|
+
|
|
36
|
+
- **`deployment.md`**: Documented QAI's guest deployment on Kilotest infrastructure and its PM2 configuration. With QAI integrated into Kilotest, a single deployment configuration applies to both. PM2 and infrastructure details are now documented in `docs/SERVICE.md` with no separate QAI-specific deployment doc.
|
|
37
|
+
|
|
38
|
+
- **`repository.md`**, **`project-vision.md`**: Project-management framing specific to QAI as a university-course project. These provide historical context but are not carried forward to Kilotest's documentation.
|
|
39
|
+
|
|
40
|
+
- **`future.md`**, **`ai-implementation-review.md`**: Point-in-time project status and review artifacts. Historical context and any actionable ideas are incorporated into the main codebase and Kilotest's backlog; the original documents are not ported.
|
|
41
|
+
|
|
42
|
+
- **`manual-verification.md`**: Documented QAI's testing and deployment verification procedures. Kilotest's unified test suite and continuous-integration configuration now cover the integrated tutorial content. Some manual verification procedures from QAI (such as form submission and alerting) are mirrored in Kilotest's `smokeTest.ts` and existing test suites.
|
package/eslint.config.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import globals from "globals";
|
|
|
3
3
|
import json from "@eslint/json";
|
|
4
4
|
import markdown from "@eslint/markdown";
|
|
5
5
|
import css from "@eslint/css";
|
|
6
|
+
import tseslint from "typescript-eslint";
|
|
6
7
|
import {defineConfig} from "eslint/config";
|
|
7
8
|
|
|
8
9
|
export default defineConfig([
|
|
@@ -34,6 +35,32 @@ export default defineConfig([
|
|
|
34
35
|
]
|
|
35
36
|
}
|
|
36
37
|
},
|
|
38
|
+
{
|
|
39
|
+
files: ["**/*.ts"],
|
|
40
|
+
plugins: {
|
|
41
|
+
js
|
|
42
|
+
},
|
|
43
|
+
extends: ["js/recommended", tseslint.configs.recommended],
|
|
44
|
+
languageOptions: {
|
|
45
|
+
globals: globals.node
|
|
46
|
+
},
|
|
47
|
+
rules: {
|
|
48
|
+
"@typescript-eslint/ban-ts-comment": [
|
|
49
|
+
"error",
|
|
50
|
+
{"ts-nocheck": "allow-with-description"}
|
|
51
|
+
],
|
|
52
|
+
"no-control-regex": "off",
|
|
53
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
54
|
+
"rest-spread-spacing": ["error", "never"],
|
|
55
|
+
"space-unary-ops": [
|
|
56
|
+
"error",
|
|
57
|
+
{
|
|
58
|
+
"words": true,
|
|
59
|
+
"nonwords": false
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
},
|
|
37
64
|
{
|
|
38
65
|
files: ["**/*.js"],
|
|
39
66
|
languageOptions: {
|