@rohal12/spindle-lsp 0.3.4 → 0.4.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/dist/bin.js CHANGED
@@ -2087,6 +2087,7 @@ var init_link_parser = __esm({
2087
2087
 
2088
2088
  // src/plugins/diagnostics.ts
2089
2089
  function computeDiagnostics(uri, workspace) {
2090
+ if (!workspace.initialized) return [];
2090
2091
  try {
2091
2092
  const text = workspace.documents.getText(uri);
2092
2093
  if (text === void 0) return [];
@@ -2109,15 +2110,13 @@ function computeDiagnostics(uri, workspace) {
2109
2110
  validateVariables(uri, workspace, diagnostics);
2110
2111
  } catch {
2111
2112
  }
2112
- if (workspace.initialized) {
2113
- try {
2114
- validateLinks(text, passages, passageNames, diagnostics);
2115
- } catch {
2116
- }
2117
- try {
2118
- validateWidgetInvocations(macros, workspace, diagnostics);
2119
- } catch {
2120
- }
2113
+ try {
2114
+ validateLinks(text, passages, passageNames, diagnostics);
2115
+ } catch {
2116
+ }
2117
+ try {
2118
+ validateWidgetInvocations(macros, workspace, diagnostics);
2119
+ } catch {
2121
2120
  }
2122
2121
  return diagnostics;
2123
2122
  } catch {
@@ -2278,7 +2277,6 @@ function validateArguments(macros, workspace, passageNames, diagnostics) {
2278
2277
  }
2279
2278
  }
2280
2279
  function validateVariables(uri, workspace, diagnostics) {
2281
- if (!workspace.initialized) return;
2282
2280
  if (!workspace.variables.hasStoryVariables()) {
2283
2281
  const undeclared2 = workspace.variables.getUndeclared(uri);
2284
2282
  const text = workspace.documents.getText(uri);
@@ -2981,6 +2979,197 @@ var init_format2 = __esm({
2981
2979
  }
2982
2980
  });
2983
2981
 
2982
+ // src/mcp/server.ts
2983
+ var server_exports = {};
2984
+ __export(server_exports, {
2985
+ startMcpServer: () => startMcpServer
2986
+ });
2987
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "node:fs";
2988
+ import { resolve as resolve2 } from "node:path";
2989
+ import { pathToFileURL as pathToFileURL3, fileURLToPath as fileURLToPath3 } from "node:url";
2990
+ import { glob as glob3 } from "glob";
2991
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2992
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2993
+ import { z } from "zod";
2994
+ function filterBySeverity2(diagnostics, minSeverity) {
2995
+ const minIdx = SEVERITY_ORDER2.indexOf(minSeverity);
2996
+ return diagnostics.filter((d) => {
2997
+ const idx = SEVERITY_ORDER2.indexOf(d.severity);
2998
+ return idx >= minIdx;
2999
+ });
3000
+ }
3001
+ async function resolveFiles(pattern, cwd) {
3002
+ const matches = await glob3(pattern, {
3003
+ cwd,
3004
+ absolute: true,
3005
+ nodir: true
3006
+ });
3007
+ return [...new Set(matches)];
3008
+ }
3009
+ function findConfigRoot(files) {
3010
+ const dirs = files.map((f) => resolve2(f, ".."));
3011
+ let configRoot = dirs.reduce((a, b) => {
3012
+ while (!b.startsWith(a)) a = resolve2(a, "..");
3013
+ return a;
3014
+ });
3015
+ let search = configRoot;
3016
+ for (let i = 0; i < 10; i++) {
3017
+ if (findConfigFile(search)) {
3018
+ configRoot = search;
3019
+ break;
3020
+ }
3021
+ const parent = resolve2(search, "..");
3022
+ if (parent === search) break;
3023
+ search = parent;
3024
+ }
3025
+ return configRoot;
3026
+ }
3027
+ function createWorkspace(files) {
3028
+ const configRoot = findConfigRoot(files);
3029
+ const projectConfig = loadConfigFromDisk(configRoot);
3030
+ const workspace = new WorkspaceModel();
3031
+ const fileContents = /* @__PURE__ */ new Map();
3032
+ for (const filePath of files) {
3033
+ try {
3034
+ const text = readFileSync6(filePath, "utf-8");
3035
+ const uri = pathToFileURL3(filePath).toString();
3036
+ fileContents.set(uri, text);
3037
+ } catch {
3038
+ }
3039
+ }
3040
+ workspace.initialize(fileContents);
3041
+ if (Object.keys(projectConfig.macros).length > 0) {
3042
+ workspace.macros.loadSupplements(projectConfig.macros);
3043
+ }
3044
+ return workspace;
3045
+ }
3046
+ async function startMcpServer() {
3047
+ const server = new McpServer({
3048
+ name: "spindle-lsp",
3049
+ version: "0.3.5"
3050
+ });
3051
+ server.tool(
3052
+ "spindle_check",
3053
+ "Run diagnostics on .tw/.twee files. Returns structured diagnostic results.",
3054
+ {
3055
+ path: z.string().default("**/*.{tw,twee}").describe("Glob pattern or directory to check"),
3056
+ severity: z.enum(["error", "warning", "info", "hint"]).optional().describe("Minimum severity to include")
3057
+ },
3058
+ async (args2) => {
3059
+ const cwd = process.cwd();
3060
+ const files = await resolveFiles(args2.path, cwd);
3061
+ if (files.length === 0) {
3062
+ return {
3063
+ content: [{ type: "text", text: JSON.stringify([], null, 2) }]
3064
+ };
3065
+ }
3066
+ const workspace = createWorkspace(files);
3067
+ try {
3068
+ const results = [];
3069
+ for (const [uri] of workspace.documents.getUris().map((u) => [u])) {
3070
+ let diags = computeDiagnostics(uri, workspace);
3071
+ if (args2.severity) {
3072
+ diags = filterBySeverity2(diags, args2.severity);
3073
+ }
3074
+ const filePath = fileURLToPath3(uri);
3075
+ const relativePath = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
3076
+ for (const d of diags) {
3077
+ results.push({
3078
+ file: relativePath,
3079
+ line: d.range.start.line + 1,
3080
+ column: d.range.start.character + 1,
3081
+ severity: d.severity,
3082
+ code: d.code,
3083
+ message: d.message
3084
+ });
3085
+ }
3086
+ }
3087
+ return {
3088
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }]
3089
+ };
3090
+ } finally {
3091
+ workspace.dispose();
3092
+ }
3093
+ }
3094
+ );
3095
+ server.tool(
3096
+ "spindle_format",
3097
+ "Format .tw/.twee files in place.",
3098
+ {
3099
+ path: z.string().default("**/*.{tw,twee}").describe("Glob pattern or directory to format")
3100
+ },
3101
+ async (args2) => {
3102
+ const cwd = process.cwd();
3103
+ const files = await resolveFiles(args2.path, cwd);
3104
+ let formatted = 0;
3105
+ let unchanged = 0;
3106
+ const changedFiles = [];
3107
+ for (const filePath of files) {
3108
+ try {
3109
+ const text = readFileSync6(filePath, "utf-8");
3110
+ const result = formatDocument(text);
3111
+ if (result !== text) {
3112
+ writeFileSync2(filePath, result, "utf-8");
3113
+ formatted++;
3114
+ const relativePath = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
3115
+ changedFiles.push(relativePath);
3116
+ } else {
3117
+ unchanged++;
3118
+ }
3119
+ } catch {
3120
+ }
3121
+ }
3122
+ const output = { formatted, unchanged, files: changedFiles };
3123
+ return {
3124
+ content: [{ type: "text", text: JSON.stringify(output, null, 2) }]
3125
+ };
3126
+ }
3127
+ );
3128
+ server.tool(
3129
+ "spindle_format_check",
3130
+ "Check formatting without modifying files. Returns list of files that need formatting.",
3131
+ {
3132
+ path: z.string().default("**/*.{tw,twee}").describe("Glob pattern or directory to check")
3133
+ },
3134
+ async (args2) => {
3135
+ const cwd = process.cwd();
3136
+ const files = await resolveFiles(args2.path, cwd);
3137
+ const needsFormatting = [];
3138
+ const alreadyFormatted = [];
3139
+ for (const filePath of files) {
3140
+ try {
3141
+ const text = readFileSync6(filePath, "utf-8");
3142
+ const result = formatDocument(text);
3143
+ const relativePath = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
3144
+ if (result !== text) {
3145
+ needsFormatting.push(relativePath);
3146
+ } else {
3147
+ alreadyFormatted.push(relativePath);
3148
+ }
3149
+ } catch {
3150
+ }
3151
+ }
3152
+ const output = { needsFormatting, alreadyFormatted };
3153
+ return {
3154
+ content: [{ type: "text", text: JSON.stringify(output, null, 2) }]
3155
+ };
3156
+ }
3157
+ );
3158
+ const transport = new StdioServerTransport();
3159
+ await server.connect(transport);
3160
+ }
3161
+ var SEVERITY_ORDER2;
3162
+ var init_server = __esm({
3163
+ "src/mcp/server.ts"() {
3164
+ "use strict";
3165
+ init_workspace_model();
3166
+ init_config_loader();
3167
+ init_diagnostics();
3168
+ init_format();
3169
+ SEVERITY_ORDER2 = ["hint", "info", "warning", "error"];
3170
+ }
3171
+ });
3172
+
2984
3173
  // src/core/plugin/plugin-loader.ts
2985
3174
  function loadPlugins(allPlugins2, config) {
2986
3175
  const disabled = new Set(config.disabledPlugins ?? []);
@@ -4835,8 +5024,8 @@ var init_plugins = __esm({
4835
5024
  });
4836
5025
 
4837
5026
  // src/server/server.ts
4838
- var server_exports = {};
4839
- __export(server_exports, {
5027
+ var server_exports2 = {};
5028
+ __export(server_exports2, {
4840
5029
  startServer: () => startServer
4841
5030
  });
4842
5031
  import {
@@ -4846,18 +5035,18 @@ import {
4846
5035
  FileChangeType
4847
5036
  } from "vscode-languageserver/node.js";
4848
5037
  import { TextDocument } from "vscode-languageserver-textdocument";
4849
- import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL3 } from "node:url";
4850
- import { readFileSync as readFileSync6 } from "node:fs";
4851
- import { glob as glob3 } from "glob";
5038
+ import { fileURLToPath as fileURLToPath4, pathToFileURL as pathToFileURL4 } from "node:url";
5039
+ import { readFileSync as readFileSync7 } from "node:fs";
5040
+ import { glob as glob4 } from "glob";
4852
5041
  function uriToFsPath(uri) {
4853
5042
  try {
4854
- return fileURLToPath3(uri);
5043
+ return fileURLToPath4(uri);
4855
5044
  } catch {
4856
5045
  return uri;
4857
5046
  }
4858
5047
  }
4859
5048
  function fsPathToUri(fsPath) {
4860
- return pathToFileURL3(fsPath).toString();
5049
+ return pathToFileURL4(fsPath).toString();
4861
5050
  }
4862
5051
  function startServer(_args) {
4863
5052
  const connection = createConnection(ProposedFeatures.all);
@@ -4956,7 +5145,7 @@ function startServer(_args) {
4956
5145
  } else {
4957
5146
  try {
4958
5147
  const fsPath = uriToFsPath(change.uri);
4959
- const text = readFileSync6(fsPath, "utf-8");
5148
+ const text = readFileSync7(fsPath, "utf-8");
4960
5149
  if (workspace.documents.has(change.uri)) {
4961
5150
  workspace.documents.update(change.uri, text);
4962
5151
  } else {
@@ -4979,14 +5168,14 @@ async function scanWorkspaceFiles(root) {
4979
5168
  const contents = /* @__PURE__ */ new Map();
4980
5169
  if (!root) return contents;
4981
5170
  try {
4982
- const files = await glob3("**/*.{tw,twee}", {
5171
+ const files = await glob4("**/*.{tw,twee}", {
4983
5172
  cwd: root,
4984
5173
  absolute: true,
4985
5174
  nodir: true
4986
5175
  });
4987
5176
  for (const filePath of files) {
4988
5177
  try {
4989
- const text = readFileSync6(filePath, "utf-8");
5178
+ const text = readFileSync7(filePath, "utf-8");
4990
5179
  const uri = fsPathToUri(filePath);
4991
5180
  contents.set(uri, text);
4992
5181
  } catch {
@@ -4996,7 +5185,7 @@ async function scanWorkspaceFiles(root) {
4996
5185
  }
4997
5186
  return contents;
4998
5187
  }
4999
- var init_server = __esm({
5188
+ var init_server2 = __esm({
5000
5189
  "src/server/server.ts"() {
5001
5190
  "use strict";
5002
5191
  init_workspace_model();
@@ -5017,8 +5206,11 @@ if (args[0] === "check") {
5017
5206
  const { runFormat: runFormat2 } = await Promise.resolve().then(() => (init_format2(), format_exports));
5018
5207
  const exitCode = await runFormat2(args.slice(1));
5019
5208
  process.exit(exitCode);
5209
+ } else if (args[0] === "mcp") {
5210
+ const { startMcpServer: startMcpServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
5211
+ await startMcpServer2();
5020
5212
  } else {
5021
- const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
5213
+ const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server2(), server_exports2));
5022
5214
  startServer2(args);
5023
5215
  }
5024
5216
  //# sourceMappingURL=bin.js.map