@jrpool/kilotest 46.0.11 → 46.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.
@@ -8,7 +8,28 @@
8
8
  "Exec(find)",
9
9
  "Exec(git show)",
10
10
  "Exec(git commit)",
11
- "Exec(git check-ignore)"
11
+ "Exec(git check-ignore)",
12
+ "Exec(git remote)",
13
+ "Exec(node)",
14
+ "Exec(source)",
15
+ "Exec(nvm use)",
16
+ "Exec(npm run)",
17
+ "Exec(lsof)",
18
+ "Exec(uname)",
19
+ "Exec(mkdir)",
20
+ "Exec(set)",
21
+ "Exec(git add)",
22
+ "Exec(gh)",
23
+ "Exec(/opt/homebrew/bin/colima start)",
24
+ "Exec(/opt/homebrew/bin/docker info)",
25
+ "Exec(/opt/homebrew/bin/docker run)",
26
+ "Exec($DOCKER volume)",
27
+ "Exec($DOCKER run)",
28
+ "Exec($DOCKER logs)",
29
+ "Exec($DOCKER exec)",
30
+ "Exec($DOCKER stop)",
31
+ "Exec(sed)",
32
+ "Exec(awk)"
12
33
  ]
13
34
  }
14
35
  }
@@ -17,7 +17,7 @@ jobs:
17
17
  - name: Set Node.js version
18
18
  uses: actions/setup-node@v4
19
19
  with:
20
- node-version: '>=22.12.0'
20
+ node-version: '>=24.16.0'
21
21
 
22
22
  - name: Install dependencies
23
23
  run: npm ci
@@ -21,7 +21,7 @@ jobs:
21
21
  - name: Set Node.js version
22
22
  uses: actions/setup-node@v4
23
23
  with:
24
- node-version: '>=22.12.0'
24
+ node-version: '>=24.16.0'
25
25
 
26
26
  - name: Verify server.json version matches api/version.js
27
27
  run: |
package/README.md CHANGED
@@ -1,10 +1,16 @@
1
1
  # Kilotest
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/@jrpool/kilotest?label=npm)](https://www.npmjs.com/package/@jrpool/kilotest)
4
+ [![license](https://img.shields.io/github/license/jrpool/kilotest?label=license)](LICENSE)
5
+ [![MCP Registry](https://img.shields.io/badge/MCP_Registry-active-success)](https://registry.modelcontextprotocol.io/v0/servers?search=kilotest)
6
+ [![GitHub stars](https://img.shields.io/github/stars/jrpool/kilotest?label=stars)](https://github.com/jrpool/kilotest/stargazers)
7
+ [![Indexed on TensorBlock MCP Index](https://mcp-index.tensorblock.co/v1/servers/github-jrpool-kilotest-f518ba36/badge.svg)](https://tensorblock.co/mcp/servers/github-jrpool-kilotest-f518ba36)
8
+
3
9
  An ensemble testing and reporting service for front-end web quality
4
10
 
5
11
  ## Features
6
12
 
7
- This application uses an ensemble of 10 rule engines to test public web pages for front-end quality (i.e. accessibility, usability, and standards conformity).
13
+ This application uses an ensemble of 12 rule engines to test public web pages for front-end quality (i.e. accessibility, usability, and standards conformity).
8
14
 
9
15
  The testing paradigm employed by Kilotest is discussed in these papers:
10
16
 
@@ -24,7 +30,7 @@ An active production instance of Kilotest may require multiple testing agents to
24
30
 
25
31
  In the steps below, hosts `T` and `K` may be the same host or two different hosts. Host `T` can be a Debian stable, Ubuntu LTS, Windows, or macOS host. Host `K` can be any server host that can run the latest LTS version of Node.js. If hosts `T` and `K` differ, then they must be open to `https` traffic and host `K` must permit `https` requests from host `T`.
26
32
 
27
- 1. Clone the [Testaro project](https://github.com/jrpool/testaro) into a new directory on host `T`.
33
+ 1. Clone the [Testaro project](https://github.com/YRA-Tech/testaro) into a new directory on host `T`.
28
34
  1. In that directory, install the Testaro dependencies: `npm install`.
29
35
  1. Update the Testaro dependencies and rebuild: `npm run deps`.
30
36
  1. Clone the Kilotest repository into a new directory on host `K`.
@@ -43,20 +49,22 @@ Contributions are welcome! You can use GitHub issues to initiate discussions and
43
49
 
44
50
  ## Making Kilotest a service
45
51
 
46
- See the `SERVICE.md` file for instructions on how to make Kilotest a service.
52
+ See the [`SERVICE.md`](docs/SERVICE.md) file for instructions on how to make Kilotest a service.
47
53
 
48
54
  ## Using Kilotest as an AI tool
49
55
 
50
56
  Kilotest is not only a web application but also an API and an MCP server. AI platforms and models can use Kilotest via those interfaces.
51
57
 
52
- The URL for models using the MCP server is `http://kilotest.com/mcp`. The MCP server is listed at these services:
58
+ The URL for models using the MCP server is `https://kilotest.com/mcp`. The MCP server is listed at these services:
53
59
 
54
60
  - [Smithery](https://smithery.ai/servers/pool/kilotest)
55
61
  - [Glama](https://glama.ai/mcp/connectors/com.kilotest/kilotest)
56
62
 
57
63
  There is additional documentation at:
58
64
 
59
- - [AI-TOOLS.md](AI-TOOLS.md)
60
- - [llms.txt](llms.txt)
61
- - [llms-full.txt](llms-full.txt)
62
- - [QAI](https://kilotest.com/qai)
65
+ - [QAI](https://kilotest.com/qai): a tutorial for allowing AI assistants to use Kilotest
66
+ - [AI-TOOLS.md](docs/AI-TOOLS.md): how Kilotest implements the MCP protocol
67
+ - [Summary of Kilotest capabilities](https://kilotest.com/capability.md)
68
+ - [llms.txt](llms.txt): basic documentation for LLMs
69
+ - [llms-full.txt](llms-full.txt): comprehensive documentation for LLMs
70
+ - [lhm.plugin.json](lhm.plugin.json): documentation for registration with the LobeHub MCP Market
@@ -5,7 +5,7 @@
5
5
 
6
6
  // IMPORTS
7
7
 
8
- const {test, before, after} = require('node:test');
8
+ const {test, before, beforeEach, after} = require('node:test');
9
9
  const assert = require('node:assert/strict');
10
10
  const alerts = require('../alerts');
11
11
 
@@ -14,13 +14,17 @@ const alerts = require('../alerts');
14
14
  let sendAlertCalls = [];
15
15
 
16
16
  before(() => {
17
- sendAlertCalls = [];
18
17
  // @ts-expect-error: Replacing the real function with a mock for testing.
19
18
  alerts.sendAlert = async (subject, body) => {
20
19
  sendAlertCalls.push({subject, body});
21
20
  };
22
21
  });
23
22
 
23
+ // Reset the call log before each test, so tests do not depend on execution order.
24
+ beforeEach(() => {
25
+ sendAlertCalls = [];
26
+ });
27
+
24
28
  // Require requestFeature after the mock is in place, so it captures the mocked sendAlert.
25
29
  const {response} = require('./requestFeature');
26
30
 
@@ -5,7 +5,7 @@
5
5
 
6
6
  // IMPORTS
7
7
 
8
- const {test, before, after} = require('node:test');
8
+ const {test, before, beforeEach, after} = require('node:test');
9
9
  const assert = require('node:assert/strict');
10
10
  const path = require('node:path');
11
11
  const apiUtil = require('./util');
@@ -17,13 +17,17 @@ let processTestRequestCalls = [];
17
17
 
18
18
  before(() => {
19
19
  process.env.DB_DIR = path.join(__dirname, '..', 'test', 'fixtures', 'db');
20
- processTestRequestCalls = [];
21
20
  // @ts-expect-error: Replacing the real function with a mock for testing.
22
21
  apiUtil.processTestRequest = async (testType, what, url, reason) => {
23
22
  processTestRequestCalls.push({testType, what, url, reason});
24
23
  };
25
24
  });
26
25
 
26
+ // Reset the call log before each test, so tests do not depend on execution order.
27
+ beforeEach(() => {
28
+ processTestRequestCalls = [];
29
+ });
30
+
27
31
  // Require requestRetest after the mock is in place, so it captures the mocked processTestRequest.
28
32
  const {response} = require('./requestRetest');
29
33
 
@@ -5,7 +5,7 @@
5
5
 
6
6
  // IMPORTS
7
7
 
8
- const {test, before, after} = require('node:test');
8
+ const {test, before, beforeEach, after} = require('node:test');
9
9
  const assert = require('node:assert/strict');
10
10
  const path = require('node:path');
11
11
  const apiUtil = require('./util');
@@ -17,13 +17,17 @@ let processTestRequestCalls = [];
17
17
 
18
18
  before(() => {
19
19
  process.env.DB_DIR = path.join(__dirname, '..', 'test', 'fixtures', 'db');
20
- processTestRequestCalls = [];
21
20
  // @ts-expect-error: Replacing the real function with a mock for testing.
22
21
  apiUtil.processTestRequest = async (testType, what, url, reason) => {
23
22
  processTestRequestCalls.push({testType, what, url, reason});
24
23
  };
25
24
  });
26
25
 
26
+ // Reset the call log before each test, so tests do not depend on execution order.
27
+ beforeEach(() => {
28
+ processTestRequestCalls = [];
29
+ });
30
+
27
31
  // Require requestTest after the mock is in place, so it captures the mocked processTestRequest.
28
32
  const {response} = require('./requestTest');
29
33
 
package/api/util.js CHANGED
@@ -35,7 +35,7 @@ exports.getResponseMetadata = () => ({
35
35
  exports.getToolsFacts = () => ({
36
36
  'name': 'Kilotest',
37
37
  'description': {
38
- 'what Kilotest does': 'Kilotest tools generate and make available findings about the front-end quality (i.e. accessibility, usability, and standards conformity) of web pages. A Kilotest job generates findings by using Testaro to test a page against more than a thousand rules defined by an ensemble of ten rule engines. Testaro produces a report of the job. The report describes violations of the rules. Kilotest uses Testilo to enhance the report with a classification of the rule violations into about 300 issues. Kilotest makes facts about the issues and the violations retrievable at four levels of granularity.',
38
+ 'what Kilotest does': 'Kilotest tools generate and make available findings about the front-end quality (i.e. accessibility, usability, and standards conformity) of web pages. A Kilotest job generates findings by using Testaro to test a page against about 1300 rules defined by an ensemble of twelve rule engines. Testaro produces a report of the job. The report describes violations of the rules. Kilotest uses Testilo to enhance the report with a classification of the rule violations into about 380 issues. Kilotest makes facts about the issues and the violations retrievable at four levels of granularity.',
39
39
  'how to retrieve findings': {
40
40
  'level 1': 'Use the listReports tool to get a list of available reports.',
41
41
  'level 2': 'Use the listIssues tool to get a list of issues in one report.',
package/capability.md ADDED
@@ -0,0 +1,51 @@
1
+ # Kilotest capability manifest
2
+
3
+ ## Capability
4
+
5
+ Web-page front-end quality audit. Kilotest tests public web pages for front-end quality, namely accessibility, usability, and standards conformity, and makes structured findings available for retrieval.
6
+
7
+ ## Purpose
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.
10
+
11
+ ## Interfaces
12
+
13
+ - MCP server (Streamable HTTP): `https://kilotest.com/mcp`
14
+ - OpenAPI specification: `https://kilotest.com/openapi.yaml`
15
+ - HTTP API: see `https://kilotest.com/openapi.yaml` for endpoints
16
+ - Web UI: `https://kilotest.com/`
17
+ - LLM documentation: `https://kilotest.com/llms.txt` and `https://kilotest.com/llms-full.txt`
18
+
19
+ ## Initial input
20
+
21
+ A request for a list of available reports.
22
+
23
+ ## Initial output
24
+
25
+ A list of available reports.
26
+
27
+ ## Subsequent inputs
28
+
29
+ - If there is no report about the wanted page, a request to test it, with a page description and its URL.
30
+ - If there is a report about the wanted page, requests for findings from the report at successive levels of detail.
31
+
32
+ ## Subsequent outputs
33
+
34
+ - From a request to test a page: confirmation of receipt. Upon approval and execution, the list of available reports will include the new report.
35
+ - From a request for findings: Details about issues reported, related WCAG standards, what rules were violated, and which DOM elements violated them.
36
+
37
+ ## Side effects
38
+
39
+ Kilotest reads the specified public web page during testing. It does not modify the target site. Testing is asynchronous and may take up to approximately one day before reports are available for newly requested pages.
40
+
41
+ ## Cost
42
+
43
+ Free. No authentication is required for the web UI or the public MCP endpoint.
44
+
45
+ ## License
46
+
47
+ MIT.
48
+
49
+ ## Repository
50
+
51
+ `https://github.com/jrpool/kilotest`
package/checkinfo.js ADDED
@@ -0,0 +1,146 @@
1
+ /*
2
+ checkinfo.js
3
+ Summarizes the validation scope of the lint, test, and test:smoke scripts by deriving file and test-case counts from their actual configurations, so the summary stays accurate as the codebase changes.
4
+ */
5
+
6
+ // IMPORTS
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const {execSync} = require('child_process');
11
+
12
+ // CONSTANTS
13
+
14
+ const rootDir = __dirname;
15
+ const excludeDirs = new Set(['node_modules', '.git', 'coverage']);
16
+
17
+ // FUNCTIONS
18
+
19
+ // Recursively collects all files under a directory, skipping excluded directories.
20
+ const collectFiles = dir => {
21
+ const results = [];
22
+ for (const entry of fs.readdirSync(dir, {withFileTypes: true})) {
23
+ const fullPath = path.join(dir, entry.name);
24
+ if (entry.isDirectory()) {
25
+ if (!excludeDirs.has(entry.name)) {
26
+ results.push(...collectFiles(fullPath));
27
+ }
28
+ }
29
+ else {
30
+ results.push(fullPath);
31
+ }
32
+ }
33
+ return results;
34
+ };
35
+
36
+ // Converts a glob pattern to a RegExp for matching relative paths.
37
+ // Supports * (non-segment), ** (any), and literal characters.
38
+ const globToRegExp = pattern => {
39
+ const escaped = pattern
40
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
41
+ .replace(/\*\*/g, '\x00')
42
+ .replace(/\*/g, '[^/]*')
43
+ .replace(/\x00/g, '.*');
44
+ return new RegExp('^' + escaped + '$');
45
+ };
46
+
47
+ // Returns whether a relative path matches any pattern in a list.
48
+ const matchesAny = (relPath, patterns) => patterns.some(p => globToRegExp(p).test(relPath));
49
+
50
+ // Extracts the top-level ignores array from eslint.config.mjs.
51
+ const getEslintIgnores = () => {
52
+ const content = fs.readFileSync(path.join(rootDir, 'eslint.config.mjs'), 'utf8');
53
+ const match = content.match(/ignores:\s*\[([\s\S]*?)\]/);
54
+ if (!match) {
55
+ return [];
56
+ }
57
+ return [...match[1].matchAll(/"([^"]+)"/g)].map(m => m[1]);
58
+ };
59
+
60
+ // Parses .markdownlint-cli2.jsonc and returns its ignores array.
61
+ const getMarkdownlintIgnores = () => {
62
+ const content = fs.readFileSync(path.join(rootDir, '.markdownlint-cli2.jsonc'), 'utf8');
63
+ const stripped = content.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
64
+ const match = stripped.match(/"ignores"\s*:\s*\[([\s\S]*?)\]/);
65
+ if (!match) {
66
+ return [];
67
+ }
68
+ return [...match[1].matchAll(/"([^"]+)"/g)].map(m => m[1]);
69
+ };
70
+
71
+ // Runs the Node test runner with the TAP reporter and parses the test count, so dynamically generated tests (e.g., from for-loops) are counted accurately.
72
+ const getTestCaseCount = () => {
73
+ const output = execSync(
74
+ 'node --require ./test/setup.cjs --test --test-concurrency=1 --test-timeout=100 --test-reporter=tap',
75
+ {cwd: rootDir, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe']}
76
+ );
77
+ const match = output.match(/^# tests (\d+)$/m);
78
+ return match ? Number(match[1]) : 0;
79
+ };
80
+
81
+ // MAIN
82
+
83
+ const allFiles = collectFiles(rootDir);
84
+ const relFiles = allFiles.map(f => path.relative(rootDir, f));
85
+
86
+ const eslintIgnores = getEslintIgnores();
87
+ const markdownlintIgnores = getMarkdownlintIgnores();
88
+
89
+ // ESLint file counts by extension, excluding ignored files.
90
+ const eslintExts = {js: 0, json: 0, md: 0, css: 0};
91
+ for (const rel of relFiles) {
92
+ if (matchesAny(rel, eslintIgnores)) {
93
+ continue;
94
+ }
95
+ if (/\.(js|mjs|cjs)$/.test(rel)) {
96
+ eslintExts.js++;
97
+ }
98
+ else if (/\.json$/.test(rel)) {
99
+ eslintExts.json++;
100
+ }
101
+ else if (/\.md$/.test(rel)) {
102
+ eslintExts.md++;
103
+ }
104
+ else if (/\.css$/.test(rel)) {
105
+ eslintExts.css++;
106
+ }
107
+ }
108
+ const eslintTotal = Object.values(eslintExts).reduce((a, b) => a + b, 0);
109
+
110
+ // markdownlint file count (markdown files not ignored).
111
+ const markdownlintCount = relFiles.filter(
112
+ rel => rel.endsWith('.md') && !matchesAny(rel, markdownlintIgnores)
113
+ ).length;
114
+
115
+ // Test file and test-case counts.
116
+ const testFiles = relFiles.filter(rel => /\.test\.js$/.test(rel));
117
+ const testCaseCount = getTestCaseCount();
118
+
119
+ // Smoke-test path counts from the routes table in index.js.
120
+ const {routes} = require('./index');
121
+ const smokeGetCount = routes.GET.length;
122
+ const smokePostCount = routes.POST.length;
123
+ const smokeTotal = smokeGetCount + smokePostCount;
124
+
125
+ // OUTPUT
126
+
127
+ const parts = [
128
+ ['lint', [
129
+ `ESLint: ${eslintTotal} files (${eslintExts.js} JS, ${eslintExts.json} JSON, ${eslintExts.md} MD, ${eslintExts.css} CSS)`,
130
+ `markdownlint: ${markdownlintCount} files`
131
+ ]],
132
+ ['test', [
133
+ `${testFiles.length} test files, ${testCaseCount} test cases`
134
+ ]],
135
+ ['test:smoke', [
136
+ `${smokeTotal} paths (${smokeGetCount} GET, ${smokePostCount} POST)`
137
+ ]]
138
+ ];
139
+
140
+ console.log('Validation summary:');
141
+ for (const [name, lines] of parts) {
142
+ console.log(` ${name}:`);
143
+ for (const line of lines) {
144
+ console.log(` ${line}`);
145
+ }
146
+ }