semantic-js-mcp 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/.codex-plugin/plugin.json +1 -1
  2. package/.prettierrc.json +1 -0
  3. package/CHANGELOG.md +18 -0
  4. package/CONTRIBUTING.md +6 -2
  5. package/README.md +13 -3
  6. package/ROADMAP.md +1 -1
  7. package/docs/distribution.md +8 -0
  8. package/docs/getting-started.md +72 -0
  9. package/docs/result-schema-migration.md +11 -0
  10. package/lib/doctor.mjs +3 -2
  11. package/lib/file-identity.mjs +18 -0
  12. package/lib/pending-requests.mjs +38 -0
  13. package/lib/runtime.mjs +4 -2
  14. package/lib/semantic-evidence.mjs +44 -0
  15. package/lib/stable-collection.mjs +10 -0
  16. package/lib/temporary-directory.mjs +15 -0
  17. package/package.json +9 -4
  18. package/protocol.mjs +32 -4
  19. package/scripts/agent-evaluation-contract.mjs +24 -1
  20. package/scripts/check-protocol-literals.mjs +10 -0
  21. package/scripts/ci-smoke.mjs +58 -1
  22. package/scripts/distribution-policy.mjs +0 -7
  23. package/scripts/distribution-smoke.mjs +17 -13
  24. package/scripts/doctor-smoke.mjs +40 -0
  25. package/scripts/documentation-contract.mjs +39 -0
  26. package/scripts/documentation-gate-smoke.mjs +55 -0
  27. package/scripts/documentation-gate.mjs +110 -0
  28. package/scripts/generate-protocol-reference.mjs +10 -0
  29. package/scripts/lifecycle-memory-benchmark.mjs +300 -0
  30. package/scripts/lifecycle-memory-contract.mjs +53 -0
  31. package/scripts/lifecycle-memory-observer.mjs +29 -0
  32. package/scripts/lifecycle-smoke.mjs +309 -0
  33. package/scripts/negative-verification-smoke.mjs +243 -0
  34. package/scripts/postpublication-smoke.mjs +14 -15
  35. package/scripts/release-contract.mjs +8 -0
  36. package/scripts/semantic-js-mcp-ci.mjs +35 -19
  37. package/scripts/smoke.mjs +87 -14
  38. package/scripts/vue-smoke.mjs +32 -2
  39. package/server.mjs +306 -124
  40. package/skills/semantic-navigation/SKILL.md +2 -0
  41. package/skills/semantic-navigation/references/protocol-literals.md +32 -4
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "semantic-js-mcp",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Semantic understanding of JavaScript codebases for AI agents",
5
5
  "author": {
6
6
  "name": "Jonathan Muñoz Lucas"
package/.prettierrc.json CHANGED
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "bracketSpacing": false,
3
+ "endOfLine": "lf",
3
4
  "printWidth": 140,
4
5
  "proseWrap": "preserve",
5
6
  "semi": true,
package/CHANGELOG.md CHANGED
@@ -4,6 +4,24 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.10.0] - 2026-07-17
8
+
9
+ ### Added
10
+
11
+ - Add a deterministic semantic-evidence usability status to named counts and audits, with explicit follow-up reasons for incomplete collection or ambiguous definition selection.
12
+ - 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.
13
+ - Add cross-platform CI coverage for supported Node.js LTS lines.
14
+ - Add focused negative-verification and public-documentation gates.
15
+ - Add concise starter prompts and evidence examples for common agent workflows.
16
+
17
+ ### Fixed
18
+
19
+ - 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.
20
+ - Resolve cross-project import aliases consistently when a language server first returns the local import binding.
21
+ - Canonicalize Windows source positions when verifying and grouping cross-project references.
22
+ - Make release verification portable across Windows paths, executable shims, temporary-file locking, and checkout line endings.
23
+ - Keep declaration filtering and language-server client eviction stable across platform-specific provider output and slow initialization.
24
+
7
25
  ## [0.9.0] - 2026-07-16
8
26
 
9
27
  ### 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.9.0` 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.
19
+ Version `0.10.0` 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
 
@@ -42,9 +42,11 @@ semantic-js-mcp doctor
42
42
 
43
43
  Configure the host to run `semantic-js-mcp serve`.
44
44
 
45
+ For practical investigation prompts and compact evidence examples, see [Getting started](docs/getting-started.md).
46
+
45
47
  ## Runtime
46
48
 
47
- - Node.js 22 or newer is supported.
49
+ - Node.js 22 or newer is required.
48
50
  - Node.js 24 LTS is recommended for new installations. See the [Node.js release schedule](https://nodejs.org/en/about/previous-releases).
49
51
  - `rg` (ripgrep) must be available on `PATH` for repository-wide discovery.
50
52
  - The nearest workspace TypeScript SDK is used when available; the bundled TypeScript SDK is the fallback.
@@ -66,9 +68,10 @@ npm run doctor
66
68
  npm run smoke:ci
67
69
  npm run smoke
68
70
  npm run smoke:vue
71
+ npm run smoke:lifecycle
69
72
  ```
70
73
 
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.
74
+ 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
75
 
73
76
  For an MCP host that accepts a direct stdio configuration, point it at the checked-out server using an absolute path:
74
77
 
@@ -152,6 +155,8 @@ Reference pages group the locations returned in that page under `referenceGroups
152
155
 
153
156
  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
157
 
158
+ 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.
159
+
155
160
  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
161
 
157
162
  ### Limits
@@ -207,6 +212,7 @@ npm run doctor
207
212
  npm run smoke
208
213
  npm run smoke:ci
209
214
  npm run smoke:vue
215
+ npm run smoke:lifecycle
210
216
  npm run smoke:distribution
211
217
  ```
212
218
 
@@ -216,6 +222,10 @@ npm run smoke:distribution
216
222
 
217
223
  The smoke tests create temporary generic TypeScript and Vue projects. They do not depend on a specific application repository.
218
224
 
225
+ ## Reporting Problems
226
+
227
+ 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.
228
+
219
229
  ## Current Limitations
220
230
 
221
231
  - 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
- - Expand reproducible package and plugin release checks across supported Node.js LTS releases and hosted CI.
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
 
@@ -45,9 +45,13 @@ Run the complete local validation sequence before reviewing a distribution chang
45
45
  ```bash
46
46
  npm run check
47
47
  npm run check:runtime
48
+ npm run check:documentation
48
49
  npm run smoke:ci
50
+ npm run smoke:negative
51
+ npm run smoke:doctor
49
52
  npm run smoke
50
53
  npm run smoke:vue
54
+ npm run smoke:lifecycle
51
55
  npm run smoke:distribution
52
56
  ```
53
57
 
@@ -63,3 +67,7 @@ npm run verify:published -- <version>
63
67
  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
68
 
65
69
  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`.
70
+
71
+ ## Continuous Integration
72
+
73
+ 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, rm, writeFile} from "node:fs/promises";
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 rm(fixture.workspace, {recursive: true, force: true});
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("data", (chunk) => {
55
+ child.stdout.on(STREAM_EVENT.DATA, (chunk) => {
54
56
  stdout += chunk;
55
57
  });
56
- child.stderr.on("data", (chunk) => {
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.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Semantic understanding of JavaScript codebases for AI agents",
5
5
  "keywords": [
6
6
  "mcp",
@@ -44,13 +44,17 @@
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/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:negative": "node scripts/negative-verification-smoke.mjs",
55
+ "smoke:doctor": "node scripts/doctor-smoke.mjs",
53
56
  "smoke:vue": "node scripts/vue-smoke.mjs",
57
+ "smoke:lifecycle": "node scripts/lifecycle-smoke.mjs",
54
58
  "smoke:distribution": "node scripts/distribution-smoke.mjs",
55
59
  "smoke:release": "node scripts/release-smoke.mjs",
56
60
  "smoke:evaluation": "node scripts/agent-evaluation-smoke.mjs",
@@ -60,8 +64,9 @@
60
64
  "release:verify": "node scripts/release-verify.mjs",
61
65
  "verify:published": "node scripts/postpublication-smoke.mjs",
62
66
  "benchmark": "node scripts/benchmark.mjs",
63
- "format": "prettier --write \".codex-plugin/**/*.json\" \".agents/plugins/marketplace.json\" \".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\"",
64
- "format:check": "prettier --check \".codex-plugin/**/*.json\" \".agents/plugins/marketplace.json\" \".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\""
67
+ "benchmark:lifecycle-memory": "node scripts/lifecycle-memory-benchmark.mjs",
68
+ "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\"",
69
+ "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
70
  },
66
71
  "dependencies": {
67
72
  "@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: 6,
408
+ VERSION: 7,
387
409
  });
388
410
 
389
- export const SERVER_VERSION = "0.9.0";
411
+ export const SERVER_VERSION = "0.10.0";
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,
@@ -1,4 +1,13 @@
1
- import {ACCOUNTING_STATUS, CI_EXIT_CODE, CI_STATUS, COLLECTION_STATUS, DEFINITION_SELECTION_STATUS, TOOL} from "../protocol.mjs";
1
+ import {
2
+ ACCOUNTING_STATUS,
3
+ CI_EXIT_CODE,
4
+ CI_STATUS,
5
+ COLLECTION_STATUS,
6
+ DEFINITION_SELECTION_STATUS,
7
+ SEMANTIC_EVIDENCE_FOLLOW_UP_REASON,
8
+ SEMANTIC_EVIDENCE_STATUS,
9
+ TOOL,
10
+ } from "../protocol.mjs";
2
11
 
3
12
  export const AGENT_EVALUATION_ARGUMENT = Object.freeze({
4
13
  ANSWERS: "--answers",
@@ -25,6 +34,7 @@ export const AGENT_EVALUATION_CASES = Object.freeze([
25
34
  requestedSymbol: "calculateTotal",
26
35
  exactDefinitionsFound: 1,
27
36
  definitionSelectionStatus: DEFINITION_SELECTION_STATUS.ONE,
37
+ semanticEvidence: Object.freeze({status: SEMANTIC_EVIDENCE_STATUS.USABLE, followUpReasons: Object.freeze([])}),
28
38
  references: Object.freeze({verifiedTotal: 18}),
29
39
  unresolvedReferences: Object.freeze({count: 0}),
30
40
  }),
@@ -41,6 +51,11 @@ export const AGENT_EVALUATION_CASES = Object.freeze([
41
51
  result: Object.freeze({
42
52
  requestedSymbol: "RequestHandler",
43
53
  exactDefinitionsFound: 1,
54
+ definitionSelectionStatus: DEFINITION_SELECTION_STATUS.ONE,
55
+ semanticEvidence: Object.freeze({
56
+ status: SEMANTIC_EVIDENCE_STATUS.FOLLOW_UP_REQUIRED,
57
+ followUpReasons: Object.freeze([SEMANTIC_EVIDENCE_FOLLOW_UP_REASON.COLLECTION_PARTIAL]),
58
+ }),
44
59
  references: Object.freeze({verifiedTotal: 52}),
45
60
  unresolvedReferences: Object.freeze({count: 1}),
46
61
  }),
@@ -58,6 +73,10 @@ export const AGENT_EVALUATION_CASES = Object.freeze([
58
73
  requestedSymbol: "createClient",
59
74
  exactDefinitionsFound: 3,
60
75
  definitionSelectionStatus: DEFINITION_SELECTION_STATUS.MULTIPLE,
76
+ semanticEvidence: Object.freeze({
77
+ status: SEMANTIC_EVIDENCE_STATUS.FOLLOW_UP_REQUIRED,
78
+ followUpReasons: Object.freeze([SEMANTIC_EVIDENCE_FOLLOW_UP_REASON.MULTIPLE_DEFINITIONS_SELECTED]),
79
+ }),
61
80
  unresolvedReferences: Object.freeze({count: 0}),
62
81
  }),
63
82
  collection: Object.freeze({status: COLLECTION_STATUS.COMPLETE, stoppedByLimit: false}),
@@ -74,6 +93,10 @@ export const AGENT_EVALUATION_CASES = Object.freeze([
74
93
  requestedSymbol: "RenamedPanel",
75
94
  exactDefinitionsFound: 0,
76
95
  definitionSelectionStatus: DEFINITION_SELECTION_STATUS.NONE,
96
+ semanticEvidence: Object.freeze({
97
+ status: SEMANTIC_EVIDENCE_STATUS.FOLLOW_UP_REQUIRED,
98
+ followUpReasons: Object.freeze([SEMANTIC_EVIDENCE_FOLLOW_UP_REASON.NO_DEFINITION_SELECTED]),
99
+ }),
77
100
  fileHintResolution: Object.freeze({
78
101
  textMatchesFound: 2,
79
102
  textMatchesChecked: 2,