semantic-js-mcp 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/protocol.mjs ADDED
@@ -0,0 +1,356 @@
1
+ export const TOOL = Object.freeze({
2
+ DOCUMENT_SYMBOLS: "lsp_document_symbols",
3
+ WORKSPACE_SYMBOLS: "lsp_workspace_symbols",
4
+ DEFINITION: "lsp_definition",
5
+ HOVER: "lsp_hover",
6
+ DIAGNOSTICS: "lsp_diagnostics",
7
+ COUNT_TEXT_MATCHES: "lsp_count_text_matches",
8
+ COUNT_NAMED_SYMBOL: "lsp_count_named_symbol",
9
+ COUNT_REFERENCES: "lsp_count_references",
10
+ AUDIT_NAMED_SYMBOL: "lsp_audit_named_symbol",
11
+ AUDIT_SYMBOL: "lsp_audit_symbol",
12
+ REFERENCES: "lsp_references",
13
+ REFERENCE_PAGE: "lsp_reference_page",
14
+ UNRESOLVED_REFERENCE_PAGE: "lsp_unresolved_reference_page",
15
+ });
16
+
17
+ export const TOOL_ORDER = Object.freeze(Object.values(TOOL));
18
+ export const PRODUCT = Object.freeze({
19
+ NAME: "semantic-js-mcp",
20
+ DISPLAY_NAME: "Semantic JS MCP",
21
+ });
22
+
23
+ export const CLI_COMMAND = Object.freeze({
24
+ SERVE: "serve",
25
+ DOCTOR: "doctor",
26
+ HELP: "help",
27
+ VERSION: "version",
28
+ });
29
+
30
+ export const CLI_ARGUMENT = Object.freeze({
31
+ HELP: "--help",
32
+ HELP_SHORT: "-h",
33
+ VERSION: "--version",
34
+ VERSION_SHORT: "-v",
35
+ YAML: "--yaml",
36
+ });
37
+
38
+ export const CLI_MESSAGE = Object.freeze({
39
+ UNKNOWN_COMMAND: "Unknown command",
40
+ UNKNOWN_ARGUMENT: "Unknown argument",
41
+ UNKNOWN_OPTION: "Unknown option",
42
+ });
43
+
44
+ export const DOCTOR_CHECK = Object.freeze({
45
+ NODE_RUNTIME: "node-runtime",
46
+ RUNTIME_COMPONENTS: "runtime-components",
47
+ RIPGREP: "ripgrep",
48
+ MCP_STARTUP: "mcp-startup",
49
+ TOOL_DISCOVERY: "tool-discovery",
50
+ TYPESCRIPT_SYMBOLS: "typescript-symbols",
51
+ TYPESCRIPT_REFERENCES: "typescript-references",
52
+ DIAGNOSTIC_FRESHNESS: "diagnostic-freshness",
53
+ VUE_SYMBOLS: "vue-symbols",
54
+ VUE_TEMPLATE_DEFINITION: "vue-template-definition",
55
+ });
56
+
57
+ export const DOCTOR_REASON = Object.freeze({
58
+ CHECK_COMPLETED: "check-completed",
59
+ UNSUPPORTED_NODE_RUNTIME: "unsupported-node-runtime",
60
+ RUNTIME_COMPONENT_MISSING: "runtime-component-missing",
61
+ RIPGREP_UNAVAILABLE: "ripgrep-unavailable",
62
+ MCP_STARTUP_FAILED: "mcp-startup-failed",
63
+ TOOL_SET_DIFFERENT: "tool-set-different-from-protocol",
64
+ TYPESCRIPT_SYMBOL_NOT_FOUND: "typescript-symbol-not-found",
65
+ TYPESCRIPT_REFERENCE_ACCOUNTING_INCOMPLETE: "typescript-reference-accounting-incomplete",
66
+ DIAGNOSTICS_NOT_CONFIRMED: "diagnostics-not-confirmed-for-current-document",
67
+ VUE_SYMBOL_NOT_FOUND: "vue-symbol-not-found",
68
+ VUE_TEMPLATE_DEFINITION_UNRESOLVED: "vue-template-definition-unresolved",
69
+ CHECK_FAILED: "check-failed",
70
+ });
71
+
72
+ export const RUNTIME_COMMAND = Object.freeze({
73
+ RIPGREP: "rg",
74
+ });
75
+
76
+ export const RUNTIME_REQUIREMENT = Object.freeze({
77
+ MINIMUM_NODE_MAJOR: 22,
78
+ });
79
+
80
+ export const PACKAGE_PATH = Object.freeze({
81
+ SERVER: "server.mjs",
82
+ CLI: "cli.mjs",
83
+ });
84
+
85
+ export const ENVIRONMENT_VARIABLE = Object.freeze({
86
+ PROCESS_CWD: "SEMANTIC_JS_MCP_PROCESS_CWD",
87
+ CLIENT_IDLE_TIMEOUT_MS: "SEMANTIC_JS_MCP_CLIENT_IDLE_TIMEOUT_MS",
88
+ CLIENT_MINIMUM_EVICTION_AGE_MS: "SEMANTIC_JS_MCP_CLIENT_MINIMUM_EVICTION_AGE_MS",
89
+ MAXIMUM_ACTIVE_CLIENTS: "SEMANTIC_JS_MCP_MAXIMUM_ACTIVE_CLIENTS",
90
+ REFERENCE_SET_TTL_MS: "SEMANTIC_JS_MCP_REFERENCE_SET_TTL_MS",
91
+ MAXIMUM_REFERENCE_SETS: "SEMANTIC_JS_MCP_MAXIMUM_REFERENCE_SETS",
92
+ MAXIMUM_CHANGED_REFERENCE_SET_MARKERS: "SEMANTIC_JS_MCP_MAXIMUM_CHANGED_REFERENCE_SET_MARKERS",
93
+ MAXIMUM_CACHED_REFERENCE_LOCATIONS: "SEMANTIC_JS_MCP_MAXIMUM_CACHED_REFERENCE_LOCATIONS",
94
+ DEFAULT_REFERENCE_PAGE_SIZE: "SEMANTIC_JS_MCP_DEFAULT_REFERENCE_PAGE_SIZE",
95
+ CROSS_WORKSPACE_CONCURRENCY: "SEMANTIC_JS_MCP_CROSS_WORKSPACE_CONCURRENCY",
96
+ BENCHMARK_COUNTS: "SEMANTIC_JS_MCP_BENCHMARK_COUNTS",
97
+ });
98
+
99
+ export const CONFIGURATION_FILE = Object.freeze({
100
+ PACKAGE: "package.json",
101
+ TYPESCRIPT: "tsconfig.json",
102
+ JAVASCRIPT: "jsconfig.json",
103
+ });
104
+ export const WORKSPACE_CONFIGURATION_FILE_NAMES = Object.freeze(Object.values(CONFIGURATION_FILE));
105
+ export const WORKSPACE_ROOT_MARKER_FILE_NAMES = Object.freeze([CONFIGURATION_FILE.PACKAGE, CONFIGURATION_FILE.TYPESCRIPT]);
106
+ export const SOURCE_EXTENSION = Object.freeze({
107
+ TYPESCRIPT: ".ts",
108
+ TYPESCRIPT_REACT: ".tsx",
109
+ TYPESCRIPT_MODULE: ".mts",
110
+ TYPESCRIPT_COMMONJS: ".cts",
111
+ JAVASCRIPT: ".js",
112
+ JAVASCRIPT_REACT: ".jsx",
113
+ JAVASCRIPT_MODULE: ".mjs",
114
+ JAVASCRIPT_COMMONJS: ".cjs",
115
+ VUE: ".vue",
116
+ });
117
+ export const SOURCE_FILE_GLOBS = Object.freeze(Object.values(SOURCE_EXTENSION).map((extension) => `*${extension}`));
118
+ export const SOURCE_EXCLUDED_GLOBS = Object.freeze(["!**/node_modules/**", "!**/dist/**", "!**/coverage/**"]);
119
+
120
+ export const LANGUAGE_ID = Object.freeze({
121
+ TYPESCRIPT: "typescript",
122
+ TYPESCRIPT_REACT: "typescriptreact",
123
+ JAVASCRIPT: "javascript",
124
+ JAVASCRIPT_REACT: "javascriptreact",
125
+ VUE: "vue",
126
+ });
127
+
128
+ export const VUE_SCRIPT_LANGUAGE = Object.freeze({
129
+ JAVASCRIPT: "js",
130
+ JAVASCRIPT_REACT: "jsx",
131
+ });
132
+
133
+ export const FORBIDDEN_PUBLIC_FIELD = Object.freeze([
134
+ "confidence",
135
+ "exhaustive",
136
+ "rejectedCandidateCount",
137
+ "rejectedCandidates",
138
+ "rejectedCandidatesByReason",
139
+ "semanticIdentity",
140
+ "truncated",
141
+ ]);
142
+
143
+ export const COLLECTION_STATUS = Object.freeze({
144
+ COMPLETE: "complete",
145
+ LIMITED: "limited",
146
+ PARTIAL: "partial",
147
+ FAILED: "failed",
148
+ });
149
+
150
+ export const PRESENTATION_MODE = Object.freeze({
151
+ ALL_ITEMS: "all-items",
152
+ SUBSET: "subset",
153
+ COUNT_ONLY: "count-only",
154
+ SUMMARY_BY_FILE: "summary-by-file",
155
+ PAGE: "page",
156
+ });
157
+
158
+ export const LIMIT_MODE = Object.freeze({
159
+ UNLIMITED: "unlimited",
160
+ MAXIMUM: "maximum",
161
+ });
162
+
163
+ export const ACCOUNTING_STATUS = Object.freeze({
164
+ COMPLETE: "complete",
165
+ INCOMPLETE: "incomplete",
166
+ });
167
+
168
+ export const DEFINITION_MATCH = Object.freeze({
169
+ RESOLVED: "resolved",
170
+ UNRESOLVED: "unresolved",
171
+ });
172
+
173
+ export const SIGNATURE_SOURCE = Object.freeze({
174
+ QUERY_POSITION_HOVER: "query-position-hover",
175
+ RESOLVED_DEFINITION_HOVER: "resolved-definition-hover",
176
+ NOT_REPORTED: "not-reported",
177
+ });
178
+
179
+ export const DIAGNOSTIC_FRESHNESS = Object.freeze({
180
+ CURRENT: "current-document-version",
181
+ VERSION_NOT_REPORTED: "version-not-reported-by-language-server",
182
+ DIFFERENT_VERSION: "different-document-version",
183
+ NOT_REPORTED_FOR_CURRENT_DOCUMENT: "not-reported-for-current-document",
184
+ });
185
+
186
+ export const DIAGNOSTIC_SEVERITY = Object.freeze({
187
+ ERROR: "error",
188
+ WARNING: "warning",
189
+ INFORMATION: "information",
190
+ HINT: "hint",
191
+ NOT_REPORTED: "not-reported",
192
+ });
193
+
194
+ export const EVIDENCE_STATUS = Object.freeze({
195
+ VERIFIED: "verified",
196
+ UNTRUSTED: "untrusted",
197
+ });
198
+
199
+ export const DIAGNOSTIC_EVIDENCE_REASON = Object.freeze({
200
+ CURRENT_DOCUMENT_VERSION_CONFIRMED: "current-document-version-confirmed",
201
+ LANGUAGE_SERVER_VERSION_NOT_REPORTED: "language-server-version-not-reported",
202
+ LANGUAGE_SERVER_REPORTED_DIFFERENT_VERSION: "language-server-reported-different-version",
203
+ LANGUAGE_SERVER_DID_NOT_REPORT_CURRENT_DOCUMENT: "language-server-did-not-report-current-document",
204
+ });
205
+
206
+ export const CONTENT_FRESHNESS = Object.freeze({
207
+ VERIFIED_CURRENT: "verified-current-file-content",
208
+ VERIFIED_REPOSITORY_SOURCE_INVENTORY: "verified-current-repository-source-inventory",
209
+ });
210
+
211
+ export const REFERENCE_SET_CHANGE_TYPE = Object.freeze({
212
+ EVIDENCE_FILE_CONTENT_CHANGED: "evidence-file-content-changed",
213
+ REPOSITORY_SOURCE_INVENTORY_CHANGED: "repository-source-inventory-changed",
214
+ });
215
+
216
+ export const ERROR_CODE = Object.freeze({
217
+ TOOL_EXECUTION_FAILED: "TOOL_EXECUTION_FAILED",
218
+ REFERENCE_SET_CONTENT_CHANGED: "REFERENCE_SET_CONTENT_CHANGED",
219
+ REFERENCE_SET_NOT_FOUND_OR_EXPIRED: "REFERENCE_SET_NOT_FOUND_OR_EXPIRED",
220
+ REPOSITORY_CHANGED_DURING_COLLECTION: "REPOSITORY_CHANGED_DURING_COLLECTION",
221
+ RUNTIME_DEPENDENCY_MISSING: "RUNTIME_DEPENDENCY_MISSING",
222
+ RUNTIME_REQUIREMENT_UNMET: "RUNTIME_REQUIREMENT_UNMET",
223
+ });
224
+
225
+ export const EVIDENCE_TYPE = Object.freeze({
226
+ EXACT_IDENTIFIER_TEXT_MATCH: "exact-identifier-text-match",
227
+ });
228
+
229
+ export const FINGERPRINT_ALGORITHM = Object.freeze({
230
+ SHA_256: "sha256",
231
+ });
232
+
233
+ export const REFERENCE_DISCOVERY_METHOD = Object.freeze({
234
+ OWNING_WORKSPACE_LANGUAGE_SERVER: "owning-workspace-language-server",
235
+ DEFINITION_MATCH_FROM_ANOTHER_WORKSPACE: "definition-match-from-another-workspace",
236
+ });
237
+
238
+ export const DEFINITION_RESOLUTION_METHOD = Object.freeze({
239
+ LANGUAGE_SERVER: "language-server-definition",
240
+ TYPESCRIPT_SERVER: "typescript-server-definition",
241
+ VUE_TEMPLATE_IMPORT_BINDING: "vue-template-import-binding-definition",
242
+ UNRESOLVED: "unresolved",
243
+ });
244
+
245
+ export const UNRESOLVED_REFERENCE_REASON = Object.freeze({
246
+ DEFINITION_TOOLS_RETURNED_NO_LOCATION: "definition-tools-returned-no-location",
247
+ TYPESCRIPT_SERVER_REQUEST_FAILED: "typescript-server-request-failed-after-language-server-returned-no-location",
248
+ CANDIDATE_ANALYSIS_FAILED: "candidate-analysis-failed",
249
+ CANDIDATE_OPENED_IN_INFERRED_TYPESCRIPT_PROJECT: "candidate-opened-in-inferred-typescript-project",
250
+ });
251
+
252
+ export const TYPESCRIPT_PROJECT_KIND = Object.freeze({
253
+ CONFIGURED: "configured-typescript-project",
254
+ INFERRED: "inferred-typescript-project",
255
+ UNKNOWN: "typescript-project-kind-not-reported",
256
+ });
257
+
258
+ export const CI_STATUS = Object.freeze({
259
+ PASS: "pass",
260
+ FAIL: "fail",
261
+ UNTRUSTED: "untrusted",
262
+ BLOCKED: "blocked",
263
+ });
264
+
265
+ export const CI_EXIT_CODE = Object.freeze({
266
+ PASS: 0,
267
+ FAIL: 1,
268
+ UNTRUSTED: 2,
269
+ BLOCKED: 3,
270
+ });
271
+
272
+ export const DOCTOR_DISTRIBUTION_ACCEPTED_STATUS = Object.freeze([CI_STATUS.PASS, CI_STATUS.UNTRUSTED]);
273
+
274
+ export const DOCTOR_STATUS_PRIORITY = Object.freeze({
275
+ [CI_STATUS.PASS]: 0,
276
+ [CI_STATUS.UNTRUSTED]: 1,
277
+ [CI_STATUS.FAIL]: 2,
278
+ [CI_STATUS.BLOCKED]: 3,
279
+ });
280
+
281
+ export const RUNTIME_REQUIREMENT_KIND = Object.freeze({
282
+ NODE: "node",
283
+ RUNTIME_COMPONENT: "runtime-component",
284
+ });
285
+
286
+ export const CI_REASON = Object.freeze({
287
+ COMPLETE_EVIDENCE: "complete-evidence",
288
+ VERIFIED_DIAGNOSTIC_ERRORS: "verified-current-document-has-error-diagnostics",
289
+ INCOMPLETE_EVIDENCE: "collection-is-limited-or-partial",
290
+ UNTRUSTED_DIAGNOSTICS: "diagnostics-for-current-document-are-untrusted",
291
+ TOOL_EXECUTION_FAILED: "tool-execution-failed",
292
+ INVALID_INPUT: "input-is-not-a-semantic-js-mcp-result",
293
+ });
294
+
295
+ export const INTERNAL_RESOLUTION_SOURCE = Object.freeze({
296
+ NATIVE_LSP: "native-lsp",
297
+ TYPESCRIPT_SERVER_FALLBACK: "tsserver-fallback",
298
+ CROSS_WORKSPACE_DEFINITION: "cross-workspace-definition",
299
+ VUE_TEMPLATE_IMPORT_BINDING: "vue-template-import-binding",
300
+ UNRESOLVED: "unresolved",
301
+ });
302
+
303
+ export const RESULT_SCHEMA = Object.freeze({
304
+ NAME: PRODUCT.NAME,
305
+ VERSION: 5,
306
+ });
307
+
308
+ export const SERVER_VERSION = "0.8.0";
309
+
310
+ export const REQUIRED_RUNTIME_COMPONENT = Object.freeze({
311
+ TYPESCRIPT_LANGUAGE_SERVER: "typescript-language-server/lib/cli.mjs",
312
+ VUE_LANGUAGE_SERVER: "@vue/language-server/bin/vue-language-server.js",
313
+ TYPESCRIPT_SERVER: "typescript/lib/tsserver.js",
314
+ VUE_SFC_COMPILER: "@vue/compiler-sfc/dist/compiler-sfc.cjs.js",
315
+ VUE_TYPESCRIPT_PLUGIN: "@vue/typescript-plugin/package.json",
316
+ });
317
+
318
+ export const RUNTIME_STATUS = Object.freeze({
319
+ READY: "ready",
320
+ BLOCKED: "blocked",
321
+ });
322
+
323
+ export const PROCESS_EXIT_CODE = Object.freeze({
324
+ SUCCESS: 0,
325
+ FAILURE: 1,
326
+ });
327
+
328
+ export const NODE_EVENT = Object.freeze({
329
+ ERROR: "error",
330
+ CLOSE: "close",
331
+ });
332
+
333
+ export const DEFAULT = Object.freeze({
334
+ REQUEST_TIMEOUT_MS: 30_000,
335
+ DIAGNOSTIC_WAIT_MS: 2_000,
336
+ CLIENT_IDLE_TIMEOUT_MS: 60_000,
337
+ CLIENT_MINIMUM_EVICTION_AGE_MS: 15_000,
338
+ MAXIMUM_ACTIVE_CLIENTS: 4,
339
+ REFERENCE_SET_TTL_MS: 300_000,
340
+ MAXIMUM_REFERENCE_SETS: 12,
341
+ MAXIMUM_CHANGED_REFERENCE_SET_MARKERS: 24,
342
+ MAXIMUM_CACHED_REFERENCE_LOCATIONS: 50_000,
343
+ REFERENCE_PAGE_SIZE: 100,
344
+ CROSS_WORKSPACE_CONCURRENCY: 6,
345
+ FILE_FINGERPRINT_CONCURRENCY: 8,
346
+ NAMED_DEFINITION_CONCURRENCY: 2,
347
+ WORKSPACE_FILE_CONCURRENCY: 5,
348
+ FILE_SUGGESTION_COUNT: 8,
349
+ MILLISECONDS_PER_SECOND: 1_000,
350
+ BENCHMARK_COUNTS: Object.freeze([10, 100, 1_000]),
351
+ MCP_STARTUP_TIMEOUT_SECONDS: 30,
352
+ MCP_TOOL_TIMEOUT_SECONDS: 300,
353
+ INVENTORY_STAT_CONCURRENCY: 32,
354
+ COLLECTION_STABILITY_ATTEMPTS: 2,
355
+ PROCESS_ARGUMENT_OFFSET: 2,
356
+ });
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+
3
+ import path from "node:path";
4
+ import {mkdtemp, mkdir, rm, writeFile} from "node:fs/promises";
5
+ import {tmpdir} from "node:os";
6
+ import {fileURLToPath} from "node:url";
7
+ import {Client} from "@modelcontextprotocol/sdk/client/index.js";
8
+ import {StdioClientTransport} from "@modelcontextprotocol/sdk/client/stdio.js";
9
+ import {DEFAULT, ENVIRONMENT_VARIABLE, PRODUCT, TOOL} from "../protocol.mjs";
10
+
11
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
12
+ const counts = (process.env[ENVIRONMENT_VARIABLE.BENCHMARK_COUNTS] || DEFAULT.BENCHMARK_COUNTS.join(","))
13
+ .split(",")
14
+ .map((value) => Number(value.trim()))
15
+ .filter((value) => Number.isInteger(value) && value > 0);
16
+
17
+ if (counts.length === 0) throw new Error(`${ENVIRONMENT_VARIABLE.BENCHMARK_COUNTS} must contain positive integers`);
18
+
19
+ async function timedCall(client, name, argumentsValue) {
20
+ const startedAt = performance.now();
21
+ const response = await client.callTool({name, arguments: argumentsValue});
22
+ if (response.isError) throw new Error(response.content?.[0]?.text || `${name} failed`);
23
+ return {milliseconds: Math.round(performance.now() - startedAt), data: response.structuredContent};
24
+ }
25
+
26
+ const results = [];
27
+ for (const count of counts) {
28
+ const workspace = await mkdtemp(path.join(tmpdir(), `${PRODUCT.NAME}-benchmark-${count}-`));
29
+ const src = path.join(workspace, "src");
30
+ await mkdir(src, {recursive: true});
31
+ await writeFile(path.join(workspace, "package.json"), JSON.stringify({private: true, type: "module"}));
32
+ await writeFile(
33
+ path.join(workspace, "tsconfig.json"),
34
+ JSON.stringify({
35
+ compilerOptions: {strict: true, target: "ES2022", module: "ESNext", moduleResolution: "Bundler"},
36
+ include: ["src/**/*.ts"],
37
+ }),
38
+ );
39
+ const symbol = `benchmarkTarget${count}`;
40
+ const target = path.join(src, "target.ts");
41
+ await writeFile(target, `export function ${symbol}(value: number): number { return value + 1; }\n`);
42
+ await writeFile(
43
+ path.join(src, "usage.ts"),
44
+ [
45
+ `import {${symbol}} from "./target.js";`,
46
+ "export const values = [",
47
+ ...Array.from({length: count}, (_, index) => ` ${symbol}(${index}),`),
48
+ "];",
49
+ ].join("\n"),
50
+ );
51
+
52
+ const client = new Client({name: `${PRODUCT.NAME}-benchmark`, version: "1.0.0"});
53
+ const transport = new StdioClientTransport({command: process.execPath, args: [path.join(root, "server.mjs")], cwd: root});
54
+ try {
55
+ await client.connect(transport);
56
+ const text = await timedCall(client, TOOL.COUNT_TEXT_MATCHES, {root: workspace, symbol});
57
+ const verified = await timedCall(client, TOOL.COUNT_REFERENCES, {
58
+ file: target,
59
+ root: workspace,
60
+ line: 1,
61
+ column: 17,
62
+ });
63
+ const freshness = await timedCall(client, TOOL.REFERENCE_PAGE, {
64
+ referenceSetId: verified.data.result.referenceSetId,
65
+ cursor: "0",
66
+ pageSize: 1,
67
+ });
68
+ results.push({
69
+ requestedCalls: count,
70
+ exactTextMatches: text.data.result.matchesFound,
71
+ textCountWallMilliseconds: text.milliseconds,
72
+ verifiedCollectionWallMilliseconds: verified.milliseconds,
73
+ freshnessValidationWallMilliseconds: freshness.milliseconds,
74
+ verifiedReferences: verified.data.result.references.verifiedTotal,
75
+ collectionStatus: verified.data.collection.status,
76
+ performance: verified.data.collection.performance,
77
+ freshness: {
78
+ repositorySourceFilesChecked: freshness.data.collection.repositorySourceFilesChecked,
79
+ freshnessCheckMilliseconds: freshness.data.collection.freshnessCheckMilliseconds,
80
+ },
81
+ });
82
+ } finally {
83
+ await client.close().catch(() => undefined);
84
+ await rm(workspace, {recursive: true, force: true});
85
+ }
86
+ }
87
+
88
+ console.log(JSON.stringify({counts, results}, null, 2));
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {readFile} from "node:fs/promises";
4
+ import path from "node:path";
5
+ import {fileURLToPath} from "node:url";
6
+ import {
7
+ ACCOUNTING_STATUS,
8
+ COLLECTION_STATUS,
9
+ CI_REASON,
10
+ CI_STATUS,
11
+ CLI_ARGUMENT,
12
+ CLI_COMMAND,
13
+ CLI_MESSAGE,
14
+ CONFIGURATION_FILE,
15
+ CONTENT_FRESHNESS,
16
+ DEFAULT,
17
+ DEFINITION_MATCH,
18
+ DEFINITION_RESOLUTION_METHOD,
19
+ DIAGNOSTIC_EVIDENCE_REASON,
20
+ DIAGNOSTIC_FRESHNESS,
21
+ DIAGNOSTIC_SEVERITY,
22
+ DOCTOR_CHECK,
23
+ DOCTOR_DISTRIBUTION_ACCEPTED_STATUS,
24
+ DOCTOR_REASON,
25
+ DOCTOR_STATUS_PRIORITY,
26
+ EVIDENCE_STATUS,
27
+ ENVIRONMENT_VARIABLE,
28
+ ERROR_CODE,
29
+ EVIDENCE_TYPE,
30
+ FINGERPRINT_ALGORITHM,
31
+ INTERNAL_RESOLUTION_SOURCE,
32
+ LIMIT_MODE,
33
+ NODE_EVENT,
34
+ PRESENTATION_MODE,
35
+ PACKAGE_PATH,
36
+ PRODUCT,
37
+ REFERENCE_DISCOVERY_METHOD,
38
+ REFERENCE_SET_CHANGE_TYPE,
39
+ REQUIRED_RUNTIME_COMPONENT,
40
+ RUNTIME_COMMAND,
41
+ RUNTIME_REQUIREMENT,
42
+ RUNTIME_REQUIREMENT_KIND,
43
+ RESULT_SCHEMA,
44
+ TOOL,
45
+ TYPESCRIPT_PROJECT_KIND,
46
+ SOURCE_EXCLUDED_GLOBS,
47
+ SOURCE_FILE_GLOBS,
48
+ WORKSPACE_CONFIGURATION_FILE_NAMES,
49
+ WORKSPACE_ROOT_MARKER_FILE_NAMES,
50
+ RUNTIME_STATUS,
51
+ UNRESOLVED_REFERENCE_REASON,
52
+ } from "../protocol.mjs";
53
+
54
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
55
+ const runtimeSourceFiles = ["server.mjs", "cli.mjs", "lib/runtime.mjs", "lib/doctor.mjs"];
56
+ const runtimeSources = await Promise.all(
57
+ runtimeSourceFiles.map(async (file) => ({
58
+ file,
59
+ source: await readFile(path.join(root, file), "utf8"),
60
+ })),
61
+ );
62
+ const mcpConfig = JSON.parse(await readFile(path.join(root, ".mcp.json"), "utf8"));
63
+ const semanticServer = mcpConfig.mcpServers?.[PRODUCT.NAME];
64
+ if (semanticServer?.startup_timeout_sec !== DEFAULT.MCP_STARTUP_TIMEOUT_SECONDS) {
65
+ throw new Error(".mcp.json startup_timeout_sec differs from protocol.mjs");
66
+ }
67
+ if (semanticServer?.tool_timeout_sec !== DEFAULT.MCP_TOOL_TIMEOUT_SECONDS) {
68
+ throw new Error(".mcp.json tool_timeout_sec differs from protocol.mjs");
69
+ }
70
+ const groups = [
71
+ TOOL,
72
+ COLLECTION_STATUS,
73
+ PRESENTATION_MODE,
74
+ LIMIT_MODE,
75
+ ACCOUNTING_STATUS,
76
+ DEFINITION_MATCH,
77
+ DIAGNOSTIC_FRESHNESS,
78
+ DIAGNOSTIC_SEVERITY,
79
+ DIAGNOSTIC_EVIDENCE_REASON,
80
+ EVIDENCE_STATUS,
81
+ CONTENT_FRESHNESS,
82
+ ERROR_CODE,
83
+ EVIDENCE_TYPE,
84
+ FINGERPRINT_ALGORITHM,
85
+ REFERENCE_DISCOVERY_METHOD,
86
+ REFERENCE_SET_CHANGE_TYPE,
87
+ DEFINITION_RESOLUTION_METHOD,
88
+ INTERNAL_RESOLUTION_SOURCE,
89
+ UNRESOLVED_REFERENCE_REASON,
90
+ TYPESCRIPT_PROJECT_KIND,
91
+ CI_STATUS,
92
+ CI_REASON,
93
+ CLI_COMMAND,
94
+ CLI_ARGUMENT,
95
+ CLI_MESSAGE,
96
+ DOCTOR_CHECK,
97
+ DOCTOR_REASON,
98
+ Object.fromEntries(DOCTOR_DISTRIBUTION_ACCEPTED_STATUS.map((status) => [status, status])),
99
+ DOCTOR_STATUS_PRIORITY,
100
+ REQUIRED_RUNTIME_COMPONENT,
101
+ RUNTIME_COMMAND,
102
+ RUNTIME_REQUIREMENT,
103
+ RUNTIME_REQUIREMENT_KIND,
104
+ PACKAGE_PATH,
105
+ RUNTIME_STATUS,
106
+ NODE_EVENT,
107
+ PRODUCT,
108
+ ENVIRONMENT_VARIABLE,
109
+ RESULT_SCHEMA,
110
+ Object.fromEntries(WORKSPACE_CONFIGURATION_FILE_NAMES.map((name) => [name, name])),
111
+ Object.fromEntries(WORKSPACE_ROOT_MARKER_FILE_NAMES.map((name) => [name, name])),
112
+ Object.fromEntries(SOURCE_FILE_GLOBS.map((glob) => [glob, glob])),
113
+ Object.fromEntries(SOURCE_EXCLUDED_GLOBS.map((glob) => [glob, glob])),
114
+ ];
115
+ const duplicated = [];
116
+ for (const value of new Set(groups.flatMap((group) => Object.values(group)))) {
117
+ if (typeof value !== "string") continue;
118
+ const literal = JSON.stringify(value);
119
+ for (const runtimeSource of runtimeSources) {
120
+ if (runtimeSource.source.includes(literal)) duplicated.push(`${runtimeSource.file}: ${literal}`);
121
+ }
122
+ }
123
+ if (duplicated.length > 0) {
124
+ process.stderr.write(`Protocol string literals must come from protocol.mjs:\n${duplicated.join("\n")}\n`);
125
+ process.exitCode = 1;
126
+ }
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {ERROR_CODE, PROCESS_EXIT_CODE, RUNTIME_COMMAND, RUNTIME_REQUIREMENT_KIND, RUNTIME_STATUS, SERVER_VERSION} from "../protocol.mjs";
4
+ import {PACKAGE_ROOT, inspectExternalCommand, inspectNodeRuntime, inspectRuntimeComponents} from "../lib/runtime.mjs";
5
+
6
+ const node = inspectNodeRuntime();
7
+ const components = inspectRuntimeComponents();
8
+ const ripgrep = await inspectExternalCommand(RUNTIME_COMMAND.RIPGREP, ["--version"]);
9
+ const missingComponents = components.filter((component) => !component.available);
10
+ const blocked = !node.supported || missingComponents.length > 0 || !ripgrep.available;
11
+ const unmetRequirements = [
12
+ ...(!node.supported ? [{requirement: RUNTIME_REQUIREMENT_KIND.NODE, details: node}] : []),
13
+ ...missingComponents.map((component) => ({requirement: RUNTIME_REQUIREMENT_KIND.RUNTIME_COMPONENT, details: component})),
14
+ ...(!ripgrep.available ? [{requirement: RUNTIME_COMMAND.RIPGREP, details: ripgrep}] : []),
15
+ ];
16
+ const result = {
17
+ serverVersion: SERVER_VERSION,
18
+ installationRoot: PACKAGE_ROOT,
19
+ status: blocked ? RUNTIME_STATUS.BLOCKED : RUNTIME_STATUS.READY,
20
+ node,
21
+ components,
22
+ externalCommands: [ripgrep],
23
+ error: blocked
24
+ ? {
25
+ code: ERROR_CODE.RUNTIME_REQUIREMENT_UNMET,
26
+ unmetRequirements,
27
+ }
28
+ : undefined,
29
+ };
30
+
31
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
32
+ process.exitCode = blocked ? PROCESS_EXIT_CODE.FAILURE : PROCESS_EXIT_CODE.SUCCESS;