semantic-js-mcp 0.9.0 → 0.10.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/.codex-plugin/plugin.json +1 -1
- package/.prettierrc.json +1 -0
- package/CHANGELOG.md +29 -0
- package/CONTRIBUTING.md +6 -2
- package/README.md +25 -3
- package/ROADMAP.md +1 -1
- package/docs/distribution.md +27 -0
- package/docs/getting-started.md +72 -0
- package/docs/result-schema-migration.md +11 -0
- package/lib/doctor.mjs +3 -2
- package/lib/file-identity.mjs +18 -0
- package/lib/pending-requests.mjs +38 -0
- package/lib/runtime.mjs +4 -2
- package/lib/semantic-evidence.mjs +44 -0
- package/lib/stable-collection.mjs +10 -0
- package/lib/temporary-directory.mjs +15 -0
- package/package.json +10 -4
- package/protocol.mjs +32 -4
- package/scripts/agent-evaluation-contract.mjs +24 -1
- package/scripts/check-protocol-literals.mjs +10 -0
- package/scripts/ci-smoke.mjs +58 -1
- package/scripts/codex-plugin-verification.mjs +19 -0
- package/scripts/distribution-policy.mjs +1 -7
- package/scripts/distribution-smoke.mjs +17 -13
- package/scripts/doctor-smoke.mjs +40 -0
- package/scripts/documentation-contract.mjs +47 -0
- package/scripts/documentation-gate-smoke.mjs +74 -0
- package/scripts/documentation-gate.mjs +114 -0
- package/scripts/generate-protocol-reference.mjs +10 -0
- package/scripts/lifecycle-memory-benchmark.mjs +300 -0
- package/scripts/lifecycle-memory-contract.mjs +53 -0
- package/scripts/lifecycle-memory-observer.mjs +29 -0
- package/scripts/lifecycle-smoke.mjs +309 -0
- package/scripts/negative-verification-smoke.mjs +243 -0
- package/scripts/postpublication-smoke.mjs +14 -15
- package/scripts/publish-workflow-smoke.mjs +62 -0
- package/scripts/release-contract.mjs +10 -0
- package/scripts/release-smoke.mjs +22 -1
- package/scripts/semantic-js-mcp-ci.mjs +35 -19
- package/scripts/smoke.mjs +87 -14
- package/scripts/vue-smoke.mjs +32 -2
- package/server.mjs +306 -124
- package/skills/semantic-navigation/SKILL.md +2 -0
- package/skills/semantic-navigation/references/protocol-literals.md +32 -4
package/.prettierrc.json
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,35 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.10.1] - 2026-07-17
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Publish release tags to npm through a protected GitHub Actions environment using short-lived OIDC credentials and automatic provenance.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Document and validate marketplace refresh before reinstalling an existing Codex plugin.
|
|
16
|
+
- Install and verify ripgrep in the trusted-publishing runner before executing the release gate.
|
|
17
|
+
|
|
18
|
+
## [0.10.0] - 2026-07-17
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- Add a deterministic semantic-evidence usability status to named counts and audits, with explicit follow-up reasons for incomplete collection or ambiguous definition selection.
|
|
23
|
+
- Add a repeated post-disposal memory benchmark that separates MCP heap, MCP resident memory, and child-provider resident memory under an explicitly scoped measurement method.
|
|
24
|
+
- Add cross-platform CI coverage for supported Node.js LTS lines.
|
|
25
|
+
- Add focused negative-verification and public-documentation gates.
|
|
26
|
+
- Add concise starter prompts and evidence examples for common agent workflows.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- Recover with fresh language-server and Vue tsserver clients after an initialized provider exits, while settling pending requests and diagnostics without leaving timeout handles active.
|
|
31
|
+
- Resolve cross-project import aliases consistently when a language server first returns the local import binding.
|
|
32
|
+
- Canonicalize Windows source positions when verifying and grouping cross-project references.
|
|
33
|
+
- Make release verification portable across Windows paths, executable shims, temporary-file locking, and checkout line endings.
|
|
34
|
+
- Keep declaration filtering and language-server client eviction stable across platform-specific provider output and slow initialization.
|
|
35
|
+
|
|
7
36
|
## [0.9.0] - 2026-07-16
|
|
8
37
|
|
|
9
38
|
### Changed
|
package/CONTRIBUTING.md
CHANGED
|
@@ -19,13 +19,14 @@ npm run doctor
|
|
|
19
19
|
npm run smoke:ci
|
|
20
20
|
npm run smoke
|
|
21
21
|
npm run smoke:vue
|
|
22
|
+
npm run smoke:lifecycle
|
|
22
23
|
npm run smoke:release
|
|
23
24
|
npm run smoke:evaluation
|
|
24
25
|
npm run smoke:matrix
|
|
25
26
|
npm run smoke:distribution
|
|
26
27
|
```
|
|
27
28
|
|
|
28
|
-
Run `npm run benchmark` for changes that affect repository scanning, reference verification, caching, lifecycle, or memory.
|
|
29
|
+
Run `npm run benchmark` for changes that affect repository scanning, reference verification, caching, lifecycle, or memory. Provider disposal changes should also run `npm run benchmark:lifecycle-memory`; it uses explicit garbage collection and POSIX process metrics, remains separate from the deterministic release gate, and reports its measurement scope.
|
|
29
30
|
|
|
30
31
|
## Source Of Truth
|
|
31
32
|
|
|
@@ -56,13 +57,16 @@ npm run check:runtime
|
|
|
56
57
|
npm run smoke:ci
|
|
57
58
|
npm run smoke
|
|
58
59
|
npm run smoke:vue
|
|
60
|
+
npm run smoke:lifecycle
|
|
59
61
|
npm run smoke:release
|
|
60
62
|
npm run smoke:evaluation
|
|
61
63
|
npm run smoke:matrix
|
|
62
64
|
npm run smoke:distribution
|
|
63
65
|
```
|
|
64
66
|
|
|
65
|
-
Also run `npm run benchmark` when collection cost or memory could change. Run `npm run doctor` when changing runtime resolution, provider startup, the CLI, or diagnostic trust. New behavior should use generic fixtures and cover complete, partial, limited, failed, stale, or untrusted outcomes as applicable.
|
|
67
|
+
Also run `npm run benchmark` when collection cost or memory could change, and `npm run benchmark:lifecycle-memory` when provider disposal behavior changes. Run `npm run doctor` when changing runtime resolution, provider startup, the CLI, or diagnostic trust. New behavior should use generic fixtures and cover complete, partial, limited, failed, stale, or untrusted outcomes as applicable.
|
|
68
|
+
|
|
69
|
+
`npm run check:documentation` validates the durable public documentation contract. `npm run smoke:negative` exercises missing runtime providers, invalid workspaces and limits, stale or expired reference sets, repository mutation, unresolved candidates, and diagnostic trust. The complete `npm run release:verify` gate includes both checks.
|
|
66
70
|
|
|
67
71
|
`npm run release:verify` executes the complete local release gate and reports every check in one machine-readable result. It does not publish, tag, or modify an installed plugin.
|
|
68
72
|
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ For each file, it identifies the owning workspace and uses the corresponding Typ
|
|
|
16
16
|
|
|
17
17
|
## Project Status
|
|
18
18
|
|
|
19
|
-
Version `0.
|
|
19
|
+
Version `0.10.1` is the current release. APIs and result contracts may evolve while the project remains on the `0.x` release line. See the [roadmap](ROADMAP.md) for areas under consideration.
|
|
20
20
|
|
|
21
21
|
## Installation
|
|
22
22
|
|
|
@@ -31,6 +31,18 @@ codex plugin add semantic-js-mcp@elnonathan
|
|
|
31
31
|
|
|
32
32
|
Codex CLI 0.144.4 or newer is required for npm-backed marketplace installation. Start a new Codex session after installation. The plugin bundles both the MCP server configuration and the semantic-navigation skill.
|
|
33
33
|
|
|
34
|
+
### Updating the Codex plugin
|
|
35
|
+
|
|
36
|
+
Codex installs from a local snapshot of each configured marketplace. Refresh that snapshot before reinstalling so `plugin add` resolves the version currently declared by the marketplace:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
codex plugin marketplace upgrade elnonathan
|
|
40
|
+
codex plugin add semantic-js-mcp@elnonathan
|
|
41
|
+
codex plugin list
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`plugin add` reinstalls the plugin; a separate removal is not required. Start a new Codex session after the command reports the expected version.
|
|
45
|
+
|
|
34
46
|
### MCP executable
|
|
35
47
|
|
|
36
48
|
Install the package globally when using an MCP host that accepts a command-based stdio configuration:
|
|
@@ -42,9 +54,11 @@ semantic-js-mcp doctor
|
|
|
42
54
|
|
|
43
55
|
Configure the host to run `semantic-js-mcp serve`.
|
|
44
56
|
|
|
57
|
+
For practical investigation prompts and compact evidence examples, see [Getting started](docs/getting-started.md).
|
|
58
|
+
|
|
45
59
|
## Runtime
|
|
46
60
|
|
|
47
|
-
- Node.js 22 or newer is
|
|
61
|
+
- Node.js 22 or newer is required.
|
|
48
62
|
- Node.js 24 LTS is recommended for new installations. See the [Node.js release schedule](https://nodejs.org/en/about/previous-releases).
|
|
49
63
|
- `rg` (ripgrep) must be available on `PATH` for repository-wide discovery.
|
|
50
64
|
- The nearest workspace TypeScript SDK is used when available; the bundled TypeScript SDK is the fallback.
|
|
@@ -66,9 +80,10 @@ npm run doctor
|
|
|
66
80
|
npm run smoke:ci
|
|
67
81
|
npm run smoke
|
|
68
82
|
npm run smoke:vue
|
|
83
|
+
npm run smoke:lifecycle
|
|
69
84
|
```
|
|
70
85
|
|
|
71
|
-
Run `npm run benchmark` after changes to scanning, references, caching, lifecycle, or memory. Normal analysis is local and read-only; it does not require network access after dependencies are installed.
|
|
86
|
+
Run `npm run benchmark` after changes to scanning, references, caching, lifecycle, or memory. Provider disposal changes can also be characterized with `npm run benchmark:lifecycle-memory`; its output declares the garbage-collection and platform measurement method. Normal analysis is local and read-only; it does not require network access after dependencies are installed.
|
|
72
87
|
|
|
73
88
|
For an MCP host that accepts a direct stdio configuration, point it at the checked-out server using an absolute path:
|
|
74
89
|
|
|
@@ -152,6 +167,8 @@ Reference pages group the locations returned in that page under `referenceGroups
|
|
|
152
167
|
|
|
153
168
|
Named counts and audits report `definitionSelectionStatus` as `no-definition-selected`, `one-definition-selected`, or `multiple-definitions-selected`. This status describes exact declarations remaining after `fileHint` filtering, before any `maxDefinitions` analysis limit. When selection is empty or ambiguous, `continueWith` recommends structural or position-based tools. It recommends `lsp_reference_page` only when the response contains a reusable reference set.
|
|
154
169
|
|
|
170
|
+
They also report `semanticEvidence.status` as `usable-as-requested` or `follow-up-required`. Follow-up reasons combine incomplete collection and ambiguous definition selection without treating a compact, paged, or subset presentation as incomplete evidence. This status evaluates only the requested semantic result; it does not declare the code correct.
|
|
171
|
+
|
|
155
172
|
When `fileHint` selects no declaration, a named audit can verify whether source bindings resolve to the hinted file while keeping declaration identity and uncertainty explicit.
|
|
156
173
|
|
|
157
174
|
### Limits
|
|
@@ -207,6 +224,7 @@ npm run doctor
|
|
|
207
224
|
npm run smoke
|
|
208
225
|
npm run smoke:ci
|
|
209
226
|
npm run smoke:vue
|
|
227
|
+
npm run smoke:lifecycle
|
|
210
228
|
npm run smoke:distribution
|
|
211
229
|
```
|
|
212
230
|
|
|
@@ -216,6 +234,10 @@ npm run smoke:distribution
|
|
|
216
234
|
|
|
217
235
|
The smoke tests create temporary generic TypeScript and Vue projects. They do not depend on a specific application repository.
|
|
218
236
|
|
|
237
|
+
## Reporting Problems
|
|
238
|
+
|
|
239
|
+
Run `semantic-js-mcp doctor` and include its structured output, the package version, Node.js version, operating system, affected source type, and the smallest reproducible workspace or public repository. Remove proprietary source, credentials, local absolute paths, and customer data before opening an issue. See [Contributing](CONTRIBUTING.md) for validation details and the [Security policy](SECURITY.md) for private vulnerability reports.
|
|
240
|
+
|
|
219
241
|
## Current Limitations
|
|
220
242
|
|
|
221
243
|
- The server provides static semantic evidence, not runtime call tracing or behavioral proof.
|
package/ROADMAP.md
CHANGED
|
@@ -28,7 +28,7 @@ Semantic JS MCP is under active development. This roadmap outlines areas of inve
|
|
|
28
28
|
|
|
29
29
|
## Distribution
|
|
30
30
|
|
|
31
|
-
-
|
|
31
|
+
- Maintain reproducible package and plugin release checks across supported Node.js LTS releases and operating systems.
|
|
32
32
|
- Document setup for additional MCP hosts using the same public server contract.
|
|
33
33
|
- Add immutable release-source, upgrade, rollback, and installed-plugin verification.
|
|
34
34
|
|
package/docs/distribution.md
CHANGED
|
@@ -11,6 +11,16 @@ codex plugin add semantic-js-mcp@elnonathan
|
|
|
11
11
|
|
|
12
12
|
The marketplace entry pins a concrete npm package version. Codex CLI 0.144.4 is the minimum verified client for npm-backed marketplace installation. A release updates the package version, plugin manifest, and marketplace entry together.
|
|
13
13
|
|
|
14
|
+
For an existing marketplace installation, refresh its local snapshot before reinstalling the plugin:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
codex plugin marketplace upgrade elnonathan
|
|
18
|
+
codex plugin add semantic-js-mcp@elnonathan
|
|
19
|
+
codex plugin list
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`plugin add` reinstalls the plugin selected by the refreshed marketplace. Start a new Codex session after the listed version matches the release being installed.
|
|
23
|
+
|
|
14
24
|
## Executable
|
|
15
25
|
|
|
16
26
|
```bash
|
|
@@ -45,9 +55,14 @@ Run the complete local validation sequence before reviewing a distribution chang
|
|
|
45
55
|
```bash
|
|
46
56
|
npm run check
|
|
47
57
|
npm run check:runtime
|
|
58
|
+
npm run check:documentation
|
|
48
59
|
npm run smoke:ci
|
|
60
|
+
npm run smoke:publish
|
|
61
|
+
npm run smoke:negative
|
|
62
|
+
npm run smoke:doctor
|
|
49
63
|
npm run smoke
|
|
50
64
|
npm run smoke:vue
|
|
65
|
+
npm run smoke:lifecycle
|
|
51
66
|
npm run smoke:distribution
|
|
52
67
|
```
|
|
53
68
|
|
|
@@ -63,3 +78,15 @@ npm run verify:published -- <version>
|
|
|
63
78
|
The local release gate runs every configured source, runtime, semantic, evaluation, distribution, and benchmark check and reports all failures instead of stopping at the first one. It performs no publication or installed-plugin mutation.
|
|
64
79
|
|
|
65
80
|
Postpublication verification requires an explicit version and the matching `v<version>` repository tag. It queries that immutable registry version, installs it with a fresh temporary npm cache and consumer project, verifies the installed executable and manifest, then runs the installed doctor to cover MCP startup, tool discovery, TypeScript evidence, and Vue navigation. It also installs the plugin from the tag-pinned marketplace inside a temporary `CODEX_HOME` and verifies the enabled plugin version. Temporary state is removed afterward. An unavailable registry, marketplace, or network is reported as `blocked`.
|
|
81
|
+
|
|
82
|
+
## npm Trusted Publishing
|
|
83
|
+
|
|
84
|
+
The `publish.yml` workflow publishes tags matching `v*` from a GitHub-hosted Node.js 24 runner. It requires the protected `npm-publish` environment, verifies that the tag exactly matches the package version, runs the complete release gate, and publishes through npm Trusted Publishing with OIDC. No long-lived npm token is used. npm generates provenance automatically for the public package.
|
|
85
|
+
|
|
86
|
+
Configure the npm trusted publisher for GitHub user `elnonathan`, repository `semantic-js-mcp`, workflow filename `publish.yml`, and environment `npm-publish`. Allow `npm publish` only. In package publishing access, require two-factor authentication and disallow tokens.
|
|
87
|
+
|
|
88
|
+
Create and push the matching `v<version>` tag only after the release commit is on `main` and its CI matrix passes. Approve the protected environment deployment, verify the published package, and then create the matching GitHub release.
|
|
89
|
+
|
|
90
|
+
## Continuous Integration
|
|
91
|
+
|
|
92
|
+
GitHub Actions runs the complete release gate on Node.js 22 and 24 across Ubuntu, macOS, and Windows. A second Linux matrix runs the same gate on Node.js 22 across Ubuntu, Fedora, Arch Linux, and openSUSE. The gate verifies ripgrep before running static checks, runtime resolution, documentation, CI policy, negative fixtures, doctor, TypeScript and Vue semantics, provider lifecycle, package installation, and the short benchmark.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Getting Started
|
|
2
|
+
|
|
3
|
+
Semantic JS MCP adds static semantic evidence to an agent's existing code investigation. These prompts are starting points; the agent should still inspect source, search text, and run focused checks when behavior matters.
|
|
4
|
+
|
|
5
|
+
## Trace A Symbol
|
|
6
|
+
|
|
7
|
+
> Trace the named symbol `parseRequest` across this repository. Measure its scope first, verify the exact declaration and references, then inspect its direct callers and callees. Report unresolved or incomplete evidence explicitly.
|
|
8
|
+
|
|
9
|
+
## Review A Security-Sensitive Change
|
|
10
|
+
|
|
11
|
+
> Review the authentication changes in this diff. Use semantic evidence to verify every material symbol and cross-workspace reference, corroborate reachability with text search and direct source inspection, and distinguish introduced defects from pre-existing behavior.
|
|
12
|
+
|
|
13
|
+
## Check Current Diagnostics
|
|
14
|
+
|
|
15
|
+
> Check current diagnostics for `src/handler.ts`. Treat the file as clean only if the language server confirms the current document snapshot; otherwise report the result as untrusted and explain the required follow-up.
|
|
16
|
+
|
|
17
|
+
## Reading Compact Results
|
|
18
|
+
|
|
19
|
+
The examples below show the decision-bearing fields. Actual responses also identify the producer, normalized request, presentation mode, and exact continuation tools.
|
|
20
|
+
|
|
21
|
+
### Complete Evidence
|
|
22
|
+
|
|
23
|
+
```yaml
|
|
24
|
+
result:
|
|
25
|
+
definitionSelectionStatus: one-definition-selected
|
|
26
|
+
semanticEvidence:
|
|
27
|
+
status: usable-as-requested
|
|
28
|
+
followUpReasons: []
|
|
29
|
+
collection:
|
|
30
|
+
status: complete
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
This supports the requested static symbol claim. It does not prove runtime behavior or approve a change.
|
|
34
|
+
|
|
35
|
+
### Partial Evidence
|
|
36
|
+
|
|
37
|
+
```yaml
|
|
38
|
+
result:
|
|
39
|
+
semanticEvidence:
|
|
40
|
+
status: follow-up-required
|
|
41
|
+
followUpReasons:
|
|
42
|
+
- collection-is-partial
|
|
43
|
+
collection:
|
|
44
|
+
status: partial
|
|
45
|
+
continueWith:
|
|
46
|
+
- lsp_unresolved_reference_page
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Inspect the unresolved candidates and relevant source before making a coverage claim.
|
|
50
|
+
|
|
51
|
+
### Untrusted Diagnostics
|
|
52
|
+
|
|
53
|
+
```yaml
|
|
54
|
+
result:
|
|
55
|
+
evidence:
|
|
56
|
+
status: untrusted
|
|
57
|
+
reason: language-server-did-not-report-current-document
|
|
58
|
+
diagnosticsForCurrentDocument: null
|
|
59
|
+
collection:
|
|
60
|
+
status: partial
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
An empty unconfirmed report is not a clean diagnostic result.
|
|
64
|
+
|
|
65
|
+
### Startup Failure
|
|
66
|
+
|
|
67
|
+
```yaml
|
|
68
|
+
status: blocked
|
|
69
|
+
reason: runtime-component-missing
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Run `semantic-js-mcp doctor` and report its structured runtime component evidence. A missing provider or unavailable command is an environment blocker, not a code pass or diagnostic result.
|
|
@@ -4,6 +4,17 @@ Semantic JS MCP increments `producer.resultSchemaVersion` when a tool-result
|
|
|
4
4
|
shape changes incompatibly. Consumers should validate this value before parsing
|
|
5
5
|
tool-specific evidence.
|
|
6
6
|
|
|
7
|
+
## Schema 7
|
|
8
|
+
|
|
9
|
+
Schema 7 adds `result.semanticEvidence` to named counts and audits. Its
|
|
10
|
+
`status` is `usable-as-requested` only when collection is complete and exactly
|
|
11
|
+
one definition is selected. Otherwise `follow-up-required` includes every
|
|
12
|
+
applicable collection or definition-selection reason under `followUpReasons`.
|
|
13
|
+
|
|
14
|
+
Presentation mode, page size, and subset presentation never affect this
|
|
15
|
+
status. It summarizes whether the requested semantic result needs another
|
|
16
|
+
semantic step; it does not establish code correctness or runtime behavior.
|
|
17
|
+
|
|
7
18
|
## Schema 6
|
|
8
19
|
|
|
9
20
|
Schema 6 replaces representational duplication with a compact response while
|
package/lib/doctor.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import {mkdtemp, mkdir, realpath,
|
|
2
|
+
import {mkdtemp, mkdir, realpath, writeFile} from "node:fs/promises";
|
|
3
3
|
import {tmpdir} from "node:os";
|
|
4
4
|
import {Client} from "@modelcontextprotocol/sdk/client/index.js";
|
|
5
5
|
import {StdioClientTransport} from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
TOOL_ORDER,
|
|
25
25
|
} from "../protocol.mjs";
|
|
26
26
|
import {PACKAGE_ROOT, inspectExternalCommand, inspectNodeRuntime, inspectRuntimeComponents} from "./runtime.mjs";
|
|
27
|
+
import {removeTemporaryDirectory} from "./temporary-directory.mjs";
|
|
27
28
|
|
|
28
29
|
const STATUS_EXIT_CODE = Object.freeze({
|
|
29
30
|
[CI_STATUS.PASS]: CI_EXIT_CODE.PASS,
|
|
@@ -312,7 +313,7 @@ export async function runDoctor({packageRoot = PACKAGE_ROOT} = {}) {
|
|
|
312
313
|
}
|
|
313
314
|
} finally {
|
|
314
315
|
await client.close().catch(() => undefined);
|
|
315
|
-
await
|
|
316
|
+
await removeTemporaryDirectory(fixture.workspace);
|
|
316
317
|
}
|
|
317
318
|
|
|
318
319
|
return doctorResult(packageRoot, checks, runtime);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import {OPERATING_SYSTEM} from "../protocol.mjs";
|
|
3
|
+
|
|
4
|
+
export function fileIdentity(file, operatingSystem = process.platform) {
|
|
5
|
+
const pathImplementation = operatingSystem === OPERATING_SYSTEM.WINDOWS ? path.win32 : path;
|
|
6
|
+
const resolved = pathImplementation.resolve(file);
|
|
7
|
+
return operatingSystem === OPERATING_SYSTEM.WINDOWS ? resolved.toLowerCase() : resolved;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function locationKeyAt(file, line, column, operatingSystem = process.platform) {
|
|
11
|
+
return `${fileIdentity(file, operatingSystem)}:${line}:${column}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function locationKeyForOperatingSystem(operatingSystem) {
|
|
15
|
+
return (location) => locationKeyAt(location.file, location.range.start.line, location.range.start.column, operatingSystem);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const locationKey = locationKeyForOperatingSystem(process.platform);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export class PendingRequestRegistry {
|
|
2
|
+
constructor({timeoutMilliseconds, timeoutMessage}) {
|
|
3
|
+
this.timeoutMilliseconds = timeoutMilliseconds;
|
|
4
|
+
this.timeoutMessage = timeoutMessage;
|
|
5
|
+
this.entries = new Map();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
get size() {
|
|
9
|
+
return this.entries.size;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
create(key, operation) {
|
|
13
|
+
if (this.entries.has(key)) throw new Error(`Pending request already exists: ${key}`);
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const timer = setTimeout(() => {
|
|
16
|
+
this.entries.delete(key);
|
|
17
|
+
reject(new Error(this.timeoutMessage(operation)));
|
|
18
|
+
}, this.timeoutMilliseconds);
|
|
19
|
+
this.entries.set(key, {resolve, reject, timer});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
take(key) {
|
|
24
|
+
const entry = this.entries.get(key);
|
|
25
|
+
if (!entry) return undefined;
|
|
26
|
+
clearTimeout(entry.timer);
|
|
27
|
+
this.entries.delete(key);
|
|
28
|
+
return entry;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
rejectAll(error) {
|
|
32
|
+
for (const entry of this.entries.values()) {
|
|
33
|
+
clearTimeout(entry.timer);
|
|
34
|
+
entry.reject(error);
|
|
35
|
+
}
|
|
36
|
+
this.entries.clear();
|
|
37
|
+
}
|
|
38
|
+
}
|
package/lib/runtime.mjs
CHANGED
|
@@ -5,6 +5,8 @@ import path from "node:path";
|
|
|
5
5
|
import {fileURLToPath} from "node:url";
|
|
6
6
|
import {CONFIGURATION_FILE, NODE_EVENT, PROCESS_EXIT_CODE, REQUIRED_RUNTIME_COMPONENT, RUNTIME_REQUIREMENT} from "../protocol.mjs";
|
|
7
7
|
|
|
8
|
+
const STREAM_EVENT = Object.freeze({DATA: "data"});
|
|
9
|
+
|
|
8
10
|
export const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
9
11
|
|
|
10
12
|
export function inspectNodeRuntime() {
|
|
@@ -50,10 +52,10 @@ export function inspectExternalCommand(command, args) {
|
|
|
50
52
|
const child = spawn(command, args, {stdio: ["ignore", "pipe", "pipe"]});
|
|
51
53
|
let stdout = "";
|
|
52
54
|
let stderr = "";
|
|
53
|
-
child.stdout.on(
|
|
55
|
+
child.stdout.on(STREAM_EVENT.DATA, (chunk) => {
|
|
54
56
|
stdout += chunk;
|
|
55
57
|
});
|
|
56
|
-
child.stderr.on(
|
|
58
|
+
child.stderr.on(STREAM_EVENT.DATA, (chunk) => {
|
|
57
59
|
stderr += chunk;
|
|
58
60
|
});
|
|
59
61
|
child.on(NODE_EVENT.ERROR, (error) => resolve({command, available: false, error: error.message}));
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
COLLECTION_STATUS,
|
|
3
|
+
DEFINITION_SELECTION_STATUS,
|
|
4
|
+
SEMANTIC_EVIDENCE_FOLLOW_UP_REASON,
|
|
5
|
+
SEMANTIC_EVIDENCE_STATUS,
|
|
6
|
+
TOOL,
|
|
7
|
+
} from "../protocol.mjs";
|
|
8
|
+
|
|
9
|
+
const NAMED_SYMBOL_TOOLS = new Set([TOOL.COUNT_NAMED_SYMBOL, TOOL.AUDIT_NAMED_SYMBOL]);
|
|
10
|
+
const DEFINITION_SELECTION_STATUSES = new Set(Object.values(DEFINITION_SELECTION_STATUS));
|
|
11
|
+
|
|
12
|
+
export function isNamedSymbolTool(tool) {
|
|
13
|
+
return NAMED_SYMBOL_TOOLS.has(tool);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function namedSemanticEvidence(selectionStatus, collectionStatus) {
|
|
17
|
+
const followUpReasons = [];
|
|
18
|
+
if (collectionStatus === COLLECTION_STATUS.LIMITED) {
|
|
19
|
+
followUpReasons.push(SEMANTIC_EVIDENCE_FOLLOW_UP_REASON.COLLECTION_LIMITED);
|
|
20
|
+
}
|
|
21
|
+
if (collectionStatus === COLLECTION_STATUS.PARTIAL) {
|
|
22
|
+
followUpReasons.push(SEMANTIC_EVIDENCE_FOLLOW_UP_REASON.COLLECTION_PARTIAL);
|
|
23
|
+
}
|
|
24
|
+
if (selectionStatus === DEFINITION_SELECTION_STATUS.NONE) {
|
|
25
|
+
followUpReasons.push(SEMANTIC_EVIDENCE_FOLLOW_UP_REASON.NO_DEFINITION_SELECTED);
|
|
26
|
+
}
|
|
27
|
+
if (selectionStatus === DEFINITION_SELECTION_STATUS.MULTIPLE) {
|
|
28
|
+
followUpReasons.push(SEMANTIC_EVIDENCE_FOLLOW_UP_REASON.MULTIPLE_DEFINITIONS_SELECTED);
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
status: followUpReasons.length === 0 ? SEMANTIC_EVIDENCE_STATUS.USABLE : SEMANTIC_EVIDENCE_STATUS.FOLLOW_UP_REQUIRED,
|
|
32
|
+
followUpReasons,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function namedSemanticEvidenceMatches(result, collectionStatus) {
|
|
37
|
+
const actual = result?.semanticEvidence;
|
|
38
|
+
if (!actual || !Array.isArray(actual.followUpReasons)) return false;
|
|
39
|
+
if (!DEFINITION_SELECTION_STATUSES.has(result.definitionSelectionStatus)) return false;
|
|
40
|
+
const expected = namedSemanticEvidence(result.definitionSelectionStatus, collectionStatus);
|
|
41
|
+
if (actual.status !== expected.status) return false;
|
|
42
|
+
if (actual.followUpReasons.length !== expected.followUpReasons.length) return false;
|
|
43
|
+
return actual.followUpReasons.every((reason, index) => reason === expected.followUpReasons[index]);
|
|
44
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export async function collectStableSnapshot({attempts, collect, inventory, sameInventory, fingerprint}) {
|
|
2
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
3
|
+
const inventoryBefore = await inventory();
|
|
4
|
+
const value = await collect();
|
|
5
|
+
const [fingerprints, inventoryAfter] = await Promise.all([fingerprint(value), inventory()]);
|
|
6
|
+
if (!sameInventory(inventoryBefore, inventoryAfter)) continue;
|
|
7
|
+
return {value, fingerprints, inventory: inventoryAfter, attempt};
|
|
8
|
+
}
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {rm} from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
const CLEANUP = Object.freeze({
|
|
4
|
+
MAXIMUM_RETRIES: 10,
|
|
5
|
+
RETRY_DELAY_MILLISECONDS: 100,
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export async function removeTemporaryDirectory(directory) {
|
|
9
|
+
await rm(directory, {
|
|
10
|
+
recursive: true,
|
|
11
|
+
force: true,
|
|
12
|
+
maxRetries: CLEANUP.MAXIMUM_RETRIES,
|
|
13
|
+
retryDelay: CLEANUP.RETRY_DELAY_MILLISECONDS,
|
|
14
|
+
});
|
|
15
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "semantic-js-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "Semantic understanding of JavaScript codebases for AI agents",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -44,13 +44,18 @@
|
|
|
44
44
|
"node": ">=22"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
|
-
"check": "npm run format:check && node --check protocol.mjs && node --check server.mjs && node --check cli.mjs && node --check lib/runtime.mjs && node --check lib/doctor.mjs && node --check scripts/generate-protocol-reference.mjs && node --check scripts/check-protocol-literals.mjs && node --check scripts/check-runtime.mjs && node --check scripts/semantic-js-mcp-ci.mjs && node --check scripts/ci-smoke.mjs && node --check scripts/smoke.mjs && node --check scripts/vue-smoke.mjs && node --check scripts/benchmark.mjs && node --check scripts/distribution-policy.mjs && node --check scripts/distribution-smoke.mjs && node --check scripts/release-contract.mjs && node --check scripts/release-verify.mjs && node --check scripts/release-smoke.mjs && node --check scripts/postpublication-smoke.mjs && node --check scripts/codex-plugin-verification.mjs && node --check scripts/agent-evaluation-contract.mjs && node --check scripts/agent-evaluation.mjs && node --check scripts/agent-evaluation-smoke.mjs && node --check scripts/repository-matrix-contract.mjs && node --check scripts/repository-matrix.mjs && node --check scripts/repository-matrix-smoke.mjs && node scripts/generate-protocol-reference.mjs --check && node scripts/check-protocol-literals.mjs",
|
|
47
|
+
"check": "npm run format:check && node --check protocol.mjs && node --check server.mjs && node --check cli.mjs && node --check lib/runtime.mjs && node --check lib/doctor.mjs && node --check lib/semantic-evidence.mjs && node --check lib/pending-requests.mjs && node --check lib/stable-collection.mjs && node --check scripts/generate-protocol-reference.mjs && node --check scripts/check-protocol-literals.mjs && node --check scripts/check-runtime.mjs && node --check scripts/documentation-contract.mjs && node --check scripts/documentation-gate.mjs && node --check scripts/documentation-gate-smoke.mjs && node --check scripts/semantic-js-mcp-ci.mjs && node --check scripts/ci-smoke.mjs && node --check scripts/publish-workflow-smoke.mjs && node --check scripts/negative-verification-smoke.mjs && node --check scripts/doctor-smoke.mjs && node --check scripts/smoke.mjs && node --check scripts/vue-smoke.mjs && node --check scripts/lifecycle-smoke.mjs && node --check scripts/lifecycle-memory-contract.mjs && node --check scripts/lifecycle-memory-observer.mjs && node --check scripts/lifecycle-memory-benchmark.mjs && node --check scripts/benchmark.mjs && node --check scripts/distribution-policy.mjs && node --check scripts/distribution-smoke.mjs && node --check scripts/release-contract.mjs && node --check scripts/release-verify.mjs && node --check scripts/release-smoke.mjs && node --check scripts/postpublication-smoke.mjs && node --check scripts/codex-plugin-verification.mjs && node --check scripts/agent-evaluation-contract.mjs && node --check scripts/agent-evaluation.mjs && node --check scripts/agent-evaluation-smoke.mjs && node --check scripts/repository-matrix-contract.mjs && node --check scripts/repository-matrix.mjs && node --check scripts/repository-matrix-smoke.mjs && node scripts/generate-protocol-reference.mjs --check && node scripts/check-protocol-literals.mjs && node scripts/documentation-gate-smoke.mjs",
|
|
48
48
|
"check:runtime": "node scripts/check-runtime.mjs",
|
|
49
|
+
"check:documentation": "node scripts/documentation-gate.mjs",
|
|
49
50
|
"doctor": "node cli.mjs doctor",
|
|
50
51
|
"ci:evaluate": "node scripts/semantic-js-mcp-ci.mjs",
|
|
51
52
|
"smoke": "node scripts/smoke.mjs",
|
|
52
53
|
"smoke:ci": "node scripts/ci-smoke.mjs",
|
|
54
|
+
"smoke:publish": "node scripts/publish-workflow-smoke.mjs",
|
|
55
|
+
"smoke:negative": "node scripts/negative-verification-smoke.mjs",
|
|
56
|
+
"smoke:doctor": "node scripts/doctor-smoke.mjs",
|
|
53
57
|
"smoke:vue": "node scripts/vue-smoke.mjs",
|
|
58
|
+
"smoke:lifecycle": "node scripts/lifecycle-smoke.mjs",
|
|
54
59
|
"smoke:distribution": "node scripts/distribution-smoke.mjs",
|
|
55
60
|
"smoke:release": "node scripts/release-smoke.mjs",
|
|
56
61
|
"smoke:evaluation": "node scripts/agent-evaluation-smoke.mjs",
|
|
@@ -60,8 +65,9 @@
|
|
|
60
65
|
"release:verify": "node scripts/release-verify.mjs",
|
|
61
66
|
"verify:published": "node scripts/postpublication-smoke.mjs",
|
|
62
67
|
"benchmark": "node scripts/benchmark.mjs",
|
|
63
|
-
"
|
|
64
|
-
"format
|
|
68
|
+
"benchmark:lifecycle-memory": "node scripts/lifecycle-memory-benchmark.mjs",
|
|
69
|
+
"format": "prettier --write \".codex-plugin/**/*.json\" \".agents/plugins/marketplace.json\" \".github/**/*.yml\" \".mcp.json\" \".prettierrc.json\" \"package.json\" \"package-lock.json\" \"CHANGELOG.md\" \"CONTRIBUTING.md\" \"README.md\" \"ROADMAP.md\" \"SECURITY.md\" \"*.mjs\" \"docs/**/*.md\" \"lib/**/*.mjs\" \"scripts/**/*.mjs\" \"skills/**/SKILL.md\" \"skills/**/agents/*.yaml\"",
|
|
70
|
+
"format:check": "prettier --check \".codex-plugin/**/*.json\" \".agents/plugins/marketplace.json\" \".github/**/*.yml\" \".mcp.json\" \".prettierrc.json\" \"package.json\" \"package-lock.json\" \"CHANGELOG.md\" \"CONTRIBUTING.md\" \"README.md\" \"ROADMAP.md\" \"SECURITY.md\" \"*.mjs\" \"docs/**/*.md\" \"lib/**/*.mjs\" \"scripts/**/*.mjs\" \"skills/**/SKILL.md\" \"skills/**/agents/*.yaml\""
|
|
65
71
|
},
|
|
66
72
|
"dependencies": {
|
|
67
73
|
"@modelcontextprotocol/sdk": "1.27.1",
|
package/protocol.mjs
CHANGED
|
@@ -207,9 +207,9 @@ export const TOOL_DESCRIPTION = Object.freeze({
|
|
|
207
207
|
[TOOL.DIAGNOSTICS]:
|
|
208
208
|
"Returns diagnostics for one file and marks evidence untrusted when the current document snapshot cannot be confirmed.",
|
|
209
209
|
[TOOL.COUNT_TEXT_MATCHES]: `Counts exact identifier text matches without semantic verification. Use ${TOOL.COUNT_NAMED_SYMBOL} or ${TOOL.AUDIT_NAMED_SYMBOL} to verify identity.`,
|
|
210
|
-
[TOOL.COUNT_NAMED_SYMBOL]: `Returns exact-definition and verified-reference counts for a symbol name. Use ${TOOL.AUDIT_NAMED_SYMBOL} for identity, signature, or file-hint binding verification and ${TOOL.REFERENCE_PAGE} with a returned referenceSetId for locations.`,
|
|
210
|
+
[TOOL.COUNT_NAMED_SYMBOL]: `Returns exact-definition and verified-reference counts plus explicit semantic follow-up status for a symbol name. Use ${TOOL.AUDIT_NAMED_SYMBOL} for identity, signature, or file-hint binding verification and ${TOOL.REFERENCE_PAGE} with a returned referenceSetId for locations.`,
|
|
211
211
|
[TOOL.COUNT_REFERENCES]: `Returns verified-reference counts for the symbol at one source position. Use ${TOOL.AUDIT_SYMBOL} for identity and signature or ${TOOL.REFERENCE_PAGE} with the returned referenceSetId for locations.`,
|
|
212
|
-
[TOOL.AUDIT_NAMED_SYMBOL]: `Returns a compact exact-name audit and verifies source bindings to fileHint when no declaration is selected. Use ${TOOL.REFERENCE_PAGE} with a returned referenceSetId for locations.`,
|
|
212
|
+
[TOOL.AUDIT_NAMED_SYMBOL]: `Returns a compact exact-name audit with explicit semantic follow-up status and verifies source bindings to fileHint when no declaration is selected. Use ${TOOL.REFERENCE_PAGE} with a returned referenceSetId for locations.`,
|
|
213
213
|
[TOOL.AUDIT_SYMBOL]: `Returns a compact definition, signature, coverage, and freshness summary for one source position. Use ${TOOL.REFERENCE_PAGE} with the returned referenceSetId for locations.`,
|
|
214
214
|
[TOOL.REFERENCES]: `Returns the first page of verified source locations and detailed collection evidence. Use ${TOOL.REFERENCE_PAGE} for later pages.`,
|
|
215
215
|
[TOOL.REFERENCE_PAGE]: `Returns a page from a freshness-checked reference set created by a count, audit, or ${TOOL.REFERENCES} call.`,
|
|
@@ -219,10 +219,19 @@ export const TOOL_DESCRIPTION = Object.freeze({
|
|
|
219
219
|
|
|
220
220
|
export const LSP_METHOD = Object.freeze({
|
|
221
221
|
DOCUMENT_DIAGNOSTIC: "textDocument/diagnostic",
|
|
222
|
+
EXECUTE_COMMAND: "workspace/executeCommand",
|
|
222
223
|
PUBLISH_DIAGNOSTICS: "textDocument/publishDiagnostics",
|
|
223
224
|
WORKSPACE_DIAGNOSTIC_REFRESH: "workspace/diagnostic/refresh",
|
|
224
225
|
});
|
|
225
226
|
|
|
227
|
+
export const LSP_COMMAND = Object.freeze({
|
|
228
|
+
GO_TO_SOURCE_DEFINITION: "_typescript.goToSourceDefinition",
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
export const TYPESCRIPT_SERVER_COMMAND = Object.freeze({
|
|
232
|
+
DEFINITION_AND_BOUND_SPAN: "definitionAndBoundSpan",
|
|
233
|
+
});
|
|
234
|
+
|
|
226
235
|
export const LIMIT_MODE = Object.freeze({
|
|
227
236
|
UNLIMITED: "unlimited",
|
|
228
237
|
MAXIMUM: "maximum",
|
|
@@ -244,6 +253,18 @@ export const DEFINITION_SELECTION_STATUS = Object.freeze({
|
|
|
244
253
|
MULTIPLE: "multiple-definitions-selected",
|
|
245
254
|
});
|
|
246
255
|
|
|
256
|
+
export const SEMANTIC_EVIDENCE_STATUS = Object.freeze({
|
|
257
|
+
USABLE: "usable-as-requested",
|
|
258
|
+
FOLLOW_UP_REQUIRED: "follow-up-required",
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
export const SEMANTIC_EVIDENCE_FOLLOW_UP_REASON = Object.freeze({
|
|
262
|
+
COLLECTION_LIMITED: "collection-is-limited",
|
|
263
|
+
COLLECTION_PARTIAL: "collection-is-partial",
|
|
264
|
+
NO_DEFINITION_SELECTED: DEFINITION_SELECTION_STATUS.NONE,
|
|
265
|
+
MULTIPLE_DEFINITIONS_SELECTED: DEFINITION_SELECTION_STATUS.MULTIPLE,
|
|
266
|
+
});
|
|
267
|
+
|
|
247
268
|
export const SIGNATURE_SOURCE = Object.freeze({
|
|
248
269
|
QUERY_POSITION_HOVER: "query-position-hover",
|
|
249
270
|
RESOLVED_DEFINITION_HOVER: "resolved-definition-hover",
|
|
@@ -369,6 +390,7 @@ export const CI_REASON = Object.freeze({
|
|
|
369
390
|
VERIFIED_DIAGNOSTIC_ERRORS: "verified-current-document-has-error-diagnostics",
|
|
370
391
|
INCOMPLETE_EVIDENCE: "collection-is-limited-or-partial",
|
|
371
392
|
UNTRUSTED_DIAGNOSTICS: "diagnostics-for-current-document-are-untrusted",
|
|
393
|
+
SEMANTIC_FOLLOW_UP_REQUIRED: "semantic-evidence-requires-follow-up",
|
|
372
394
|
TOOL_EXECUTION_FAILED: "tool-execution-failed",
|
|
373
395
|
INVALID_INPUT: "input-is-not-a-semantic-js-mcp-result",
|
|
374
396
|
});
|
|
@@ -383,10 +405,10 @@ export const INTERNAL_RESOLUTION_SOURCE = Object.freeze({
|
|
|
383
405
|
|
|
384
406
|
export const RESULT_SCHEMA = Object.freeze({
|
|
385
407
|
NAME: PRODUCT.NAME,
|
|
386
|
-
VERSION:
|
|
408
|
+
VERSION: 7,
|
|
387
409
|
});
|
|
388
410
|
|
|
389
|
-
export const SERVER_VERSION = "0.
|
|
411
|
+
export const SERVER_VERSION = "0.10.1";
|
|
390
412
|
|
|
391
413
|
export const REQUIRED_RUNTIME_COMPONENT = Object.freeze({
|
|
392
414
|
TYPESCRIPT_LANGUAGE_SERVER: "typescript-language-server/lib/cli.mjs",
|
|
@@ -411,6 +433,12 @@ export const NODE_EVENT = Object.freeze({
|
|
|
411
433
|
CLOSE: "close",
|
|
412
434
|
});
|
|
413
435
|
|
|
436
|
+
export const OPERATING_SYSTEM = Object.freeze({
|
|
437
|
+
LINUX: "linux",
|
|
438
|
+
MACOS: "darwin",
|
|
439
|
+
WINDOWS: "win32",
|
|
440
|
+
});
|
|
441
|
+
|
|
414
442
|
export const DEFAULT = Object.freeze({
|
|
415
443
|
REQUEST_TIMEOUT_MS: 30_000,
|
|
416
444
|
DIAGNOSTIC_WAIT_MS: 2_000,
|