@jrpool/kilotest 63.1.8 → 63.2.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/README.md CHANGED
@@ -88,8 +88,8 @@ When you make a change in a feature branch **without** any API or tool contract
88
88
 
89
89
  When you **make an API or tool contract change** in a feature branch:
90
90
 
91
- 1. `lhm.plugin.json`: Transform it to match `api/schemas.ts` and `mcp.ts`.
92
- 2. `llms.txt`, `llms-full.txt`, `capability.md`, `README.md`: Update parameter and path names.
91
+ 1. `llms.txt`, `llms-full.txt`: Update narrative descriptions of the changed tools or parameters.
92
+ 2. `capability.md` and `README.md`: Update their prose to describe the changed behavior.
93
93
  3. Stage, commit, and optionally push all changes.
94
94
  4. Execute `npm run prePR -- <level>` (`major`, `minor`, or `patch`).
95
95
  5. Submit a pull request.
package/content.ts ADDED
@@ -0,0 +1,22 @@
1
+ /*
2
+ content.ts
3
+ Canonical prose describing what Kilotest is and does, reused verbatim by mcp.ts, scripts/generate-openapi.ts, scripts/generate-lhm.ts, and scripts/generate-llms.ts, so the sentences are written once instead of being hand-copied into mcp.ts, lhm.plugin.json, llms.txt, and openapi.yaml.
4
+ */
5
+
6
+ // CONSTANTS
7
+
8
+ // Brief description of the Kilotest tool collection. Used as the MCP server description and as lhm.plugin.json’s top-level description. No length limit.
9
+ export const collectionDescription = 'Tools that test web pages with an ensemble of 12 rule engines for front-end quality (accessibility, usability, and W3C web standards, including HTML, CSS, ARIA, and WCAG)';
10
+
11
+ // Description of the Kilotest domain and data hierarchy. Used as (1) the McpServer
12
+ // instructions, which is the only place most tools' own descriptions (everything but
13
+ // getKilotestOverview) get this context from, since repeating it in each of their
14
+ // descriptions would dilute how models read each tool, (2) the first paragraph of the
15
+ // kilotestOverview prose that mcp.ts returns from the getKilotestOverview tool and the
16
+ // kilotest-overview resource, (3) the opening paragraph of llms.txt, (4) the start of the
17
+ // openapi.yaml info.description, and (5) the getKilotestOverview tool's own description and
18
+ // the kilotest-overview resource's own description, because some MCP clients do not
19
+ // propagate the McpServer instructions to their models, so getKilotestOverview and the
20
+ // kilotest-overview resource (unlike the other tools) carry the context redundantly, in both
21
+ // their own description and their returned body.
22
+ export const sharedContext = 'Kilotest integrates an ensemble of 12 rule engines, with about 1,300 rules, to test web pages for front-end quality (accessibility, usability, and W3C web standards, including HTML, CSS, ARIA, and WCAG). Each test job generates a standardized report describing which elements violated which rules and grouping the violations into about 380 issues. Kilotest can list the tested pages, the reports about each page, the issues in each report, the violators of each issue, and the diagnoses of each violation. Kilotest also accepts requests to test pages not yet tested and to retest already tested pages.';
package/docs/AI-TOOLS.md CHANGED
@@ -40,6 +40,8 @@ The internal features that make Kilotest a collection of tools for language mode
40
40
 
41
41
  The external features that support the use of Kilotest as a collection of AI tools are:
42
42
 
43
+ ### Registrations
44
+
43
45
  - A [pull request](https://github.com/public-apis/public-apis/pull/6346/changes) to add Kilotest to the list of public APIs in the `public-apis` repository.
44
46
  - An [issue](https://github.com/APIs-guru/openapi-directory/issues/2677) to add Kilotest to `openapi-directory`.
45
47
  - A [pull request](https://github.com/w3c/wai-evaluation-tools-list/pull/1153) to add Kilotest to the WAI evaluation tools list.
@@ -51,17 +53,27 @@ The external features that support the use of Kilotest as a collection of AI too
51
53
  - Registration of Kilotest with the [RapidAPI](https://rapidapi.com/jrpool/api/kilotest/playground/apiendpoint_0f03577a-ff9a-472a-a0ed-533bd198981a) Hub.
52
54
  - Registration of Kilotest with the [LobeHub MCP Market](https://lobehub.com/mcp) (identifier: `jrpool-kilotest`), enabled with an `lhm.plugin.json` file.
53
55
  - Registration of Kilotest with [MCPTry](https://mcptry.com/servers/io.github.jrpool/kilotest).
56
+ - Registration of the MCP server as a community connector in the [Claude directory of plugins and connectors](https://claude.ai/directory/kilotest).
57
+ - Registration of Kilotest and its sitemap at [Google Search Console](https://search.google.com/search-console?resource_id=sc-domain:kilotest.com) and [Bing Webmaster Tools](https://www.bing.com/webmasters/home?siteUrl=https://kilotest.com/).
58
+ - Submission of the MCP server to `mcp.directory`.
59
+ - Submission of the MCP server to [MCP.so](https://mcp.so/). The submission is in review.
60
+
61
+ ### Deployment
62
+
54
63
  - Deployment of an MCP server in HTTP mode on the Kilotest service host.
55
- - Submission of the MCP server to the [Claude directory of plugins and connectors](https://claude.ai/directory/manage).
64
+
65
+ ### Local configuration
66
+
56
67
  - Configuration of Claude Desktop on the local development host and the `claude.ai` web application to connect Claude Desktop models to the Kilotest MCP server. The configuration was performed in the UI of each platform with the addition of Kilotest as a _connector_. The user used the `Customize/Connectors/Add connector/Add custom connector` interface, providing these data before activating the `Add` button:
57
68
 
58
69
  - Name: Kilotest
59
70
  - Remote MCP server URL: `https://kilotest.com/mcp`
60
71
 
72
+ ### Documentation
73
+
61
74
  - MCP-related topics added to the [repository details](https://github.com/jrpool/kilotest).
62
- - An [MCP Registry badge](https://registry.modelcontextprotocol.io/v0/servers?search=kilotest) added to the `README.md` file.
75
+ - Registry badges added to the `README.md` file.
63
76
  - A link to the MCP server on the `README.md` page of the [`testaro-issues`](https://github.com/jrpool/testaro-issues) repository.
64
- - Registration of Kilotest and its sitemap at [Google Search Console](https://search.google.com/search-console?resource_id=sc-domain:kilotest.com) and [Bing Webmaster Tools](https://www.bing.com/webmasters/home?siteUrl=https://kilotest.com/).
65
77
 
66
78
  ## Required maintenance
67
79
 
@@ -69,7 +81,7 @@ The maintenance routine is prescribed in the [README.md](../README.md) file.
69
81
 
70
82
  Releasing a new version is a two-script procedure:
71
83
 
72
- - On a feature branch, commit all substantive changes, then execute `npm run prePR -- <level>` (`major`, `minor`, or `patch`). The script bumps `package.json` and `package-lock.json`, regenerates `openapi.yaml`, `openapi-3.0.yaml`, and the versions of `server.json` and `lhm.plugin.json` by explicitly running `npm run generate`, commits and pushes the result, and records the branch name for the next step.
84
+ - On a feature branch, commit all substantive changes, then execute `npm run prePR -- <level>` (`major`, `minor`, or `patch`). The script bumps `package.json` and `package-lock.json`, regenerates `openapi.yaml`, `openapi-3.0.yaml`, and the descriptions, schemas, and versions of `server.json` and `lhm.plugin.json` by explicitly running `npm run generate`, commits and pushes the result, and records the branch name for the next step.
73
85
  - Submit a pull request and merge it to `main` (triggers `publish-mcp.yml`, which publishes `server.json` to the MCP Registry).
74
86
  - Execute `npm run postPR`. The script checks out and pulls `main`, verifies that the merge landed, publishes to npm, updates the LobeHub listing via `npm run publish:lhm`, deletes the feature branch, and prunes remote-tracking branches. If the LobeHub update fails, retry it alone with `npm run publish:lhm`.
75
87
 
@@ -84,8 +96,9 @@ A change to the tool or API contract (tool names, parameter names, schemas, resp
84
96
  - `openapi.yaml`: regenerated automatically by the `version` hook during `npm run prePR`; never edit it by hand.
85
97
  - `openapi-3.0.yaml`: an OpenAPI 3.0.3 conversion of `openapi.yaml`, regenerated by the same hook, for registries whose importers reject 3.1 documents; never edit it by hand.
86
98
  - `server.json`: contains only the server descriptor (name, description, repository, remotes, version), no tool schemas. Its version is synced by `update-versions.ts`; edit it manually only if the server description or remote URL changes.
87
- - `lhm.plugin.json`: embeds every tool’s input and output schema and its descriptions, in the LobeHub manifest format. It is hand-maintained, not generated, so transform it to match `api/schemas.ts` and `mcp.ts` whenever the contract changes.
88
- - `llms.txt`, `llms-full.txt`, `capability.md`, `README.md`: hand-maintained; search them for stale parameter and path names after a contract change.
99
+ - `lhm.plugin.json`: embeds every tool’s input and output schema and its descriptions, in the LobeHub manifest format. Its `description` and every tool’s `description`, `inputSchema`, `outputSchema`, and `annotations` are regenerated from `mcp.ts`/`api/schemas.ts` by `scripts/generate-lhm.ts` (run by `npm run generate`, part of `npm run prePR`); never edit those fields by hand. The remaining fields (`author`, `cloudEndpoint`, `homepage`, `identifier`, `name`, `prompts`, `resources`, `tags`, `version`, and each tool’s `execution` block) have no canonical source elsewhere and are still edited manually. See `docs/decisions/010-content-generation.md`.
100
+ - `content.ts`: canonical prose shared verbatim by `mcp.ts`, `scripts/generate-openapi.ts`, and `scripts/generate-lhm.ts`. Also hand-check `llms.txt`’s opening paragraph, which is supposed to match `content.ts`’s `sharedContext` verbatim but is not generated.
101
+ - `llms-full.txt`, `capability.md`, `README.md`, `docs/openai-plugin/skills/kilotest-overview/SKILL.md`: hand-maintained, intentionally paraphrased rather than copied verbatim; search them for stale parameter and path names after a contract change.
89
102
 
90
103
  #### External registries that update themselves, needing no maintainer action
91
104
 
@@ -123,7 +136,7 @@ To-dos recommended by Claude Sonnet 5 Medium on Devin:
123
136
 
124
137
  - **No content/backlink outreach.** Registrations are passive listings; there's no blog post, Show HN, or subreddit post (r/modelcontextprotocol, r/accessibility, r/webdev) building external backlinks and social-discovery signal, which the doc's own "difficult problem…lack of standardization" framing suggests is exactly the gap left for the **human**-driven parts of discoverability.
125
138
 
126
- - **Other directories not yet targeted.** The [mcp.so](https://mcp.so/submit) directory offers a free tier but steers submitters toward a $39 paid listing; deferred as a commercial decision. The Docker MCP Catalog is not a fit because it requires stdio transport and a Dockerfile, and Kilotest is streamable-http.
139
+ - **Other directories not yet targeted.** The Docker MCP Catalog is not a fit because it requires stdio transport and a Dockerfile, and Kilotest is streamable-http.
127
140
 
128
141
  - **Client-onboarding friction isn't documented.** Some MCP clients need the `mcp-remote` npx bridge to use a remote streamable-HTTP server like Kilotest's. Ready-made Claude Desktop/Cursor/Windsurf config instructions added to the AI tutorial would convert "discovered" into "used" faster, since case 3 of your use cases assumes a technically savvy discoverer but not all users are.
129
142
 
@@ -0,0 +1,32 @@
1
+ ---
2
+ date: 2026-11-04
3
+ status: accepted
4
+ ---
5
+
6
+ # Generating duplicated prose and schemas instead of hand-copying them
7
+
8
+ ## Context and problem
9
+
10
+ `mcp.ts`, `lhm.plugin.json`, `llms.txt`, and `scripts/generate-openapi.ts` each carried their own hand-typed copy of the same sentence describing what Kilotest is (“Kilotest integrates an ensemble of 12 rule engines…”). The four copies had already drifted: `llms.txt` had an accidentally duplicated clause and said “test job” where `lhm.plugin.json` said “test session”, and `generate-openapi.ts`’s `info.description` was a fourth, independently worded variant. Separately, `lhm.plugin.json` hand-transcribes every MCP tool’s `inputSchema`, `outputSchema`, `description`, and `annotations` from `mcp.ts`/`api/schemas.ts`, a step `docs/AI-TOOLS.md` already flagged as “hand-maintained, not generated…transform it…whenever the contract changes.” Regenerating `lhm.plugin.json` from the live registrations in `mcp.ts` found that this hand-transcription had already gone stale: several tool descriptions no longer matched `mcp.ts`, and `listDiagnoses`’s output schema was missing two fields (`identifier of the rule engine` and `how to report that you disagree with this diagnosis`) that the real tool returns.
11
+
12
+ ## Considered options
13
+
14
+ - Status quo: keep hand-copying prose and schemas into each surface, relying on the `docs/AI-TOOLS.md` “Contract changes” checklist to remind a maintainer to update every copy.
15
+ - Add a lint/test check that only flags byte-for-byte differences between the copies, without removing the duplication itself.
16
+ - Generate the duplicated surfaces from canonical sources, extending the pattern the project already uses for `package.json`’s `version` (copied into `server.json`/`lhm.plugin.json` by `scripts/update-versions.ts`) and for `api/schemas.ts`/`api/routes.ts` (rendered into `openapi.yaml`/`openapi-3.0.yaml` by `scripts/generate-openapi.ts`).
17
+
18
+ ## Decision
19
+
20
+ Generate, rather than hand-copy, wherever the same sentence or schema is supposed to appear verbatim in more than one file, extending the project's existing generate-from-canonical-source pattern:
21
+
22
+ - `content.ts` is the new single source of truth for prose that must be identical across surfaces (`collectionDescription`, the one-line tool-collection description; `sharedContext`, the domain/data-hierarchy paragraph). `mcp.ts` and `scripts/generate-openapi.ts` import it instead of defining their own copies.
23
+ - `scripts/generate-lhm.ts` regenerates `lhm.plugin.json`’s top-level `description` and every tool’s `description`, `inputSchema`, `outputSchema`, and `annotations` from `content.ts` and the same tool registrations `mcp.ts` creates at runtime (via `createMCPServer()`) and the same Zod schemas `api/schemas.ts` exports, using `z.toJSONSchema`. Fields with no canonical source elsewhere in the repository (`author`, `cloudEndpoint`, `homepage`, `identifier`, `name`, `prompts`, `resources`, `tags`, `version`, and each tool’s `execution` block) are left untouched by the generator.
24
+ - `scripts/generate-lhm.ts` is wired into `npm run generate`, alongside `generate-openapi.ts` and `update-versions.ts`, so `prePR.ts` regenerates it before every release the same way it already regenerates `openapi.yaml`.
25
+ - `llms.txt`’s opening paragraph is hand-corrected to match `content.ts`’s `sharedContext` verbatim; it is not generated, because the rest of the file (and `llms-full.txt`, `capability.md`, `README.md`, and the OpenAI-plugin `SKILL.md`) intentionally paraphrase for their audience rather than repeating the same sentence, so forcing byte-identical text there would reduce documentation quality for no benefit. These remain on the existing hand-maintained checklist in `docs/AI-TOOLS.md`.
26
+ - Publishing remains separate from generation, as it already was for `openapi.yaml`/`server.json`: `npm run generate` produces correct file content locally, and `npm run publish:lhm` still performs the manual, OAuth-gated push to LobeHub.
27
+
28
+ The byte-for-byte lint-check option was rejected as a primary strategy because it would still require a maintainer to hand-edit three or four files in sync on every contract change; it only catches drift after the fact rather than removing the duplication that causes it.
29
+
30
+ ### Confirmation
31
+
32
+ To confirm the correctness of the implementation, run `npm run generate`, verify `lhm.plugin.json`, `openapi.yaml`, and `openapi-3.0.yaml` are unchanged (a clean `git diff`), and run `npm test` and `npm run lint` to confirm nothing else regressed.
package/lhm.plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
+ "_generated": "Do not edit the _generated, version, description, or tools property. They are automatically generated from content.ts, api/schemas.ts/mcp.ts, scripts/generate-lhm.ts, and scripts/update-versions.ts.",
2
3
  "author": "Jonathan Robert Pool",
3
4
  "cloudEndpoint": "https://kilotest.com/mcp",
4
- "description": "Tools that test web pages for front-end quality (accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG) and make test results available",
5
5
  "homepage": "https://github.com/jrpool/kilotest",
6
6
  "identifier": "jrpool-kilotest",
7
7
  "name": "Kilotest",
@@ -18,10 +18,12 @@
18
18
  "wcag",
19
19
  "web standards"
20
20
  ],
21
+ "version": "63.2.1",
22
+ "description": "Tools that test web pages with an ensemble of 12 rule engines for front-end quality (accessibility, usability, and W3C web standards, including HTML, CSS, ARIA, and WCAG)",
21
23
  "tools": [
22
24
  {
23
25
  "name": "getKilotestOverview",
24
- "description": "Explains what Kilotest is, what its tools do, and how its results are structured. Use this before any other tool when the available tools or workflow are unclear. Returns a prose overview of the Kilotest domain, the tool workflow, and links to further documentation. Kilotest integrates an ensemble of 12 rule engines, with about 1,300 rules, to test web pages for front-end quality (accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG). Each test session generates a standardized report describing which elements violated which rules and grouping the violations into about 380 issues. Kilotest can list the tested pages, the reports about each page, the issues in each report, the violators of each issue, and the diagnoses of each violation. Kilotest also accepts requests to test pages not yet tested and to retest already tested pages.",
26
+ "description": "Explains what Kilotest is, what its tools do, and how its results are structured. Use this before any other tool when the available tools or workflow are unclear. Returns a prose overview of the Kilotest domain, the tool workflow, and links to further documentation. Kilotest integrates an ensemble of 12 rule engines, with about 1,300 rules, to test web pages for front-end quality (accessibility, usability, and W3C web standards, including HTML, CSS, ARIA, and WCAG). Each test job generates a standardized report describing which elements violated which rules and grouping the violations into about 380 issues. Kilotest can list the tested pages, the reports about each page, the issues in each report, the violators of each issue, and the diagnoses of each violation. Kilotest also accepts requests to test pages not yet tested and to retest already tested pages.",
25
27
  "inputSchema": {
26
28
  "type": "object",
27
29
  "properties": {},
@@ -30,8 +32,8 @@
30
32
  "annotations": {
31
33
  "title": "Explain Kilotest and its tools",
32
34
  "readOnlyHint": true,
33
- "destructiveHint": false,
34
35
  "idempotentHint": true,
36
+ "destructiveHint": false,
35
37
  "openWorldHint": false
36
38
  },
37
39
  "execution": {
@@ -40,7 +42,7 @@
40
42
  },
41
43
  {
42
44
  "name": "listPages",
43
- "description": "Lists all pages Kilotest has tested. Use this first to discover whether a page already has a report before requesting a test. Returns, for each tested page, its description, URL, and the timestamp and job identifier of its latest report.",
45
+ "description": "Lists all pages Kilotest has tested. Use this first to discover whether a page already has any reports before requesting a test. Returns, for each tested page, its description, URL, and the identifier of its latest available report.",
44
46
  "inputSchema": {
45
47
  "type": "object",
46
48
  "properties": {},
@@ -382,8 +384,8 @@
382
384
  "annotations": {
383
385
  "title": "List tested pages",
384
386
  "readOnlyHint": true,
385
- "destructiveHint": false,
386
387
  "idempotentHint": true,
388
+ "destructiveHint": false,
387
389
  "openWorldHint": false
388
390
  },
389
391
  "execution": {
@@ -392,7 +394,7 @@
392
394
  },
393
395
  {
394
396
  "name": "listIssues",
395
- "description": "Lists the issues reported in one report, along with the history of all reports about the same page. Use this after listPages to drill into a specific report, identified by its timestamp and job identifier. Returns basics about the report’s issues and a list of every report about the same page, from oldest to latest.",
397
+ "description": "Lists the issues reported in one report, along with the history of all reports about the same page. Use this after listPages to switch to an earlier or later report or to begin examining this report. Returns the history and the basics about this report’s issues.",
396
398
  "inputSchema": {
397
399
  "type": "object",
398
400
  "properties": {
@@ -1124,8 +1126,8 @@
1124
1126
  "annotations": {
1125
1127
  "title": "List issues in a report",
1126
1128
  "readOnlyHint": true,
1127
- "destructiveHint": false,
1128
1129
  "idempotentHint": true,
1130
+ "destructiveHint": false,
1129
1131
  "openWorldHint": false
1130
1132
  },
1131
1133
  "execution": {
@@ -1134,7 +1136,7 @@
1134
1136
  },
1135
1137
  {
1136
1138
  "name": "listViolators",
1137
- "description": "Lists the elements of a tested page that were reported as exhibiting one issue in one report. Use this after listIssues to drill from an issue down to the elements it affects. Returns basics about each violating element, including its identifier in the page’s element catalog.",
1139
+ "description": "Lists the elements of a tested page that were reported as exhibiting one issue in one report. Use this after listIssues to drill down from an issue to the elements violating rules belonging to that issue. Returns basics about each violating element.",
1138
1140
  "inputSchema": {
1139
1141
  "type": "object",
1140
1142
  "properties": {
@@ -1633,8 +1635,8 @@
1633
1635
  "annotations": {
1634
1636
  "title": "List elements exhibiting an issue",
1635
1637
  "readOnlyHint": true,
1636
- "destructiveHint": false,
1637
1638
  "idempotentHint": true,
1639
+ "destructiveHint": false,
1638
1640
  "openWorldHint": false
1639
1641
  },
1640
1642
  "execution": {
@@ -1643,7 +1645,7 @@
1643
1645
  },
1644
1646
  {
1645
1647
  "name": "listDiagnoses",
1646
- "description": "Lists the diagnoses that rule engines gave for how one element exhibited one issue in one report. Use this after listViolators to drill from an element down to the rule-engine explanations of why it was flagged. Returns, for each rule engine that reported the issue on that element, its diagnosis.",
1648
+ "description": "Lists the diagnoses that rule engines gave for how one element exhibited one issue in one report. Use this after listViolators to get details about the element as a violator of the issue. Returns, for each rule engine that reported a violation by the element of a rule belonging to the issue, the diagnosis given by the rule engine.",
1647
1649
  "inputSchema": {
1648
1650
  "type": "object",
1649
1651
  "properties": {
@@ -1802,6 +1804,9 @@
1802
1804
  "items": {
1803
1805
  "type": "object",
1804
1806
  "properties": {
1807
+ "identifier of the rule engine": {
1808
+ "description": "Copied from the report act (which); type not guaranteed."
1809
+ },
1805
1810
  "identifier of the violated rule": {
1806
1811
  "description": "Copied from the report instance (ruleID); type not guaranteed."
1807
1812
  },
@@ -1813,13 +1818,53 @@
1813
1818
  },
1814
1819
  "count of violations of the rule by the element": {
1815
1820
  "type": "number"
1821
+ },
1822
+ "how to report that you disagree with this diagnosis": {
1823
+ "type": "object",
1824
+ "properties": {
1825
+ "method": {
1826
+ "type": "string",
1827
+ "const": "POST"
1828
+ },
1829
+ "URL": {
1830
+ "type": "string"
1831
+ },
1832
+ "request body": {
1833
+ "type": "object",
1834
+ "properties": {
1835
+ "engineID": {
1836
+ "type": "string"
1837
+ },
1838
+ "ruleID": {
1839
+ "type": "string"
1840
+ },
1841
+ "reason": {
1842
+ "type": "string"
1843
+ }
1844
+ },
1845
+ "required": [
1846
+ "engineID",
1847
+ "ruleID",
1848
+ "reason"
1849
+ ],
1850
+ "additionalProperties": false
1851
+ }
1852
+ },
1853
+ "required": [
1854
+ "method",
1855
+ "URL",
1856
+ "request body"
1857
+ ],
1858
+ "additionalProperties": false
1816
1859
  }
1817
1860
  },
1818
1861
  "required": [
1862
+ "identifier of the rule engine",
1819
1863
  "identifier of the violated rule",
1820
1864
  "description of the violation",
1821
1865
  "severity of the violation on a 0-to-3 scale",
1822
- "count of violations of the rule by the element"
1866
+ "count of violations of the rule by the element",
1867
+ "how to report that you disagree with this diagnosis"
1823
1868
  ],
1824
1869
  "additionalProperties": false
1825
1870
  }
@@ -2160,8 +2205,8 @@
2160
2205
  "annotations": {
2161
2206
  "title": "List diagnoses of a violation",
2162
2207
  "readOnlyHint": true,
2163
- "destructiveHint": false,
2164
2208
  "idempotentHint": true,
2209
+ "destructiveHint": false,
2165
2210
  "openWorldHint": false
2166
2211
  },
2167
2212
  "execution": {
@@ -2170,7 +2215,7 @@
2170
2215
  },
2171
2216
  {
2172
2217
  "name": "getReport",
2173
- "description": "Retrieves one entire report as JSON, with all of its issues, violators, and diagnoses. Use this instead of drilling down through listIssues, listViolators, and listDiagnoses when the full report is needed in one call. Returns the complete report data for the specified timestamp and job identifier.",
2218
+ "description": "Retrieves one entire report as JSON, with all of its issues, violators, and diagnoses. Use this instead of drilling down through listIssues, listViolators, and listDiagnoses when the full report is needed in one call. Returns the complete report data for the specified report identifier. The size of the report is disclosed by the listIssues tool.",
2174
2219
  "inputSchema": {
2175
2220
  "type": "object",
2176
2221
  "properties": {
@@ -2426,8 +2471,8 @@
2426
2471
  "annotations": {
2427
2472
  "title": "Get a full report",
2428
2473
  "readOnlyHint": true,
2429
- "destructiveHint": false,
2430
2474
  "idempotentHint": true,
2475
+ "destructiveHint": false,
2431
2476
  "openWorldHint": false
2432
2477
  },
2433
2478
  "execution": {
@@ -2436,7 +2481,7 @@
2436
2481
  },
2437
2482
  {
2438
2483
  "name": "requestNewTest",
2439
- "description": "Submits a request to test a page that has no report yet; queued for manual approval. Use orderNewTest instead for immediate, automatically approved testing. Returns a request confirmation with disposition details.",
2484
+ "description": "Submits a request to test a page that has no report yet; queued for manager approval. Use orderNewTest instead for immediate, automatically approved testing. Returns a request confirmation with disposition details.",
2440
2485
  "inputSchema": {
2441
2486
  "type": "object",
2442
2487
  "properties": {
@@ -2793,8 +2838,8 @@
2793
2838
  "annotations": {
2794
2839
  "title": "Request testing of a new page",
2795
2840
  "readOnlyHint": false,
2796
- "destructiveHint": false,
2797
2841
  "idempotentHint": false,
2842
+ "destructiveHint": false,
2798
2843
  "openWorldHint": true
2799
2844
  },
2800
2845
  "execution": {
@@ -2803,7 +2848,7 @@
2803
2848
  },
2804
2849
  {
2805
2850
  "name": "requestRetest",
2806
- "description": "Submits a request to retest a page whose existing report is obsolete; queued for manual approval. Use orderRetest instead for immediate, automatically approved retesting. Returns a request confirmation with disposition details.",
2851
+ "description": "Submits a request to retest a page whose existing report is considered obsolete; queued for manager approval. Use orderRetest instead for immediate, automatically approved retesting. Returns a request confirmation with disposition details.",
2807
2852
  "inputSchema": {
2808
2853
  "type": "object",
2809
2854
  "properties": {
@@ -3145,8 +3190,8 @@
3145
3190
  "annotations": {
3146
3191
  "title": "Request a retest",
3147
3192
  "readOnlyHint": false,
3148
- "destructiveHint": false,
3149
3193
  "idempotentHint": false,
3194
+ "destructiveHint": false,
3150
3195
  "openWorldHint": true
3151
3196
  },
3152
3197
  "execution": {
@@ -3155,7 +3200,7 @@
3155
3200
  },
3156
3201
  {
3157
3202
  "name": "requestFeature",
3158
- "description": "Submits a request to add a new feature or improve an existing one; queued for manual review. There is no immediate-approval alternative for feature requests. Returns a request confirmation with disposition details.",
3203
+ "description": "Submits a request to add a new feature or improve an existing one; queued for manager review. There is no immediate-approval alternative for feature requests. Returns a request confirmation with disposition details.",
3159
3204
  "inputSchema": {
3160
3205
  "type": "object",
3161
3206
  "properties": {
@@ -3427,8 +3472,8 @@
3427
3472
  "annotations": {
3428
3473
  "title": "Request a feature",
3429
3474
  "readOnlyHint": false,
3430
- "destructiveHint": false,
3431
3475
  "idempotentHint": false,
3476
+ "destructiveHint": false,
3432
3477
  "openWorldHint": false
3433
3478
  },
3434
3479
  "execution": {
@@ -3765,8 +3810,8 @@
3765
3810
  "annotations": {
3766
3811
  "title": "Report disagreement with a diagnosis",
3767
3812
  "readOnlyHint": false,
3768
- "destructiveHint": false,
3769
3813
  "idempotentHint": false,
3814
+ "destructiveHint": false,
3770
3815
  "openWorldHint": false
3771
3816
  },
3772
3817
  "execution": {
@@ -3775,7 +3820,7 @@
3775
3820
  },
3776
3821
  {
3777
3822
  "name": "orderNewTest",
3778
- "description": "Orders testing of a page that has no report yet, with automated approval or rejection and immediate enqueueing if approved. Use this instead of requestNewTest when immediate testing is wanted; testing typically takes 2 to 4 minutes. Returns an order confirmation and disposition details, including, if approved, a report identifier (timestamp and job identifier) and a question about whether to wait for completion with the awaitTest tool.",
3823
+ "description": "Orders testing of a page that has no report yet, with automated approval or rejection and immediate enqueueing if approved. Use this instead of requestNewTest when immediate testing is wanted; testing typically takes 2 to 4 minutes. Returns an order confirmation and disposition details, including, if approved, a report identifier and a question about whether to wait for completion with the awaitTest tool.",
3779
3824
  "inputSchema": {
3780
3825
  "type": "object",
3781
3826
  "properties": {
@@ -4140,8 +4185,8 @@
4140
4185
  "annotations": {
4141
4186
  "title": "Order immediate testing of a new page",
4142
4187
  "readOnlyHint": false,
4143
- "destructiveHint": false,
4144
4188
  "idempotentHint": false,
4189
+ "destructiveHint": false,
4145
4190
  "openWorldHint": true
4146
4191
  },
4147
4192
  "execution": {
@@ -4150,7 +4195,7 @@
4150
4195
  },
4151
4196
  {
4152
4197
  "name": "orderRetest",
4153
- "description": "Orders a retest of a page whose existing report is considered obsolete, with automated approval or rejection and immediate enqueuing if approved. Use this instead of requestRetest when immediate retesting is wanted; testing typically takes 2 to 4 minutes. Returns an order confirmation and disposition details, including, if approved, a report identifier (timestamp and job identifier) and a question about whether to wait for completion with the awaitTest tool.",
4198
+ "description": "Orders a retest of a page whose existing report is considered obsolete, with automated approval or rejection and immediate enqueuing if approved. Use this instead of requestRetest when immediate retesting is wanted; testing typically takes 2 to 4 minutes. Returns an order confirmation and disposition details, including, if approved, a report identifier and a question about whether to wait for completion with the awaitTest tool.",
4154
4199
  "inputSchema": {
4155
4200
  "type": "object",
4156
4201
  "properties": {
@@ -4500,8 +4545,8 @@
4500
4545
  "annotations": {
4501
4546
  "title": "Order an immediate retest",
4502
4547
  "readOnlyHint": false,
4503
- "destructiveHint": false,
4504
4548
  "idempotentHint": false,
4549
+ "destructiveHint": false,
4505
4550
  "openWorldHint": true
4506
4551
  },
4507
4552
  "execution": {
@@ -4809,14 +4854,13 @@
4809
4854
  "annotations": {
4810
4855
  "title": "Wait for an ordered job to finish",
4811
4856
  "readOnlyHint": true,
4812
- "destructiveHint": false,
4813
4857
  "idempotentHint": false,
4858
+ "destructiveHint": false,
4814
4859
  "openWorldHint": false
4815
4860
  },
4816
4861
  "execution": {
4817
4862
  "taskSupport": "forbidden"
4818
4863
  }
4819
4864
  }
4820
- ],
4821
- "version": "63.1.8"
4865
+ ]
4822
4866
  }
package/llms-full.txt CHANGED
@@ -101,6 +101,13 @@ Kilotest offers these tools:
101
101
  - `issueID`: identifier of the issue
102
102
  - `reportID`: identifier of the report, with YYMMDDTHHmm format for its initial segment (example: 260503T0432-x9z)
103
103
  - source of parameters: response to a `listViolators` request
104
+ - `getReport`
105
+ - method: `GET`
106
+ - purpose: get one full report in JSON
107
+ - path: `/api/getReport/{reportID}`
108
+ - parameters
109
+ - `reportID`: identifier of the report, with YYMMDDTHHmm format for its initial segment (example: 260503T0432-x9z)
110
+ - source of parameters: response to a `listIssues` request
104
111
  - `requestNewTest`
105
112
  - method: `POST`
106
113
  - purpose: request testing of one web page about which no report is available
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # Kilotest
2
2
 
3
- Kilotest integrates an ensemble of 12 rule engines, with about 1,300 rules, to test web pages for front-end quality (accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG, including HTML, CSS, ARIA, and WCAG). Each test job generates a standardized report describing which elements violated which rules and grouping the violations into about 380 issues. Kilotest can list the tested pages, the reports about each page, the issues in each report, the violators of each issue, and the diagnoses of each violation. Kilotest also accepts requests to test pages not yet tested and to retest already tested pages. Kilotest features are available via a web UI, an API, and an MCP server with streamable HTTP and no authentication.
3
+ Kilotest integrates an ensemble of 12 rule engines, with about 1,300 rules, to test web pages for front-end quality (accessibility, usability, and W3C web standards, including HTML, CSS, ARIA, and WCAG). Each test job generates a standardized report describing which elements violated which rules and grouping the violations into about 380 issues. Kilotest can list the tested pages, the reports about each page, the issues in each report, the violators of each issue, and the diagnoses of each violation. Kilotest also accepts requests to test pages not yet tested and to retest already tested pages. Kilotest features are available via a web UI, an API, and an MCP server with streamable HTTP and no authentication.
4
4
 
5
5
  ## API
6
6
 
package/mcp.ts CHANGED
@@ -22,6 +22,7 @@ import * as awaitTestAPI from './api/awaitTest.ts';
22
22
  import * as reportDisagreementAPI from './api/reportDisagreement.ts';
23
23
  import {version} from './api/version.ts';
24
24
  import {recordMetric} from './util.ts';
25
+ import {collectionDescription, sharedContext} from './content.ts';
25
26
 
26
27
  import {
27
28
  getReportSchema,
@@ -53,17 +54,10 @@ import {
53
54
 
54
55
  export const mcpPath = '/mcp';
55
56
 
56
- // Shared statement of the Kilotest domain and data hierarchy, repeated in the server
57
- // instructions and in the getKilotestOverview tool description (but not in the other tool
58
- // descriptions, to avoid diluting how models read each tool), because some MCP clients do
59
- // not propagate server-level instructions or the server description to their models.
60
- const sharedContext = 'Kilotest integrates an ensemble of 12 rule engines, with about 1,300 rules, to test web pages for front-end quality (accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG). Each test job generates a standardized report describing which elements violated which rules and grouping the violations into about 380 issues. Kilotest can list the tested pages, the reports about each page, the issues in each report, the violators of each issue, and the diagnoses of each violation. Kilotest also accepts requests to test pages not yet tested and to retest already tested pages.';
61
-
62
57
  // Detailed overview of the Kilotest domain, data hierarchy, and tool workflow, returned by
63
58
  // the getKilotestOverview tool and served as the docs://kilotest/overview resource.
64
59
  const kilotestOverview = [
65
- 'Kilotest tests web pages for front-end quality: accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG.',
66
- 'Kilotest integrates an ensemble of twelve independent rule engines to test a web page and stores the results as a structured report.',
60
+ sharedContext,
67
61
  'Results are organized as a hierarchy: tested pages; for each tested page, reports from the tests of the page; for each report, issues reported; for each issue, violators of rules belonging to the issue (a violator is an element or the whole page); for each violator, diagnoses by rule engines of its violations.',
68
62
  'Typical workflow: call the listPages tool to learn whether the page has been tested. If it has, drill down from its latest report with the listIssues, listViolators, and listDiagnoses tools, or retrieve the entire report with the getReport tool. The listIssues output also lists all reports about the same page, so you can examine earlier reports. If the page has not been tested, request a test with the requestNewTest tool or order a test with the orderNewTest tool. If the latest report is obsolete, request a retest with the requestRetest tool or order a retest with the orderRetest tool.',
69
63
  'The request tools submit requests for manager approval. The order tools obtain automated review and approval or rejection. Approval produces a report identifier and an opportunity to wait for completion. To wait, call the awaitTest tool with that identifier; it blocks until the report is ready, the job fails, or a maximum wait time elapses. To check later instead, call the listIssues tool with the same identifier.',
@@ -77,7 +71,7 @@ export const createMCPServer = (): McpServer => {
77
71
  const server = new McpServer({
78
72
  name: 'Kilotest',
79
73
  version,
80
- description: 'Tools that test web pages for front-end quality (accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG) and make test results available'
74
+ description: collectionDescription
81
75
  },
82
76
  {
83
77
  instructions: `${sharedContext} Use the listPages tool to start. If it shows that the page you want facts about has been tested, drill down from its latest report with the listIssues, listViolators, and listDiagnoses tools. If not, use the requestNewTest tool to request that the page be tested, or the orderNewTest tool for immediate, automatically approved testing. If the latest report about the page is obsolete, use the requestRetest tool to request that the page be retested, or the orderRetest tool for immediate, automatically approved retesting. For detailed documentation, call the getKilotestOverview tool.`
package/openapi-3.0.yaml CHANGED
@@ -1,8 +1,8 @@
1
1
  openapi: 3.0.3
2
2
  info:
3
3
  title: Kilotest API
4
- description: 'Kilotest is an ensemble testing and reporting service for front-end web quality. It uses an ensemble of 12 rule engines to test web pages for front-end quality (accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG). Kilotest acts as a web server for human users, an MCP server for AI platforms, and an API for programmatic access. With this API you can request that a page be tested or retested, discover available reports, incrementally retrieve facts from a report: from what reports are available, to what issues were reported in one report, to what elements violated one issue, to what diagnoses rule engines gave for one violation.'
5
- version: 63.1.8
4
+ description: 'Kilotest integrates an ensemble of 12 rule engines, with about 1,300 rules, to test web pages for front-end quality (accessibility, usability, and W3C web standards, including HTML, CSS, ARIA, and WCAG). Each test job generates a standardized report describing which elements violated which rules and grouping the violations into about 380 issues. Kilotest can list the tested pages, the reports about each page, the issues in each report, the violators of each issue, and the diagnoses of each violation. Kilotest also accepts requests to test pages not yet tested and to retest already tested pages. Kilotest acts as a web server for human users, an MCP server for AI platforms, and an API for programmatic access. With this API you can request that a page be tested or retested, discover available reports, and incrementally retrieve facts from a report: from what reports are available, to what issues were reported in one report, to what elements violated one issue, to what diagnoses rule engines gave for one violation.'
5
+ version: 63.2.1
6
6
  license:
7
7
  name: MIT
8
8
  url: https://github.com/jrpool/kilotest/blob/main/LICENSE
package/openapi.yaml CHANGED
@@ -1,8 +1,8 @@
1
1
  openapi: 3.1.0
2
2
  info:
3
3
  title: Kilotest API
4
- description: 'Kilotest is an ensemble testing and reporting service for front-end web quality. It uses an ensemble of 12 rule engines to test web pages for front-end quality (accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG). Kilotest acts as a web server for human users, an MCP server for AI platforms, and an API for programmatic access. With this API you can request that a page be tested or retested, discover available reports, incrementally retrieve facts from a report: from what reports are available, to what issues were reported in one report, to what elements violated one issue, to what diagnoses rule engines gave for one violation.'
5
- version: 63.1.8
4
+ description: 'Kilotest integrates an ensemble of 12 rule engines, with about 1,300 rules, to test web pages for front-end quality (accessibility, usability, and W3C web standards, including HTML, CSS, ARIA, and WCAG). Each test job generates a standardized report describing which elements violated which rules and grouping the violations into about 380 issues. Kilotest can list the tested pages, the reports about each page, the issues in each report, the violators of each issue, and the diagnoses of each violation. Kilotest also accepts requests to test pages not yet tested and to retest already tested pages. Kilotest acts as a web server for human users, an MCP server for AI platforms, and an API for programmatic access. With this API you can request that a page be tested or retested, discover available reports, and incrementally retrieve facts from a report: from what reports are available, to what issues were reported in one report, to what elements violated one issue, to what diagnoses rule engines gave for one violation.'
5
+ version: 63.2.1
6
6
  license:
7
7
  name: MIT
8
8
  identifier: MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jrpool/kilotest",
3
- "version": "63.1.8",
3
+ "version": "63.2.1",
4
4
  "description": "Ensemble testing for front-end web quality",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -10,11 +10,11 @@
10
10
  "scripts": {
11
11
  "start": "node index.ts",
12
12
  "typecheck": "tsc --noEmit",
13
- "lint": "npx eslint && npx htmlhint web error.html && out=$(npx markdownlint-cli2 2>&1); code=$?; if [ $code -ne 0 ]; then echo \"$out\"; exit $code; fi",
13
+ "lint": "npx eslint && npx htmlhint web error.html && out=$(npx markdownlint-cli2 2>&1); code=$?; if [ $code -ne 0 ]; then echo \"$out\"; exit $code; fi && node scripts/check-docs.ts",
14
14
  "test": "c8 node --require ./test/setup.ts --experimental-test-module-mocks --test --test-concurrency=1 --test-randomize '**/*.test.ts'",
15
15
  "test:smoke": "node smokeTest.ts",
16
16
  "checkinfo": "node checkinfo.ts",
17
- "generate": "node scripts/generate-openapi.ts && node scripts/update-versions.ts",
17
+ "generate": "node scripts/generate-openapi.ts && node scripts/generate-lhm.ts && node scripts/generate-llms.ts && node scripts/update-versions.ts",
18
18
  "build:media": "node scripts/buildDemoVideo.ts",
19
19
  "publish:lhm": "npx -y @lobehub/market-cli plugin update --dir .",
20
20
  "prePR": "node scripts/prePR.ts",
@@ -0,0 +1,59 @@
1
+ /*
2
+ check-docs.ts
3
+ Verifies that the `/api/...` path templates (including path-parameter names and order)
4
+ quoted in llms.txt and llms-full.txt match api/routes.ts, the single source of truth for
5
+ API methods and paths. Fails with a listing of mismatches if any API path in those files is
6
+ missing, stale, or renders a route not present in api/routes.ts, and fails if any route in
7
+ api/routes.ts is not documented in a file. Run via `npm run lint`.
8
+ */
9
+
10
+ // IMPORTS
11
+
12
+ import fs from 'node:fs';
13
+ import path from 'node:path';
14
+ import {routes} from '../api/routes.ts';
15
+
16
+ // CONSTANTS
17
+
18
+ const rootDir = path.join(import.meta.dirname, '..');
19
+ const targetFiles = ['llms.txt', 'llms-full.txt'];
20
+ const apiPathPattern = /\/api\/[A-Za-z]+(?:\/\{[A-Za-z]+\})*/g;
21
+ const expectedPaths = new Set(routes.map(route => route.path));
22
+
23
+ // FUNCTIONS
24
+
25
+ // Returns the operationId of a path template, i.e. the segment right after "/api/".
26
+ const operationIdOf = (apiPath: string): string => apiPath.split('/')[2] ?? '';
27
+
28
+ // EXECUTION
29
+
30
+ const errors: string[] = [];
31
+ for (const fileName of targetFiles) {
32
+ const filePath = path.join(rootDir, fileName);
33
+ const content = fs.readFileSync(filePath, 'utf8');
34
+ const found = new Set(content.match(apiPathPattern) ?? []);
35
+ for (const apiPath of found) {
36
+ if (!expectedPaths.has(apiPath)) {
37
+ const operationId = operationIdOf(apiPath);
38
+ const route = routes.find(candidate => candidate.operationId === operationId);
39
+ errors.push(
40
+ route
41
+ ? `${fileName}: "${apiPath}" does not match api/routes.ts, which defines ${operationId} as "${route.path}"`
42
+ : `${fileName}: "${apiPath}" does not match any operationId in api/routes.ts`
43
+ );
44
+ }
45
+ }
46
+ for (const route of routes) {
47
+ if (!found.has(route.path)) {
48
+ errors.push(`${fileName}: missing the path "${route.path}" documented in api/routes.ts for ${route.operationId}`);
49
+ }
50
+ }
51
+ }
52
+ if (errors.length) {
53
+ console.error('Documentation path/parameter mismatches found:');
54
+ for (const error of errors) {
55
+ console.error(` ${error}`);
56
+ }
57
+ process.exit(1);
58
+ }
59
+ console.log(`API paths in ${targetFiles.join(' and ')} match api/routes.ts.`);
@@ -0,0 +1,121 @@
1
+ /*
2
+ generate-lhm.ts
3
+ Regenerates the description and tool entries (description, inputSchema, outputSchema,
4
+ annotations) of lhm.plugin.json from the single sources of truth: content.ts for prose and
5
+ api/schemas.ts (via the same registrations mcp.ts makes) for schemas, and (re)writes a
6
+ "_generated" warning property so editors of the raw JSON see that those fields are
7
+ overwritten on the next generation. version has its own generator, update-versions.ts, which
8
+ copies it from package.json. Every remaining property (author, cloudEndpoint, homepage,
9
+ identifier, name, prompts, resources, tags, and each tool's execution block) has no
10
+ generator anywhere in the repository, so it is left untouched here and is meant to be
11
+ edited by hand.
12
+ */
13
+
14
+ // IMPORTS
15
+
16
+ import fs from 'node:fs';
17
+ import path from 'node:path';
18
+ import {z} from 'zod';
19
+ import {createMCPServer} from '../mcp.ts';
20
+ import {collectionDescription} from '../content.ts';
21
+ import {
22
+ listIssuesSchema,
23
+ listViolatorsSchema,
24
+ listDiagnosesSchema,
25
+ getReportSchema,
26
+ requestNewTestSchema,
27
+ requestRetestSchema,
28
+ requestFeatureSchema,
29
+ orderNewTestSchema,
30
+ orderRetestSchema,
31
+ awaitTestSchema,
32
+ reportDisagreementSchema,
33
+ listPagesResponseSchema,
34
+ listIssuesResponseSchema,
35
+ listViolatorsResponseSchema,
36
+ listDiagnosesResponseSchema,
37
+ getReportResponseSchema,
38
+ requestNewTestResponseSchema,
39
+ requestRetestResponseSchema,
40
+ requestFeatureResponseSchema,
41
+ orderNewTestResponseSchema,
42
+ orderRetestResponseSchema,
43
+ awaitTestResponseSchema,
44
+ reportDisagreementResponseSchema
45
+ } from '../api/schemas.ts';
46
+
47
+ // TYPES
48
+
49
+ type ZodRawShape = Record<string, z.ZodTypeAny>;
50
+
51
+ // CONSTANTS
52
+
53
+ const outputPath = path.join(import.meta.dirname, '..', 'lhm.plugin.json');
54
+ const jsonSchemaOptions = {target: 'draft-7' as const, unrepresentable: 'any' as const};
55
+ // Edit warning (disregarded on publication per https://market.lobehub.com/s/publish-mcp/references/manifest.md).
56
+ const generatedWarning = 'Do not edit the _generated, version, description, or tools property. They are automatically generated from content.ts, api/schemas.ts/mcp.ts, scripts/generate-lhm.ts, and scripts/update-versions.ts.';
57
+
58
+ // The input and output schemas mcp.ts passes to registerTool for each tool, keyed by tool name, matching mcp.ts's own registerTool calls (mcp.ts remains the source for descriptions and annotations, read at runtime below via createMCPServer).
59
+ const toolSchemas: Record<string, {input: ZodRawShape; output?: z.ZodTypeAny}> = {
60
+ getKilotestOverview: {input: {}},
61
+ listPages: {input: {}, output: listPagesResponseSchema},
62
+ listIssues: {input: listIssuesSchema, output: listIssuesResponseSchema},
63
+ listViolators: {input: listViolatorsSchema, output: listViolatorsResponseSchema},
64
+ listDiagnoses: {input: listDiagnosesSchema, output: listDiagnosesResponseSchema},
65
+ getReport: {input: getReportSchema, output: getReportResponseSchema},
66
+ requestNewTest: {input: requestNewTestSchema, output: requestNewTestResponseSchema},
67
+ requestRetest: {input: requestRetestSchema, output: requestRetestResponseSchema},
68
+ requestFeature: {input: requestFeatureSchema, output: requestFeatureResponseSchema},
69
+ reportDisagreement: {input: reportDisagreementSchema, output: reportDisagreementResponseSchema},
70
+ orderNewTest: {input: orderNewTestSchema, output: orderNewTestResponseSchema},
71
+ orderRetest: {input: orderRetestSchema, output: orderRetestResponseSchema},
72
+ awaitTest: {input: awaitTestSchema, output: awaitTestResponseSchema}
73
+ };
74
+
75
+ // FUNCTIONS
76
+
77
+ // Builds the lhm.plugin.json inputSchema for a tool: like outputSchema below, but LobeHub’sexisting input schemas never carry additionalProperties, so that key is dropped.
78
+ const buildInputSchema = (shape: ZodRawShape): Record<string, unknown> => {
79
+ const generated = z.toJSONSchema(z.object(shape), jsonSchemaOptions) as Record<string, unknown>;
80
+ delete generated.additionalProperties;
81
+ const {$schema, ...withoutSchemaKeyword} = generated;
82
+ return {...withoutSchemaKeyword, $schema};
83
+ };
84
+ // Builds the lhm.plugin.json outputSchema for a tool, reordering keys to match the file’s existing convention ($schema placed just before additionalProperties and definitions).
85
+ const buildOutputSchema = (schema: z.ZodTypeAny): Record<string, unknown> => {
86
+ const generated = z.toJSONSchema(schema, jsonSchemaOptions) as Record<string, unknown>;
87
+ const {$schema, additionalProperties, definitions, ...rest} = generated;
88
+ return {
89
+ ...rest,
90
+ ...($schema !== undefined ? {$schema} : {}),
91
+ ...(additionalProperties !== undefined ? {additionalProperties} : {}),
92
+ ...(definitions !== undefined ? {definitions} : {})
93
+ };
94
+ };
95
+
96
+ // EXECUTION
97
+
98
+ const server = createMCPServer();
99
+ const registeredTools = (server as unknown as {_registeredTools: Record<string, {description: string; annotations: unknown}>})._registeredTools;
100
+ const parsedManifest = JSON.parse(fs.readFileSync(outputPath, 'utf8'));
101
+ // Places "_generated" first regardless of where (or whether) it already appears, so it is the first thing a reader of the raw JSON sees. The explicit reassignment after the spread is required even though "_generated" is already a key: were the input file's own (possibly stale) "_generated" value to spread in after it, the spread’s last-write-wins rule would silently keep that stale value in place, even though the key's position stays first either way. Reassigning the other keys below does not move their position, since they already exist on this object.
102
+ const manifest = {_generated: generatedWarning, ...parsedManifest};
103
+ manifest._generated = generatedWarning;
104
+ manifest.description = collectionDescription;
105
+ manifest.tools = manifest.tools.map((tool: {name: string; execution: unknown}) => {
106
+ const registered = registeredTools[tool.name];
107
+ const schemas = toolSchemas[tool.name];
108
+ if (!registered || !schemas) {
109
+ throw new Error(`No registration or schema mapping found for lhm.plugin.json tool "${tool.name}"`);
110
+ }
111
+ return {
112
+ name: tool.name,
113
+ description: registered.description,
114
+ inputSchema: buildInputSchema(schemas.input),
115
+ ...(schemas.output ? {outputSchema: buildOutputSchema(schemas.output)} : {}),
116
+ annotations: registered.annotations,
117
+ execution: tool.execution
118
+ };
119
+ });
120
+ fs.writeFileSync(outputPath, `${JSON.stringify(manifest, null, 2)}\n`);
121
+ console.log(`Wrote ${outputPath}`);
@@ -0,0 +1,34 @@
1
+ /*
2
+ generate-llms.ts
3
+ Regenerates the opening paragraph of llms.txt from content.ts's sharedContext, the single
4
+ source of truth content.ts already documents that paragraph as reusing (along with the MCP
5
+ server instructions, the openapi.yaml description, and the getKilotestOverview tool
6
+ description). Leaves the rest of llms.txt untouched, because it has no canonical source
7
+ elsewhere in the repository.
8
+ */
9
+
10
+ // IMPORTS
11
+
12
+ import fs from 'node:fs';
13
+ import path from 'node:path';
14
+ import {sharedContext} from '../content.ts';
15
+
16
+ // CONSTANTS
17
+
18
+ const outputPath = path.join(import.meta.dirname, '..', 'llms.txt');
19
+ // Text appended after sharedContext to form the full opening paragraph; unlike sharedContext,
20
+ // this sentence is specific to llms.txt (it is not reused anywhere else), so it lives here
21
+ // rather than in content.ts.
22
+ const openingSuffix = ' Kilotest features are available via a web UI, an API, and an MCP server with streamable HTTP and no authentication.';
23
+ const openingParagraph = `${sharedContext}${openingSuffix}`;
24
+
25
+ // EXECUTION
26
+
27
+ const lines = fs.readFileSync(outputPath, 'utf8').split('\n');
28
+ const paragraphIndex = lines.findIndex(line => line.startsWith('Kilotest integrates an ensemble of'));
29
+ if (paragraphIndex === -1) {
30
+ throw new Error(`Could not find the opening paragraph to regenerate in ${outputPath}`);
31
+ }
32
+ lines[paragraphIndex] = openingParagraph;
33
+ fs.writeFileSync(outputPath, lines.join('\n'));
34
+ console.log(`Wrote ${outputPath}`);
@@ -12,6 +12,7 @@ import {createDocument} from 'zod-openapi';
12
12
  import pkg from '../package.json' with {type: 'json'};
13
13
  import {routes} from '../api/routes.ts';
14
14
  import {version} from '../api/version.ts';
15
+ import {sharedContext} from '../content.ts';
15
16
 
16
17
  // CONSTANTS
17
18
 
@@ -137,7 +138,7 @@ const generate = () => {
137
138
  openapi: '3.1.0',
138
139
  info: {
139
140
  title: 'Kilotest API',
140
- description: 'Kilotest is an ensemble testing and reporting service for front-end web quality. It uses an ensemble of 12 rule engines to test web pages for front-end quality (accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG). Kilotest acts as a web server for human users, an MCP server for AI platforms, and an API for programmatic access. With this API you can request that a page be tested or retested, discover available reports, incrementally retrieve facts from a report: from what reports are available, to what issues were reported in one report, to what elements violated one issue, to what diagnoses rule engines gave for one violation.',
141
+ description: `${sharedContext} Kilotest acts as a web server for human users, an MCP server for AI platforms, and an API for programmatic access. With this API you can request that a page be tested or retested, discover available reports, and incrementally retrieve facts from a report: from what reports are available, to what issues were reported in one report, to what elements violated one issue, to what diagnoses rule engines gave for one violation.`,
141
142
  version,
142
143
  license: {
143
144
  name: license,
package/scripts/prePR.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  prePR.ts
3
- Prepares a release pull request on the current feature branch: verifies a clean working tree, bumps the package version, regenerates the generated files (openapi.yaml, openapi-3.0.yaml, server.json, and lhm.plugin.json), commits and pushes the result, records the branch name for postPR.ts, and prints the remaining manual steps. The generation is run explicitly because the project's .npmrc sets ignore-scripts=true, which disables the npm "version" lifecycle hook that would otherwise run it.
3
+ Prepares a release pull request on the current feature branch: verifies a clean working tree, bumps the package version, regenerates the generated files (openapi.yaml, openapi-3.0.yaml, lhm.plugin.json, the opening paragraph of llms.txt, server.json, and the version field of lhm.plugin.json), commits and pushes the result, records the branch name for postPR.ts, and prints the remaining manual steps. The generation is run explicitly because the project's .npmrc sets ignore-scripts=true, which disables the npm "version" lifecycle hook that would otherwise run it.
4
4
  */
5
5
 
6
6
  // IMPORTS
package/server.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "url": "https://github.com/jrpool/kilotest",
7
7
  "source": "github"
8
8
  },
9
- "version": "63.1.8",
9
+ "version": "63.2.1",
10
10
  "remotes": [
11
11
  {
12
12
  "type": "streamable-http",